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.
Files changed (43) hide show
  1. linkml/__init__.py +14 -0
  2. linkml/generators/csvgen.py +15 -5
  3. linkml/generators/docgen/class_diagram.md.jinja2 +19 -4
  4. linkml/generators/docgen/subset.md.jinja2 +7 -7
  5. linkml/generators/docgen.py +59 -14
  6. linkml/generators/graphqlgen.py +14 -16
  7. linkml/generators/jsonldcontextgen.py +3 -3
  8. linkml/generators/jsonldgen.py +4 -3
  9. linkml/generators/jsonschemagen.py +9 -0
  10. linkml/generators/markdowngen.py +341 -301
  11. linkml/generators/owlgen.py +87 -20
  12. linkml/generators/plantumlgen.py +9 -8
  13. linkml/generators/prefixmapgen.py +15 -23
  14. linkml/generators/protogen.py +23 -18
  15. linkml/generators/pydanticgen/array.py +15 -3
  16. linkml/generators/pydanticgen/pydanticgen.py +13 -2
  17. linkml/generators/pydanticgen/template.py +6 -4
  18. linkml/generators/pythongen.py +5 -5
  19. linkml/generators/rdfgen.py +14 -5
  20. linkml/generators/shaclgen.py +18 -6
  21. linkml/generators/shexgen.py +9 -7
  22. linkml/generators/sqlalchemygen.py +1 -0
  23. linkml/generators/sqltablegen.py +16 -1
  24. linkml/generators/summarygen.py +8 -2
  25. linkml/generators/terminusdbgen.py +2 -2
  26. linkml/generators/yumlgen.py +2 -2
  27. linkml/transformers/relmodel_transformer.py +21 -1
  28. linkml/utils/__init__.py +3 -0
  29. linkml/utils/deprecation.py +255 -0
  30. linkml/utils/generator.py +87 -61
  31. linkml/utils/rawloader.py +5 -1
  32. linkml/utils/schemaloader.py +2 -1
  33. linkml/utils/sqlutils.py +13 -14
  34. linkml/validator/cli.py +11 -0
  35. linkml/validator/plugins/jsonschema_validation_plugin.py +2 -0
  36. linkml/validator/plugins/shacl_validation_plugin.py +10 -4
  37. linkml/validator/report.py +1 -0
  38. linkml/workspaces/example_runner.py +2 -0
  39. {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/METADATA +2 -1
  40. {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/RECORD +43 -42
  41. {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/LICENSE +0 -0
  42. {linkml-1.7.7.dist-info → linkml-1.7.9.dist-info}/WHEEL +0 -0
  43. {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
- if any(c for c in schema.classes.values() if not c.definition_uri):
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
- output = StringIO()
267
- # Note: we currently redirect stdout, this means that print statements within
268
- # each generator will be redirected to the StringIO object.
269
- # See https://github.com/linkml/linkml/issues/923 for discussion on simplifying this
270
- with redirect_stdout(output):
271
- # the default is to use the Visitor Pattern; each individual generator may
272
- # choose to override methods {visit,end}_{element}.
273
- # See https://github.com/linkml/linkml/issues/923
274
- self.visit_schema(**kwargs)
275
- for sn, ss in (
276
- sorted(self.schema.subsets.items(), key=lambda s: s[0].lower())
277
- if self.visits_are_sorted
278
- else self.schema.subsets.items()
279
- ):
280
- self.visit_subset(ss)
281
- for tn, typ in (
282
- sorted(self.schema.types.items(), key=lambda s: s[0].lower())
283
- if self.visits_are_sorted
284
- else self.schema.types.items()
285
- ):
286
- self.visit_type(typ)
287
- for enum in (
288
- sorted(self.schema.enums.values(), key=lambda e: e.name.lower())
289
- if self.visits_are_sorted
290
- else self.schema.enums.values()
291
- ):
292
- self.visit_enum(enum)
293
- for sn, slot in (
294
- sorted(self.schema.slots.items(), key=lambda c: c[0].lower())
295
- if self.visits_are_sorted
296
- else self.schema.slots.items()
297
- ):
298
- self.visit_slot(self.aliased_slot_name(slot), slot)
299
- for cls in (
300
- sorted(self.schema.classes.values(), key=lambda c: c.name.lower())
301
- if self.visits_are_sorted
302
- else self.schema.classes.values()
303
- ):
304
- if self.visit_class(cls):
305
- for slot in self.all_slots(cls) if self.visit_all_class_slots else self.own_slots(cls):
306
- self.visit_class_slot(cls, self.aliased_slot_name(slot), slot)
307
- self.end_class(cls)
308
- self.end_schema(**kwargs)
309
- return output.getvalue()
310
-
311
- def visit_schema(self, **kwargs) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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
@@ -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 = None
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
- gen = ShaclGenerator(context._schema)
44
- g = gen.as_graph()
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 JSON Schema validation on the provided instance
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 JSON Schema artifact
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
  """
@@ -28,6 +28,7 @@ class ValidationResult(BaseModel):
28
28
  instance: Optional[Any] = None
29
29
  instance_index: Optional[int] = None
30
30
  instantiates: Optional[str] = None
31
+ context: List[str] = []
31
32
 
32
33
 
33
34
  class ValidationReport(BaseModel):
@@ -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.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