Skip to content

Commit f08a0f2

Browse files
authored
Merge pull request #350 from ialarmedalien/bugfix/issue-2422
Ensure that relative imports can be imported without requiring `./` in front of the import file name
2 parents 905789c + 45fac3b commit f08a0f2

File tree

9 files changed

+95
-27
lines changed

9 files changed

+95
-27
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Runtime support for linkml generated data classes.
1111

1212
## About
1313

14-
This python library provides runtime support for [LinkML](https://linkml.io/linkml/) datamodels.
14+
This Python library provides runtime support for [LinkML](https://linkml.io/linkml/) datamodels.
1515

1616
See the [LinkML repo](https://github.com/linkml/linkml) for the [Python Dataclass Generator](https://linkml.io/linkml/generators/python.html) which will convert a schema into a Python object model. That model will have dependencies on functionality in this library.
1717

@@ -24,8 +24,8 @@ See [working with data](https://linkml.io/linkml/data/index.html) in the documen
2424

2525
This repository also contains the Python dataclass representation of the [LinkML metamodel](https://github.com/linkml/linkml-model), and various utility functions that are useful for working with LinkML data and schemas.
2626

27-
It also includes the [SchemaView](https://linkml.io/linkml/developers/manipulating-schemas.html) class for working with LinkML schemas
27+
It also includes the [SchemaView](https://linkml.io/linkml/developers/manipulating-schemas.html) class for working with LinkML schemas.
2828

2929
## Notebooks
3030

31-
See the [notebooks](https://github.com/linkml/linkml-runtime/tree/main/notebooks) folder for examples
31+
See the [notebooks](https://github.com/linkml/linkml-runtime/tree/main/notebooks) folder for examples.

linkml_runtime/utils/schemaview.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -108,25 +108,6 @@ def is_absolute_path(path: str) -> bool:
108108
drive, tail = os.path.splitdrive(norm_path)
109109
return bool(drive and tail)
110110

111-
def _resolve_import(source_sch: str, imported_sch: str) -> str:
112-
if os.path.isabs(imported_sch):
113-
# Absolute import paths are not modified
114-
return imported_sch
115-
if urlparse(imported_sch).scheme:
116-
# File with URL schemes are not modified
117-
return imported_sch
118-
119-
if WINDOWS:
120-
path = PurePath(os.path.normpath(PurePath(source_sch).parent / imported_sch)).as_posix()
121-
else:
122-
path = os.path.normpath(str(Path(source_sch).parent / imported_sch))
123-
124-
if imported_sch.startswith(".") and not path.startswith("."):
125-
# Above condition handles cases where both source schema and imported schema are relative paths: these should remain relative
126-
return f"./{path}"
127-
128-
return path
129-
130111

131112
@dataclass
132113
class SchemaUsage:
@@ -319,14 +300,24 @@ def imports_closure(self, imports: bool = True, traverse: Optional[bool] = None,
319300
# path, and the target import doesn't have : (as in a curie or a URI)
320301
# we prepend the relative path. This WILL make the key in the `schema_map` not
321302
# equal to the literal text specified in the importing schema, but this is
322-
# essential to sensible deduplication: eg. for
303+
# essential to sensible deduplication: e.g. for
323304
# - main.yaml (imports ./types.yaml, ./subdir/subschema.yaml)
324305
# - types.yaml
325306
# - subdir/subschema.yaml (imports ./types.yaml)
326307
# - subdir/types.yaml
327308
# we should treat the two `types.yaml` as separate schemas from the POV of the
328309
# origin schema.
329-
i = _resolve_import(sn, i)
310+
311+
# if i is not a CURIE and sn looks like a path with at least one parent folder,
312+
# normalise i with respect to sn
313+
if "/" in sn and ":" not in i:
314+
if WINDOWS:
315+
# This cannot be simplified. os.path.normpath() must be called before .as_posix()
316+
i = PurePath(
317+
os.path.normpath(PurePath(sn).parent / i)
318+
).as_posix()
319+
else:
320+
i = os.path.normpath(str(Path(sn).parent / i))
330321
todo.append(i)
331322

332323
# add item to closure
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
id: four
2+
name: import_four
3+
title: Import Four
4+
description: |
5+
Import loaded by the StepChild class.
6+
imports:
7+
- linkml:types
8+
classes:
9+
Four:
10+
attributes:
11+
value:
12+
range: string
13+
ifabsent: "Four"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
id: one
2+
name: import_one
3+
title: Import One
4+
description: |
5+
Import loaded by the StepChild class.
6+
imports:
7+
- linkml:types
8+
- two
9+
classes:
10+
One:
11+
attributes:
12+
value:
13+
range: string
14+
ifabsent: "One"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
id: stepchild
2+
name: stepchild
3+
title: stepchild
4+
description: |
5+
Child class that imports files in the same directory as itself without consistently using `./` in the link notation.
6+
imports:
7+
- linkml:types
8+
- one
9+
- two
10+
- ./three
11+
classes:
12+
StepChild:
13+
attributes:
14+
value:
15+
range: string
16+
ifabsent: "StepChild"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
id: three
2+
name: import_three
3+
title: Import Three
4+
description: |
5+
Import loaded by the StepChild class.
6+
imports:
7+
- linkml:types
8+
- ./four
9+
classes:
10+
Three:
11+
attributes:
12+
value:
13+
range: string
14+
ifabsent: "Three"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
id: two
2+
name: import_two
3+
title: Import Two
4+
description: |
5+
Import loaded by the StepChild class.
6+
imports:
7+
- linkml:types
8+
classes:
9+
Two:
10+
attributes:
11+
value:
12+
range: string
13+
ifabsent: "Two"

tests/test_utils/input/imports_relative/L0_0/L1_0_0/main.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ imports:
88
- ../../L0_1/cousin
99
- ./L2_0_0_0/child
1010
- ./L2_0_0_1/child
11+
- L2_0_0_2/stepchild
1112
classes:
1213
Main:
1314
description: "Our intrepid main class!"
1415
attributes:
1516
value:
1617
range: string
17-
ifabsent: "Main"
18+
ifabsent: "Main"

tests/test_utils/test_schemaview.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
SCHEMA_NO_IMPORTS = Path(INPUT_DIR) / 'kitchen_sink_noimports.yaml'
2121
SCHEMA_WITH_IMPORTS = Path(INPUT_DIR) / 'kitchen_sink.yaml'
22-
SCHEMA_WITH_STRUCTURED_PATTERNS = Path(INPUT_DIR) / "pattern-example.yaml"
22+
SCHEMA_WITH_STRUCTURED_PATTERNS = Path(INPUT_DIR) / 'pattern-example.yaml'
2323
SCHEMA_IMPORT_TREE = Path(INPUT_DIR) / 'imports' / 'main.yaml'
2424
SCHEMA_RELATIVE_IMPORT_TREE = Path(INPUT_DIR) / 'imports_relative' / 'L0_0' / 'L1_0_0' / 'main.yaml'
2525
SCHEMA_RELATIVE_IMPORT_TREE2 = Path(INPUT_DIR) / 'imports_relative' / 'L0_2' / 'main.yaml'
@@ -357,7 +357,7 @@ def test_caching():
357357
view.add_class(ClassDefinition('X'))
358358
assert len(['X']) == len(view.all_classes())
359359
view.add_class(ClassDefinition('Y'))
360-
assert len(['X', 'Y']) == len(view.all_classes())
360+
assert len(['X', 'Y']) == len(view.all_classes())
361361
# bypass view method and add directly to schema;
362362
# in general this is not recommended as the cache will
363363
# not be updated
@@ -546,6 +546,11 @@ def test_imports_relative():
546546
'../L1_0_1/dupe',
547547
'./L2_0_0_0/child',
548548
'./L2_0_0_1/child',
549+
'L2_0_0_2/two',
550+
'L2_0_0_2/one',
551+
'L2_0_0_2/four',
552+
'L2_0_0_2/three',
553+
'L2_0_0_2/stepchild',
549554
'main'
550555
]
551556

@@ -716,6 +721,7 @@ def test_slot_inheritance():
716721
with pytest.raises(ValueError):
717722
view.slot_ancestors('s5')
718723

724+
719725
def test_attribute_inheritance():
720726
"""
721727
Tests attribute inheritance edge cases.

0 commit comments

Comments
 (0)