linkml 1.7.7__py3-none-any.whl → 1.7.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- linkml/__init__.py +14 -0
- linkml/generators/csvgen.py +15 -5
- linkml/generators/docgen/class_diagram.md.jinja2 +19 -4
- linkml/generators/docgen/subset.md.jinja2 +7 -7
- linkml/generators/docgen.py +59 -14
- linkml/generators/graphqlgen.py +14 -16
- linkml/generators/jsonldcontextgen.py +3 -3
- linkml/generators/jsonldgen.py +4 -3
- linkml/generators/jsonschemagen.py +9 -0
- linkml/generators/markdowngen.py +341 -301
- linkml/generators/owlgen.py +87 -20
- linkml/generators/plantumlgen.py +9 -8
- linkml/generators/prefixmapgen.py +15 -23
- linkml/generators/protogen.py +23 -18
- linkml/generators/pydanticgen/array.py +15 -3
- linkml/generators/pydanticgen/pydanticgen.py +13 -2
- linkml/generators/pydanticgen/template.py +6 -4
- linkml/generators/pythongen.py +5 -5
- linkml/generators/rdfgen.py +14 -5
- linkml/generators/shaclgen.py +18 -6
- linkml/generators/shexgen.py +9 -7
- linkml/generators/sqlalchemygen.py +1 -0
- linkml/generators/sqltablegen.py +16 -1
- linkml/generators/summarygen.py +8 -2
- linkml/generators/terminusdbgen.py +2 -2
- linkml/generators/yumlgen.py +2 -2
- linkml/transformers/relmodel_transformer.py +21 -1
- linkml/utils/__init__.py +3 -0
- linkml/utils/deprecation.py +255 -0
- linkml/utils/generator.py +87 -61
- linkml/utils/rawloader.py +5 -1
- linkml/utils/schemaloader.py +2 -1
- linkml/utils/sqlutils.py +13 -14
- linkml/validator/cli.py +11 -0
- linkml/validator/plugins/jsonschema_validation_plugin.py +2 -0
- linkml/validator/plugins/shacl_validation_plugin.py +10 -4
- linkml/validator/report.py +1 -0
- linkml/workspaces/example_runner.py +2 -0
- {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/METADATA +2 -1
- {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/RECORD +43 -42
- {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/LICENSE +0 -0
- {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/WHEEL +0 -0
- {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/entry_points.txt +0 -0
linkml/utils/generator.py
CHANGED
@@ -20,17 +20,14 @@ import logging
|
|
20
20
|
import os
|
21
21
|
import re
|
22
22
|
import sys
|
23
|
-
from contextlib import redirect_stdout
|
24
23
|
from dataclasses import dataclass, field
|
25
24
|
from functools import lru_cache
|
26
|
-
from io import StringIO
|
27
25
|
from pathlib import Path
|
28
26
|
from typing import Callable, ClassVar, Dict, List, Mapping, Optional, Set, TextIO, Type, Union, cast
|
29
27
|
|
30
28
|
import click
|
31
29
|
from click import Argument, Command, Option
|
32
30
|
from linkml_runtime import SchemaView
|
33
|
-
from linkml_runtime.dumpers import yaml_dumper
|
34
31
|
from linkml_runtime.linkml_model.meta import (
|
35
32
|
ClassDefinition,
|
36
33
|
ClassDefinitionName,
|
@@ -83,7 +80,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
83
80
|
For usage `Generator Docs <https://linkml.io/linkml/generators/>`_
|
84
81
|
"""
|
85
82
|
|
86
|
-
schema: Union[str, TextIO, SchemaDefinition, "Generator"]
|
83
|
+
schema: Union[str, TextIO, SchemaDefinition, "Generator", Path]
|
87
84
|
"""metamodel compliant schema. Can be URI, file name, actual schema, another generator, an
|
88
85
|
open file or a pre-parsed schema"""
|
89
86
|
|
@@ -130,7 +127,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
130
127
|
useuris: Optional[bool] = None
|
131
128
|
"""True means declared class slot uri's are used. False means use model uris"""
|
132
129
|
|
133
|
-
log_level: int = DEFAULT_LOG_LEVEL_INT
|
130
|
+
log_level: Optional[int] = DEFAULT_LOG_LEVEL_INT
|
134
131
|
"""Logging level, 0 is minimum"""
|
135
132
|
|
136
133
|
mergeimports: Optional[bool] = True
|
@@ -177,9 +174,14 @@ class Generator(metaclass=abc.ABCMeta):
|
|
177
174
|
stacktrace: bool = False
|
178
175
|
"""True means print stack trace, false just error message"""
|
179
176
|
|
177
|
+
include: Optional[Union[str, Path, SchemaDefinition]] = None
|
178
|
+
"""If set, include extra schema outside of the imports mechanism"""
|
179
|
+
|
180
180
|
def __post_init__(self) -> None:
|
181
181
|
if not self.logger:
|
182
182
|
self.logger = logging.getLogger()
|
183
|
+
if self.log_level is not None:
|
184
|
+
self.logger.setLevel(self.log_level)
|
183
185
|
if self.format is None:
|
184
186
|
self.format = self.valid_formats[0]
|
185
187
|
if self.format not in self.valid_formats:
|
@@ -191,7 +193,11 @@ class Generator(metaclass=abc.ABCMeta):
|
|
191
193
|
self.source_file_size = None
|
192
194
|
if self.requires_metamodel:
|
193
195
|
self.metamodel = _resolved_metamodel(self.mergeimports)
|
196
|
+
|
194
197
|
schema = self.schema
|
198
|
+
if isinstance(schema, Path):
|
199
|
+
schema = str(schema)
|
200
|
+
|
195
201
|
# TODO: remove aliasing
|
196
202
|
self.emit_metadata = self.metadata
|
197
203
|
if self.uses_schemaloader:
|
@@ -199,7 +205,12 @@ class Generator(metaclass=abc.ABCMeta):
|
|
199
205
|
else:
|
200
206
|
logging.info(f"Using SchemaView with im={self.importmap} // base_dir={self.base_dir}")
|
201
207
|
self.schemaview = SchemaView(schema, importmap=self.importmap, base_dir=self.base_dir)
|
208
|
+
if self.include:
|
209
|
+
if isinstance(self.include, (str, Path)):
|
210
|
+
self.include = SchemaView(self.include, importmap=self.importmap, base_dir=self.base_dir).schema
|
211
|
+
self.schemaview.merge_schema(self.include)
|
202
212
|
self.schema = self.schemaview.schema
|
213
|
+
|
203
214
|
self._init_namespaces()
|
204
215
|
|
205
216
|
def _initialize_using_schemaloader(self, schema: Union[str, TextIO, SchemaDefinition, "Generator"]):
|
@@ -225,8 +236,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
225
236
|
# schemaloader based methods require schemas to have been created via SchemaLoader,
|
226
237
|
# which prepopulates some fields (e.g. definition_url). If the schema has not been processed through the
|
227
238
|
# loader, then roundtrip
|
228
|
-
|
229
|
-
schema = yaml_dumper.dumps(schema)
|
239
|
+
schema = schema._as_dict
|
230
240
|
loader = SchemaLoader(
|
231
241
|
schema,
|
232
242
|
self.base_dir,
|
@@ -263,66 +273,82 @@ class Generator(metaclass=abc.ABCMeta):
|
|
263
273
|
:param kwargs: Generator specific parameters
|
264
274
|
:return: Generated output
|
265
275
|
"""
|
266
|
-
|
267
|
-
|
268
|
-
#
|
269
|
-
#
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
)
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
)
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
):
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
)
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
self.
|
309
|
-
|
310
|
-
|
311
|
-
|
276
|
+
out = ""
|
277
|
+
|
278
|
+
# the default is to use the Visitor Pattern; each individual generator may
|
279
|
+
# choose to override methods {visit,end}_{element}.
|
280
|
+
# See https://github.com/linkml/linkml/issues/923
|
281
|
+
sub_out = self.visit_schema(**kwargs)
|
282
|
+
if sub_out is not None:
|
283
|
+
out += sub_out
|
284
|
+
for sn, ss in (
|
285
|
+
sorted(self.schema.subsets.items(), key=lambda s: s[0].lower())
|
286
|
+
if self.visits_are_sorted
|
287
|
+
else self.schema.subsets.items()
|
288
|
+
):
|
289
|
+
sub_out = self.visit_subset(ss)
|
290
|
+
if sub_out is not None:
|
291
|
+
out += sub_out
|
292
|
+
for tn, typ in (
|
293
|
+
sorted(self.schema.types.items(), key=lambda s: s[0].lower())
|
294
|
+
if self.visits_are_sorted
|
295
|
+
else self.schema.types.items()
|
296
|
+
):
|
297
|
+
sub_out = self.visit_type(typ)
|
298
|
+
if sub_out is not None:
|
299
|
+
out += sub_out
|
300
|
+
for enum in (
|
301
|
+
sorted(self.schema.enums.values(), key=lambda e: e.name.lower())
|
302
|
+
if self.visits_are_sorted
|
303
|
+
else self.schema.enums.values()
|
304
|
+
):
|
305
|
+
sub_out = self.visit_enum(enum)
|
306
|
+
if sub_out is not None:
|
307
|
+
out += sub_out
|
308
|
+
for sn, slot in (
|
309
|
+
sorted(self.schema.slots.items(), key=lambda c: c[0].lower())
|
310
|
+
if self.visits_are_sorted
|
311
|
+
else self.schema.slots.items()
|
312
|
+
):
|
313
|
+
sub_out = self.visit_slot(self.aliased_slot_name(slot), slot)
|
314
|
+
if sub_out is not None:
|
315
|
+
out += sub_out
|
316
|
+
for cls in (
|
317
|
+
sorted(self.schema.classes.values(), key=lambda c: c.name.lower())
|
318
|
+
if self.visits_are_sorted
|
319
|
+
else self.schema.classes.values()
|
320
|
+
):
|
321
|
+
cls_out = self.visit_class(cls)
|
322
|
+
if cls_out:
|
323
|
+
if isinstance(cls_out, str):
|
324
|
+
out += cls_out
|
325
|
+
for slot in self.all_slots(cls) if self.visit_all_class_slots else self.own_slots(cls):
|
326
|
+
sub_out = self.visit_class_slot(cls, self.aliased_slot_name(slot), slot)
|
327
|
+
if sub_out is not None:
|
328
|
+
out += sub_out
|
329
|
+
sub_out = self.end_class(cls)
|
330
|
+
if sub_out is not None:
|
331
|
+
out += sub_out
|
332
|
+
sub_out = self.end_schema(**kwargs)
|
333
|
+
if sub_out is not None:
|
334
|
+
out += sub_out
|
335
|
+
return out
|
336
|
+
|
337
|
+
def visit_schema(self, **kwargs) -> Optional[str]:
|
312
338
|
"""Visited once at the beginning of generation
|
313
339
|
|
314
340
|
@param kwargs: Arguments passed through from CLI -- implementation dependent
|
315
341
|
"""
|
316
342
|
...
|
317
343
|
|
318
|
-
def end_schema(self, **kwargs) ->
|
344
|
+
def end_schema(self, **kwargs) -> Optional[str]:
|
319
345
|
"""Visited once at the end of generation
|
320
346
|
|
321
347
|
@param kwargs: Arguments passed through from CLI -- implementation dependent
|
322
348
|
"""
|
323
349
|
...
|
324
350
|
|
325
|
-
def visit_class(self, cls: ClassDefinition) -> bool:
|
351
|
+
def visit_class(self, cls: ClassDefinition) -> Optional[Union[str, bool]]:
|
326
352
|
"""Visited once per schema class
|
327
353
|
|
328
354
|
@param cls: class being visited
|
@@ -330,14 +356,14 @@ class Generator(metaclass=abc.ABCMeta):
|
|
330
356
|
"""
|
331
357
|
return True
|
332
358
|
|
333
|
-
def end_class(self, cls: ClassDefinition) ->
|
359
|
+
def end_class(self, cls: ClassDefinition) -> Optional[str]:
|
334
360
|
"""Visited after visit_class_slots (if visit_class returned true)
|
335
361
|
|
336
362
|
@param cls: class being visited
|
337
363
|
"""
|
338
364
|
...
|
339
365
|
|
340
|
-
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) ->
|
366
|
+
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> Optional[str]:
|
341
367
|
"""Visited for each slot in a class. If class level visit_all_slots is true, this is visited once
|
342
368
|
for any class that is inherited (class itself, is_a, mixin, apply_to). Otherwise, just the own slots.
|
343
369
|
|
@@ -347,7 +373,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
347
373
|
"""
|
348
374
|
...
|
349
375
|
|
350
|
-
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) ->
|
376
|
+
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> Optional[str]:
|
351
377
|
"""Visited once for every slot definition in the schema.
|
352
378
|
|
353
379
|
@param aliased_slot_name: Aliased name of the slot. May not be unique
|
@@ -355,21 +381,21 @@ class Generator(metaclass=abc.ABCMeta):
|
|
355
381
|
"""
|
356
382
|
...
|
357
383
|
|
358
|
-
def visit_type(self, typ: TypeDefinition) ->
|
384
|
+
def visit_type(self, typ: TypeDefinition) -> Optional[str]:
|
359
385
|
"""Visited once for every type definition in the schema
|
360
386
|
|
361
387
|
@param typ: Type definition
|
362
388
|
"""
|
363
389
|
...
|
364
390
|
|
365
|
-
def visit_subset(self, subset: SubsetDefinition) ->
|
391
|
+
def visit_subset(self, subset: SubsetDefinition) -> Optional[str]:
|
366
392
|
"""Visited once for every subset definition in the schema
|
367
393
|
|
368
394
|
#param subset: Subset definition
|
369
395
|
"""
|
370
396
|
...
|
371
397
|
|
372
|
-
def visit_enum(self, enum: EnumDefinition) ->
|
398
|
+
def visit_enum(self, enum: EnumDefinition) -> Optional[str]:
|
373
399
|
"""Visited once for every enum definition in the schema
|
374
400
|
|
375
401
|
@param enum: Enum definition
|
linkml/utils/rawloader.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import copy
|
2
2
|
from datetime import datetime
|
3
|
+
from pathlib import Path
|
3
4
|
from typing import Optional, TextIO, Union
|
4
5
|
from urllib.parse import urlparse
|
5
6
|
|
@@ -29,7 +30,7 @@ SchemaDefinition.MissingRequiredField = mrf
|
|
29
30
|
|
30
31
|
|
31
32
|
def load_raw_schema(
|
32
|
-
data: Union[str, dict, TextIO],
|
33
|
+
data: Union[str, dict, TextIO, Path],
|
33
34
|
source_file: Optional[str] = None,
|
34
35
|
source_file_date: Optional[str] = None,
|
35
36
|
source_file_size: Optional[int] = None,
|
@@ -58,6 +59,9 @@ def load_raw_schema(
|
|
58
59
|
assert source_file_date is None, "source_file_date parameter not allowed if data is a file or URL"
|
59
60
|
assert source_file_size is None, "source_file_size parameter not allowed if data is a file or URL"
|
60
61
|
|
62
|
+
if isinstance(data, Path):
|
63
|
+
data = str(data)
|
64
|
+
|
61
65
|
# Convert the input into a valid SchemaDefinition
|
62
66
|
if isinstance(data, (str, dict, TextIO)):
|
63
67
|
# TODO: Build a generic loader that detects type from suffix or content and invokes the appropriate loader
|
linkml/utils/schemaloader.py
CHANGED
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
import os
|
3
3
|
from collections import OrderedDict
|
4
4
|
from copy import deepcopy
|
5
|
+
from pathlib import Path
|
5
6
|
from typing import Dict, Iterator, List, Mapping, Optional, Set, TextIO, Tuple, Union, cast
|
6
7
|
from urllib.parse import urlparse
|
7
8
|
|
@@ -32,7 +33,7 @@ from linkml.utils.schemasynopsis import SchemaSynopsis
|
|
32
33
|
class SchemaLoader:
|
33
34
|
def __init__(
|
34
35
|
self,
|
35
|
-
data: Union[str, TextIO, SchemaDefinition, dict],
|
36
|
+
data: Union[str, TextIO, SchemaDefinition, dict, Path],
|
36
37
|
base_dir: Optional[str] = None,
|
37
38
|
namespaces: Optional[Namespaces] = None,
|
38
39
|
useuris: Optional[bool] = None,
|
linkml/utils/sqlutils.py
CHANGED
@@ -58,16 +58,22 @@ class SQLStore:
|
|
58
58
|
- mapping your data/objects in any LinkML compliant data format (json. yaml, rdf) into ORM objects
|
59
59
|
"""
|
60
60
|
|
61
|
-
schema: Union[str, SchemaDefinition] = None
|
62
|
-
schemaview: SchemaView = None
|
63
|
-
engine: Engine = None
|
64
|
-
database_path: str = None
|
61
|
+
schema: Optional[Union[str, Path, SchemaDefinition]] = None
|
62
|
+
schemaview: Optional[SchemaView] = None
|
63
|
+
engine: Optional[Engine] = None
|
64
|
+
database_path: Union[str, Path] = None
|
65
65
|
use_memory: bool = False
|
66
66
|
"""https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#using-a-memory-database-in-multiple-threads"""
|
67
67
|
|
68
|
-
module: ModuleType = None
|
69
|
-
native_module: ModuleType = None
|
70
|
-
include_schema_in_database: bool =
|
68
|
+
module: Optional[ModuleType] = None
|
69
|
+
native_module: Optional[ModuleType] = None
|
70
|
+
include_schema_in_database: bool = False
|
71
|
+
|
72
|
+
def __post_init__(self):
|
73
|
+
if self.database_path is None and not self.use_memory:
|
74
|
+
raise ValueError("Must have database path or use_memory must be True")
|
75
|
+
if self.schema is not None and self.schemaview is None:
|
76
|
+
self.schemaview = SchemaView(self.schema)
|
71
77
|
|
72
78
|
def db_exists(self, create=True, force=False) -> Optional[str]:
|
73
79
|
"""
|
@@ -117,8 +123,6 @@ class SQLStore:
|
|
117
123
|
"""
|
118
124
|
gen = SQLAlchemyGenerator(self.schema)
|
119
125
|
self.module = gen.compile_sqla(template=TemplateEnum.DECLARATIVE)
|
120
|
-
if self.schemaview is None:
|
121
|
-
self.schemaview = SchemaView(self.schema)
|
122
126
|
return self.module
|
123
127
|
|
124
128
|
def compile_native(self) -> ModuleType:
|
@@ -223,7 +227,6 @@ class SQLStore:
|
|
223
227
|
for n, nu_typ in inspect.getmembers(self.module):
|
224
228
|
# TODO: make more efficient
|
225
229
|
if n == typ.__name__:
|
226
|
-
# print(f'Creating {nu_typ} from: {inst_args}')
|
227
230
|
nu_obj = nu_typ(**inst_args)
|
228
231
|
return nu_obj
|
229
232
|
raise ValueError(f"Cannot find {typ.__name__} in {self.module}")
|
@@ -237,8 +240,6 @@ class SQLStore:
|
|
237
240
|
:param obj: sqla object
|
238
241
|
:return: native dataclass object
|
239
242
|
"""
|
240
|
-
if self.schemaview is None:
|
241
|
-
self.schemaview = SchemaView(self.schema)
|
242
243
|
typ = type(obj)
|
243
244
|
nm = self.schemaview.class_name_mappings()
|
244
245
|
if typ.__name__ in nm:
|
@@ -262,9 +263,7 @@ class SQLStore:
|
|
262
263
|
for n, nu_typ in inspect.getmembers(self.native_module):
|
263
264
|
# TODO: make more efficient
|
264
265
|
if n == typ.__name__:
|
265
|
-
# print(f'CREATING {nu_typ} FROM {inst_args}')
|
266
266
|
nu_obj = nu_typ(**inst_args)
|
267
|
-
# print(f'CREATED {nu_obj}')
|
268
267
|
return nu_obj
|
269
268
|
raise ValueError(f"Cannot find {typ.__name__} in {self.native_module}")
|
270
269
|
else:
|
linkml/validator/cli.py
CHANGED
@@ -108,6 +108,13 @@ DEPRECATED = "[DEPRECATED: only used in legacy mode]"
|
|
108
108
|
help=f"{DEPRECATED} When handling range constraints, include all descendants of the range "
|
109
109
|
"class instead of just the range class",
|
110
110
|
)
|
111
|
+
@click.option(
|
112
|
+
"--include-context/--no-include-context",
|
113
|
+
"-D",
|
114
|
+
default=False,
|
115
|
+
show_default=True,
|
116
|
+
help="Include additional context when reporting of validation errors.",
|
117
|
+
)
|
111
118
|
@click.argument("data_sources", nargs=-1, type=click.Path(exists=True))
|
112
119
|
@click.version_option(__version__, "-V", "--version")
|
113
120
|
@click.pass_context
|
@@ -123,6 +130,7 @@ def cli(
|
|
123
130
|
input_format: Optional[str],
|
124
131
|
index_slot: Optional[str],
|
125
132
|
include_range_class_descendants: bool,
|
133
|
+
include_context: bool,
|
126
134
|
):
|
127
135
|
if legacy_mode:
|
128
136
|
from linkml.validators import jsonschemavalidator
|
@@ -183,6 +191,9 @@ def cli(
|
|
183
191
|
for result in validator.iter_results_from_source(loader, config.target_class):
|
184
192
|
severity_counter[result.severity] += 1
|
185
193
|
click.echo(f"[{result.severity.value}] [{loader.source}/{result.instance_index}] {result.message}")
|
194
|
+
if include_context:
|
195
|
+
for ctx in result.context:
|
196
|
+
click.echo(f"[CONTEXT] {ctx}")
|
186
197
|
|
187
198
|
if sum(severity_counter.values()) == 0:
|
188
199
|
click.echo("No issues found")
|
@@ -46,6 +46,7 @@ class JsonschemaValidationPlugin(ValidationPlugin):
|
|
46
46
|
path_override=self.json_schema_path,
|
47
47
|
)
|
48
48
|
for error in validator.iter_errors(instance):
|
49
|
+
error_context = [ctx.message for ctx in error.context]
|
49
50
|
best_error = best_match([error])
|
50
51
|
yield ValidationResult(
|
51
52
|
type="jsonschema validation",
|
@@ -53,4 +54,5 @@ class JsonschemaValidationPlugin(ValidationPlugin):
|
|
53
54
|
instance=instance,
|
54
55
|
instantiates=context.target_class,
|
55
56
|
message=f"{best_error.message} in /{'/'.join(str(p) for p in best_error.absolute_path)}",
|
57
|
+
context=error_context,
|
56
58
|
)
|
@@ -34,21 +34,27 @@ class ShaclValidationPlugin(ValidationPlugin):
|
|
34
34
|
self.closed = closed
|
35
35
|
self.shacl_path = shacl_path
|
36
36
|
self.raise_on_conversion_error = raise_on_conversion_error
|
37
|
+
self._loaded_graphs = {}
|
37
38
|
|
38
39
|
def _shacl_graph(self, context: ValidationContext) -> Optional[rdflib.Graph]:
|
39
40
|
g = rdflib.Graph()
|
40
41
|
if self.shacl_path:
|
41
42
|
g.parse(str(self.shacl_path))
|
42
43
|
else:
|
43
|
-
|
44
|
-
|
44
|
+
schema_hash = hash(str(context._schema))
|
45
|
+
if schema_hash in self._loaded_graphs:
|
46
|
+
g = self._loaded_graphs[schema_hash]
|
47
|
+
else:
|
48
|
+
gen = ShaclGenerator(context._schema)
|
49
|
+
g = gen.as_graph()
|
50
|
+
self._loaded_graphs[schema_hash] = g
|
45
51
|
return g
|
46
52
|
|
47
53
|
def process(self, instance: Any, context: ValidationContext) -> Iterator[ValidationResult]:
|
48
|
-
"""Perform
|
54
|
+
"""Perform SHACL Schema validation on the provided instance
|
49
55
|
|
50
56
|
:param instance: The instance to validate
|
51
|
-
:param context: The validation context which provides a
|
57
|
+
:param context: The validation context which provides a SHACL artifact
|
52
58
|
:return: Iterator over validation results
|
53
59
|
:rtype: Iterator[ValidationResult]
|
54
60
|
"""
|
linkml/validator/report.py
CHANGED
@@ -20,6 +20,7 @@ from linkml_runtime.dumpers import json_dumper, rdflib_dumper, yaml_dumper
|
|
20
20
|
from linkml_runtime.linkml_model import ElementName
|
21
21
|
from linkml_runtime.utils.formatutils import camelcase
|
22
22
|
|
23
|
+
from linkml._version import __version__
|
23
24
|
from linkml.generators.pythongen import PythonGenerator
|
24
25
|
from linkml.validator import Validator, _get_default_validator
|
25
26
|
|
@@ -298,6 +299,7 @@ class ExampleRunner:
|
|
298
299
|
show_default=True,
|
299
300
|
help="If true use type_designators to deepen ranges",
|
300
301
|
)
|
302
|
+
@click.version_option(__version__, "-V", "--version")
|
301
303
|
def cli(schema, prefixes, output: TextIO, **kwargs):
|
302
304
|
"""Process a folder of examples and a folder of counter examples.
|
303
305
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: linkml
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.9
|
4
4
|
Summary: Linked Open Data Modeling Language
|
5
5
|
Home-page: https://linkml.io/linkml/
|
6
6
|
Keywords: schema,linked data,data modeling,rdf,owl,biolink
|
@@ -51,6 +51,7 @@ Requires-Dist: pyyaml
|
|
51
51
|
Requires-Dist: rdflib (>=6.0.0)
|
52
52
|
Requires-Dist: requests (>=2.22)
|
53
53
|
Requires-Dist: sqlalchemy (>=1.4.31)
|
54
|
+
Requires-Dist: typing-extensions (>=4.4.0) ; python_version < "3.9"
|
54
55
|
Requires-Dist: watchdog (>=0.9.0)
|
55
56
|
Project-URL: Documentation, https://linkml.io/linkml/
|
56
57
|
Project-URL: Repository, https://github.com/linkml/linkml
|