linkml 1.7.7__py3-none-any.whl → 1.7.8__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 CHANGED
@@ -19,6 +19,13 @@ LOCAL_TYPES_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.TYPES, Format.YAML)
19
19
  LOCAL_MAPPINGS_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.MAPPINGS, Format.YAML)
20
20
  LOCAL_ANNOTATIONS_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.ANNOTATIONS, Format.YAML)
21
21
  LOCAL_EXTENSIONS_YAML_FILE = linkml_files.LOCAL_PATH_FOR(Source.EXTENSIONS, Format.YAML)
22
+ LOCAL_MODEL_YAML_FILES = (
23
+ LOCAL_METAMODEL_YAML_FILE,
24
+ LOCAL_TYPES_YAML_FILE,
25
+ LOCAL_MAPPINGS_YAML_FILE,
26
+ LOCAL_ANNOTATIONS_YAML_FILE,
27
+ LOCAL_EXTENSIONS_YAML_FILE,
28
+ )
22
29
 
23
30
  # Local location of jsonld and context.jsonld files
24
31
  LOCAL_METAMODEL_LDCONTEXT_FILE = linkml_files.LOCAL_PATH_FOR(Source.META, Format.JSONLD)
@@ -55,6 +62,13 @@ METATYPE_NAMESPACE = METAMODEL_NAMESPACE
55
62
  METAMAPPING_NAMESPACE = METAMODEL_NAMESPACE
56
63
  METAANNOTATIONS_NAMESPACE = METAMODEL_NAMESPACE
57
64
  METAEXTENSIONS_NAMESPACE = METAMODEL_NAMESPACE
65
+ NAMESPACES = (
66
+ METAMODEL_NAMESPACE,
67
+ METATYPE_NAMESPACE,
68
+ METAMAPPING_NAMESPACE,
69
+ METAANNOTATIONS_NAMESPACE,
70
+ METAEXTENSIONS_NAMESPACE,
71
+ )
58
72
 
59
73
  # Metamodel Context URI
60
74
  METAMODEL_CONTEXT_URI = linkml_files.URL_FOR(Source.META, Format.JSONLD)
@@ -43,20 +43,18 @@ URI: {{ gen.link(element) }}
43
43
 
44
44
  | Class | Description |
45
45
  | --- | --- |
46
- {% for c in gen.all_class_objects()|sort(attribute=sort_by) -%}
46
+ {% for c in classes_in_subset -%}
47
47
  {%- if element.name in c.in_subset -%}
48
48
  | {{gen.link(c)}} | {{c.description|enshorten}} |
49
49
  {% endif -%}
50
50
  {% endfor %}
51
51
 
52
- {% for c in gen.all_class_objects()|sort(attribute=sort_by) -%}
52
+ {% for c in classes_in_subset -%}
53
53
  {%- if element.name in c.in_subset -%}
54
- ### {{ gen.name(c) }}
55
54
 
56
- {{c.description}}
55
+ {% set induced_slots = gen.class_induced_slots(c.name)|sort(attribute=sort_by) %}
57
56
 
58
57
  {%- set filtered_slots = [] -%}
59
-
60
58
  {%- for s in induced_slots|sort(attribute=sort_by) -%}
61
59
  {%- if element.name in s.in_subset or element.name in schemaview.get_slot(s.name).in_subset -%}
62
60
  {% set _ = filtered_slots.append(s) %}
@@ -64,10 +62,12 @@ URI: {{ gen.link(element) }}
64
62
  {%- endfor %}
65
63
 
66
64
  {%- if filtered_slots|length > 0 -%}
67
- | Name | Cardinality and Range | Description |
65
+ ### Slots from {{ gen.link(c) }} also in _{{ element.name }}_
66
+
67
+ | Name | Cardinality and Range | Description |
68
68
  | --- | --- | --- |
69
69
  {% for s in filtered_slots -%}
70
- | {{gen.link(s)}} | {{ gen.cardinality(s) }} <br/> {{gen.link(s.range)}} | {{s.description|enshorten}} {% if s.identifier %}**identifier**{% endif %} |
70
+ | {{gen.link(s)}} | {{ gen.cardinality(s) }} <br/> {{gen.link(s.range)}} | {{s.description|enshorten}} {% if s.identifier %}**identifier**{% endif %} |
71
71
  {% endfor %}
72
72
  {%- endif %}
73
73
 
@@ -60,8 +60,8 @@ class JSONLDGenerator(Generator):
60
60
  """Path to a JSONLD context file"""
61
61
 
62
62
  def __post_init__(self) -> None:
63
- super().__post_init__()
64
63
  self.original_schema = deepcopy(self.schema)
64
+ super().__post_init__()
65
65
 
66
66
  def _add_type(self, node: YAMLRoot) -> dict:
67
67
  if self.format == "jsonld":
@@ -30,7 +30,7 @@ else:
30
30
  from typing import Annotated
31
31
 
32
32
  from linkml.generators.pydanticgen.build import SlotResult
33
- from linkml.generators.pydanticgen.template import Import, Imports, ObjectImport
33
+ from linkml.generators.pydanticgen.template import ConditionalImport, Import, Imports, ObjectImport
34
34
 
35
35
 
36
36
  class ArrayRepresentation(Enum):
@@ -54,7 +54,14 @@ if int(PYDANTIC_VERSION[0]) >= 2:
54
54
  item_schema = handler.generate_schema(item_type)
55
55
  if item_schema.get("type", "any") != "any":
56
56
  item_schema["strict"] = True
57
- array_ref = f"any-shape-array-{item_type.__name__}"
57
+
58
+ if item_type is Any:
59
+ # Before python 3.11, `Any` type was a special object without a __name__
60
+ item_name = "Any"
61
+ else:
62
+ item_name = item_type.__name__
63
+
64
+ array_ref = f"any-shape-array-{item_name}"
58
65
 
59
66
  schema = core_schema.definitions_schema(
60
67
  core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
@@ -78,7 +85,6 @@ if int(PYDANTIC_VERSION[0]) >= 2:
78
85
  + Import(
79
86
  module="typing",
80
87
  objects=[
81
- ObjectImport(name="Annotated"),
82
88
  ObjectImport(name="Generic"),
83
89
  ObjectImport(name="Iterable"),
84
90
  ObjectImport(name="TypeVar"),
@@ -86,6 +92,12 @@ if int(PYDANTIC_VERSION[0]) >= 2:
86
92
  ObjectImport(name="get_args"),
87
93
  ],
88
94
  )
95
+ + ConditionalImport(
96
+ condition="sys.version_info.minor > 8",
97
+ module="typing",
98
+ objects=[ObjectImport(name="Annotated")],
99
+ alternative=Import(module="typing_extensions", objects=[ObjectImport(name="Annotated")]),
100
+ )
89
101
  + Import(module="pydantic", objects=[ObjectImport(name="GetCoreSchemaHandler")])
90
102
  + Import(module="pydantic_core", objects=[ObjectImport(name="CoreSchema"), ObjectImport(name="core_schema")])
91
103
  )
@@ -76,6 +76,7 @@ DEFAULT_IMPORTS = (
76
76
  + Import(module="decimal", objects=[ObjectImport(name="Decimal")])
77
77
  + Import(module="enum", objects=[ObjectImport(name="Enum")])
78
78
  + Import(module="re")
79
+ + Import(module="sys")
79
80
  + Import(
80
81
  module="typing",
81
82
  objects=[
@@ -644,10 +645,11 @@ class PydanticGenerator(OOCodeGenerator):
644
645
  for k, c in pyschema.classes.items():
645
646
  attrs = {}
646
647
  for attr_name, src_attr in c.attributes.items():
648
+ src_attr = src_attr._as_dict
647
649
  new_fields = {
648
- k: src_attr._as_dict.get(k, None)
650
+ k: src_attr.get(k, None)
649
651
  for k in PydanticAttribute.model_fields.keys()
650
- if src_attr._as_dict.get(k, None) is not None
652
+ if src_attr.get(k, None) is not None
651
653
  }
652
654
  predef_slot = predefined.get(k, {}).get(attr_name, None)
653
655
  if predef_slot is not None:
@@ -1,3 +1,4 @@
1
+ from copy import copy
1
2
  from importlib.util import find_spec
2
3
  from typing import Any, ClassVar, Dict, Generator, List, Literal, Optional, Union, overload
3
4
 
@@ -54,6 +55,10 @@ class TemplateModel(BaseModel):
54
55
  """
55
56
 
56
57
  template: ClassVar[str]
58
+ _environment: ClassVar[Environment] = Environment(
59
+ loader=PackageLoader("linkml.generators.pydanticgen", "templates"), trim_blocks=True, lstrip_blocks=True
60
+ )
61
+
57
62
  pydantic_ver: int = int(PYDANTIC_VERSION[0])
58
63
 
59
64
  def render(self, environment: Optional[Environment] = None, black: bool = False) -> str:
@@ -95,14 +100,11 @@ class TemplateModel(BaseModel):
95
100
  def environment(cls) -> Environment:
96
101
  """
97
102
  Default environment for Template models.
98
-
99
103
  uses a :class:`jinja2.PackageLoader` for the templates directory within this module
100
104
  with the ``trim_blocks`` and ``lstrip_blocks`` parameters set to ``True`` so that the
101
105
  default templates could be written in a more readable way.
102
106
  """
103
- return Environment(
104
- loader=PackageLoader("linkml.generators.pydanticgen", "templates"), trim_blocks=True, lstrip_blocks=True
105
- )
107
+ return copy(cls._environment)
106
108
 
107
109
  if int(PYDANTIC_VERSION[0]) < 2:
108
110
  # simulate pydantic 2's model_fields behavior
@@ -4,6 +4,7 @@ import os
4
4
  import re
5
5
  from copy import copy
6
6
  from dataclasses import dataclass
7
+ from pathlib import Path
7
8
  from types import ModuleType
8
9
  from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple, Union
9
10
 
@@ -62,6 +63,8 @@ class PythonGenerator(Generator):
62
63
  emit_metadata: bool = True
63
64
 
64
65
  def __post_init__(self) -> None:
66
+ if isinstance(self.schema, Path):
67
+ self.schema = str(self.schema)
65
68
  self.sourcefile = self.schema
66
69
  self.schemaview = SchemaView(self.schema, base_dir=self.base_dir)
67
70
  super().__post_init__()
@@ -7,10 +7,12 @@ Generate a JSON LD representation of the model
7
7
 
8
8
  import os
9
9
  import urllib.parse as urlparse
10
+ from copy import deepcopy
10
11
  from dataclasses import dataclass
11
12
  from typing import List, Optional
12
13
 
13
14
  import click
15
+ from linkml_runtime.linkml_model import SchemaDefinition
14
16
  from rdflib import Graph
15
17
  from rdflib.plugin import Parser as rdflib_Parser
16
18
  from rdflib.plugin import plugins as rdflib_plugins
@@ -35,13 +37,19 @@ class RDFGenerator(Generator):
35
37
  # ObjectVars
36
38
  emit_metadata: bool = False
37
39
  context: List[str] = None
40
+ original_schema: SchemaDefinition = None
41
+ """See https://github.com/linkml/linkml/issues/871"""
42
+
43
+ def __post_init__(self):
44
+ self.original_schema = deepcopy(self.schema)
45
+ super().__post_init__()
38
46
 
39
47
  def _data(self, g: Graph) -> str:
40
48
  return g.serialize(format="turtle" if self.format == "ttl" else self.format)
41
49
 
42
50
  def end_schema(self, output: Optional[str] = None, context: str = None, **_) -> None:
43
51
  gen = JSONLDGenerator(
44
- self,
52
+ self.original_schema,
45
53
  format=JSONLDGenerator.valid_formats[0],
46
54
  metadata=self.emit_metadata,
47
55
  importmap=self.importmap,
@@ -22,7 +22,8 @@ class ShaclGenerator(Generator):
22
22
  # ClassVars
23
23
  closed: bool = True
24
24
  """True means add 'sh:closed=true' to all shapes, except of mixin shapes and shapes, that have parents"""
25
-
25
+ suffix: str = None
26
+ """parameterized suffix to be appended. No suffix per default."""
26
27
  generatorname = os.path.basename(__file__)
27
28
  generatorversion = "0.0.1"
28
29
  valid_formats = ["ttl"]
@@ -59,9 +60,12 @@ class ShaclGenerator(Generator):
59
60
 
60
61
  def shape_pv(p, v):
61
62
  if v is not None:
62
- g.add((class_uri, p, v))
63
+ g.add((class_uri_with_suffix, p, v))
63
64
 
64
65
  class_uri = URIRef(sv.get_uri(c, expand=True))
66
+ class_uri_with_suffix = class_uri
67
+ if self.suffix is not None:
68
+ class_uri_with_suffix += self.suffix
65
69
  shape_pv(RDF.type, SH.NodeShape)
66
70
  shape_pv(SH.targetClass, class_uri) # TODO
67
71
  if self.closed:
@@ -211,14 +215,21 @@ def add_simple_data_type(func: Callable, r: ElementName) -> None:
211
215
 
212
216
 
213
217
  @shared_arguments(ShaclGenerator)
218
+ @click.command()
214
219
  @click.option(
215
220
  "--closed/--non-closed",
216
221
  default=True,
217
222
  show_default=True,
218
223
  help="Use '--closed' to generate closed SHACL shapes. Use '--non-closed' to generate open SHACL shapes.",
219
224
  )
225
+ @click.option(
226
+ "-s",
227
+ "--suffix",
228
+ default=None,
229
+ show_default=True,
230
+ help="Use --suffix to append given string to SHACL class name (e. g. --suffix Shape: Person becomes PersonShape).",
231
+ )
220
232
  @click.version_option(__version__, "-V", "--version")
221
- @click.command()
222
233
  def cli(yamlfile, **args):
223
234
  """Generate SHACL turtle from a LinkML model"""
224
235
  gen = ShaclGenerator(yamlfile, **args)
@@ -121,6 +121,7 @@ class SQLAlchemyGenerator(Generator):
121
121
  is_join_table=lambda c: any(tag for tag in c.annotations.keys() if tag == "linkml:derived_from"),
122
122
  classes=rel_schema_classes_ordered,
123
123
  )
124
+ logging.debug(f"# Generated code:\n{code}")
124
125
  return code
125
126
 
126
127
  def serialize(self, **kwargs) -> str:
@@ -211,7 +211,22 @@ class SQLTableGenerator(Generator):
211
211
  col.comment = s.description
212
212
  cols.append(col)
213
213
  for uc_name, uc in c.unique_keys.items():
214
- sql_uc = UniqueConstraint(*[sql_name(sn) for sn in uc.unique_key_slots])
214
+
215
+ def _sql_name(sn: str):
216
+ if sn in c.attributes:
217
+ return sql_name(sn)
218
+ else:
219
+ # for candidate in c.attributes.values():
220
+ # if "original_slot" in candidate.annotations:
221
+ # original = candidate.annotations["original_slot"]
222
+ # if original.value == sn:
223
+ # return sql_name(candidate.name)
224
+ return None
225
+
226
+ sql_names = [_sql_name(sn) for sn in uc.unique_key_slots]
227
+ if any(sn is None for sn in sql_names):
228
+ continue
229
+ sql_uc = UniqueConstraint(*sql_names)
215
230
  cols.append(sql_uc)
216
231
  Table(sql_name(cn), schema_metadata, *cols, comment=str(c.description))
217
232
  schema_metadata.create_all(engine)
@@ -226,6 +226,7 @@ class RelationalModelTransformer:
226
226
 
227
227
  # TODO: separate out the logic into separate testable methods
228
228
  target_sv.set_modified()
229
+ multivalued_slots_original = []
229
230
  # post-process target schema
230
231
  for cn, c in target_sv.all_classes().items():
231
232
  if self.foreign_key_policy == ForeignKeyPolicy.NO_FOREIGN_KEYS:
@@ -243,6 +244,7 @@ class RelationalModelTransformer:
243
244
  slot.inlined or slot.inlined_as_list or "shared" in slot.annotations
244
245
  )
245
246
  if slot.multivalued:
247
+ multivalued_slots_original.append(slot.name)
246
248
  slot.multivalued = False
247
249
  slot_name = slot.name
248
250
  sn_singular = slot.singular_name if slot.singular_name else slot.name
@@ -332,6 +334,7 @@ class RelationalModelTransformer:
332
334
  # add PK and FK anns
333
335
  target_sv.set_modified()
334
336
  fk_policy = self.foreign_key_policy
337
+ forward_map = {}
335
338
  for c in target.classes.values():
336
339
  if self.foreign_key_policy == ForeignKeyPolicy.NO_FOREIGN_KEYS:
337
340
  continue
@@ -356,14 +359,31 @@ class RelationalModelTransformer:
356
359
  # if it is already an injected backref, no need to re-inject
357
360
  if "backref" not in a.annotations:
358
361
  del c.attributes[a.name]
362
+ original_name = a.name
359
363
  if "forwardref" not in a.annotations:
360
- add_annotation(a, "original_slot", a.name)
364
+ add_annotation(a, "original_slot", original_name)
361
365
  a.alias = f"{a.name}_{tc_pk_slot.name}"
362
366
  a.name = a.alias
363
367
  c.attributes[a.name] = a
368
+ forward_map[original_name] = a.name
364
369
  ann = Annotation("foreign_key", f"{tc.name}.{tc_pk_slot.name}")
365
370
  a.annotations[ann.tag] = ann
366
371
  target_sv.set_modified()
372
+ # Rewrite unique key constraints
373
+ # - if a slot has a range of object, it may be renamed, e.g. person => person_id
374
+ # - if a slot is multivalued then it is translated to backref and the UC must be dropped
375
+ removed_ucs = []
376
+ for uc_name, uc in c.unique_keys.items():
377
+ if any(sn in multivalued_slots_original for sn in uc.unique_key_slots):
378
+ logging.warning(
379
+ f"Cannot represent uniqueness constraint {uc_name}. "
380
+ f"one of the slots {uc.unique_key_slots} is multivalued"
381
+ )
382
+ removed_ucs.append(uc_name)
383
+ new_slot_names = [forward_map.get(sn, sn) for sn in uc.unique_key_slots]
384
+ uc.unique_key_slots = new_slot_names
385
+ for uc_name in removed_ucs:
386
+ del c.unique_keys[uc_name]
367
387
 
368
388
  result = TransformationResult(target, mappings=mappings)
369
389
  return result
linkml/utils/generator.py CHANGED
@@ -30,7 +30,6 @@ from typing import Callable, ClassVar, Dict, List, Mapping, Optional, Set, TextI
30
30
  import click
31
31
  from click import Argument, Command, Option
32
32
  from linkml_runtime import SchemaView
33
- from linkml_runtime.dumpers import yaml_dumper
34
33
  from linkml_runtime.linkml_model.meta import (
35
34
  ClassDefinition,
36
35
  ClassDefinitionName,
@@ -83,7 +82,7 @@ class Generator(metaclass=abc.ABCMeta):
83
82
  For usage `Generator Docs <https://linkml.io/linkml/generators/>`_
84
83
  """
85
84
 
86
- schema: Union[str, TextIO, SchemaDefinition, "Generator"]
85
+ schema: Union[str, TextIO, SchemaDefinition, "Generator", Path]
87
86
  """metamodel compliant schema. Can be URI, file name, actual schema, another generator, an
88
87
  open file or a pre-parsed schema"""
89
88
 
@@ -130,7 +129,7 @@ class Generator(metaclass=abc.ABCMeta):
130
129
  useuris: Optional[bool] = None
131
130
  """True means declared class slot uri's are used. False means use model uris"""
132
131
 
133
- log_level: int = DEFAULT_LOG_LEVEL_INT
132
+ log_level: Optional[int] = DEFAULT_LOG_LEVEL_INT
134
133
  """Logging level, 0 is minimum"""
135
134
 
136
135
  mergeimports: Optional[bool] = True
@@ -180,6 +179,8 @@ class Generator(metaclass=abc.ABCMeta):
180
179
  def __post_init__(self) -> None:
181
180
  if not self.logger:
182
181
  self.logger = logging.getLogger()
182
+ if self.log_level is not None:
183
+ self.logger.setLevel(self.log_level)
183
184
  if self.format is None:
184
185
  self.format = self.valid_formats[0]
185
186
  if self.format not in self.valid_formats:
@@ -191,7 +192,11 @@ class Generator(metaclass=abc.ABCMeta):
191
192
  self.source_file_size = None
192
193
  if self.requires_metamodel:
193
194
  self.metamodel = _resolved_metamodel(self.mergeimports)
195
+
194
196
  schema = self.schema
197
+ if isinstance(schema, Path):
198
+ schema = str(schema)
199
+
195
200
  # TODO: remove aliasing
196
201
  self.emit_metadata = self.metadata
197
202
  if self.uses_schemaloader:
@@ -225,8 +230,7 @@ class Generator(metaclass=abc.ABCMeta):
225
230
  # schemaloader based methods require schemas to have been created via SchemaLoader,
226
231
  # which prepopulates some fields (e.g. definition_url). If the schema has not been processed through the
227
232
  # loader, then roundtrip
228
- if any(c for c in schema.classes.values() if not c.definition_uri):
229
- schema = yaml_dumper.dumps(schema)
233
+ schema = schema._as_dict
230
234
  loader = SchemaLoader(
231
235
  schema,
232
236
  self.base_dir,
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:
@@ -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
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.7.7
3
+ Version: 1.7.8
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
@@ -1,4 +1,4 @@
1
- linkml/__init__.py,sha256=jwtUdhwmquj-0fCR6VwPH5_RfSpuG94hLWdre_W_FD4,3327
1
+ linkml/__init__.py,sha256=0REu65rXiOk3F1CYLzq60HiSZ2DfFySfGS6QFljZ01s,3663
2
2
  linkml/_version.py,sha256=udxv6OEqcE89DTVMYPtetXYg8IA8Sgc6yW26-6f8C3M,310
3
3
  linkml/generators/PythonGenNotes.md,sha256=wb9BPLLhF6378OKLbwSBAwmniLpwrcT5_bbfCHfLme8,51006
4
4
  linkml/generators/README.md,sha256=RMzT8EblC_GEdPy5WyfXHDBXlFI6k6mz3Cx2sdpcyWI,4438
@@ -14,7 +14,7 @@ linkml/generators/docgen/index.md.jinja2,sha256=wXUYTmayPLFltC0vbGE_Mf6m3GkkWav7
14
14
  linkml/generators/docgen/index.tex.jinja2,sha256=Go_EA-_N4JUpbOYbk3OY11mz5yV70VF2l2sMtgIPWw4,501
15
15
  linkml/generators/docgen/schema.md.jinja2,sha256=xlENfnzNRYgPT_0tdqNFxgklVM4Qf5BuzhFVvSMDuxs,70
16
16
  linkml/generators/docgen/slot.md.jinja2,sha256=ZGa-kaNvi5LknvFRIjKQyRpSynr1SlkLrpEmWDNPwDA,3226
17
- linkml/generators/docgen/subset.md.jinja2,sha256=fTNIpAkml5RKFbbtLore3IAzFN1cISVsyL1ru2-Z4oA,2665
17
+ linkml/generators/docgen/subset.md.jinja2,sha256=EPln_fjlhlkmwEGEp2iPX-9Xm2vVodPZLlRBlYCB_sA,2705
18
18
  linkml/generators/docgen/type.md.jinja2,sha256=QmCMJZrFwP33eHkggBVtypbyrxTb-XZn9vHOYojVaYk,635
19
19
  linkml/generators/docgen.py,sha256=dz7OWxclX306EweVIgC765h3NKtJTRg0TPnqmltWygs,34347
20
20
  linkml/generators/dotgen.py,sha256=kzp1EYtF8bBqOuycJokDmsvHVDVkkW-J-6-MiUhwJH0,5013
@@ -27,7 +27,7 @@ linkml/generators/javagen/example_template.java.jinja2,sha256=ec4CVTv_0zS7V5Y-1E
27
27
  linkml/generators/javagen/java_record_template.jinja2,sha256=OQZffLSy_xR3FIhQMltvrYyVeut7l2Q-tzK7AOiVmWs,1729
28
28
  linkml/generators/javagen.py,sha256=KxwupMztyCRHcSsbtTnOovuj1WamsAny0mxbYWvTiDs,5324
29
29
  linkml/generators/jsonldcontextgen.py,sha256=N_XWCx_1eS5KcWlzlyl79C-dW0gfe32v886UskD1iTU,8644
30
- linkml/generators/jsonldgen.py,sha256=pk8Gmh2gSvXt_o7MTuJ71thNfIJuHUGkNl3yl3RIdsE,7728
30
+ linkml/generators/jsonldgen.py,sha256=646aQc27CkuxxkDAJHSBcivCDwNAPl7OOT01OBR2AOI,7728
31
31
  linkml/generators/jsonschemagen.py,sha256=XOsYIrpA6BwtAcfh8GNGduIjpuPAsF2f_PQ1mghj_WU,27455
32
32
  linkml/generators/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  linkml/generators/linkmlgen.py,sha256=1_Kt_7rD42WvCTjq0yaq1Of7jEDZR_uusyzAT-qWMHk,3471
@@ -40,11 +40,11 @@ linkml/generators/prefixmapgen.py,sha256=L9TccwKNHEguW0ox5qgf_GhIuqauYTI8d4jSjeq
40
40
  linkml/generators/projectgen.py,sha256=OuT_AneoZFNMCn50GllfZafadUHt50u61JcftM2eed4,9750
41
41
  linkml/generators/protogen.py,sha256=5UxThsFDVyDTzzTDh77Z0anx4-tLgz8kQQ-e7ywIqwI,2290
42
42
  linkml/generators/pydanticgen/__init__.py,sha256=uKGaaQSaeKqinHImXGFE448i-t8Oi0No8suIO76sep8,629
43
- linkml/generators/pydanticgen/array.py,sha256=imbHtwgQcz_uloFWDBDojIMydZw9eysQyQl6ppT5eL8,19045
43
+ linkml/generators/pydanticgen/array.py,sha256=1QWWgz46yVmWkdNauI6Kjkbx3kexp-l210nOILoHJ18,19506
44
44
  linkml/generators/pydanticgen/black.py,sha256=c-Hgaao9hd4nPMU9w0Hmg2j4Wc81IcY0zAfppQPr1cM,721
45
45
  linkml/generators/pydanticgen/build.py,sha256=Ia8qy4C16b-KqO_fw4tGQW_Eo4grCVyX7RsuJ3uRTtk,2681
46
- linkml/generators/pydanticgen/pydanticgen.py,sha256=psEdNy_oJqHnTBZkQp4H8pNrFXv_y92j5oEoFcp_AEU,29809
47
- linkml/generators/pydanticgen/template.py,sha256=7I-qch_vr1elXwJ1kxRcZEl3j6-WTLYRh8_b2DjxjyE,19911
46
+ linkml/generators/pydanticgen/pydanticgen.py,sha256=Gnii-UuKc7gyEpaN5XbRyzWp0v1F5eAUVVzMhFGX2rM,29863
47
+ linkml/generators/pydanticgen/template.py,sha256=fAqRofLm7N94xUqXjkStdmugcxGQfiioGkxdIhLByik,19990
48
48
  linkml/generators/pydanticgen/templates/attribute.py.jinja,sha256=AlH_QFJJkONpzXQRGqnW4ufmjp9s9E7Q9W5r8ykNGeQ,443
49
49
  linkml/generators/pydanticgen/templates/base_model.py.jinja,sha256=48y64MnC9rjNSV-nKLMeDuHN4gm15UsInhnKxh65zoM,834
50
50
  linkml/generators/pydanticgen/templates/class.py.jinja,sha256=RIdkqdZS9rDILUuVqDIAWK_vATGkirLbPhdHSyHDAbY,565
@@ -54,19 +54,19 @@ linkml/generators/pydanticgen/templates/footer.py.jinja,sha256=WuWGJjkWDQcEPL2bG
54
54
  linkml/generators/pydanticgen/templates/imports.py.jinja,sha256=d1XFna2eOpkH8cgJML3vXwqGcocczvOcrbg6jjd4kP0,945
55
55
  linkml/generators/pydanticgen/templates/module.py.jinja,sha256=SkmtWtQS7D-xBiZI-sFiYC3WYPKi3VJCj_KtaMVxexM,438
56
56
  linkml/generators/pydanticgen/templates/validator.py.jinja,sha256=Yo4dubQal-HwEoJKztQkLYM5qCz1I8na2oaDlXuEpwU,532
57
- linkml/generators/pythongen.py,sha256=AvOQZJ-OT8lMYeaT2bdexHuRPRfTK7wpbmgAdfc2lW4,52805
58
- linkml/generators/rdfgen.py,sha256=L6F08iDUqVemXXrRbJmcOxvJTt14hR0oo8WLoqf4npw,2656
57
+ linkml/generators/pythongen.py,sha256=4Tw4Zz5GPvvbvk10zrD1yJFdxY3cvXJYqmFKrms8aUs,52915
58
+ linkml/generators/rdfgen.py,sha256=fPhTrMzUM6dkg3HllgD6biuRJl8afIfj7ZWu9e56MKo,2973
59
59
  linkml/generators/shacl/__init__.py,sha256=O-M-wndKw8rMW-U8X3QCNHal-ErXP6uXZqxiQSa77l4,108
60
60
  linkml/generators/shacl/ifabsent_processor.py,sha256=kV9BGA2ZPXLRfaFuW0o4jpkATvGggvrqpAo9c1UqWNE,2193
61
61
  linkml/generators/shacl/shacl_data_type.py,sha256=BT3C9tdFyBQnuucPN7YQiFAKEa9yuzy-Q26X6dmOXgo,1827
62
- linkml/generators/shaclgen.py,sha256=vCNAX15wg0h86ZplRjo721_58UHUSVHkQULTYV_1HZI,8324
62
+ linkml/generators/shaclgen.py,sha256=P3Kf10a0_1MCk4-prai9rrgPRHnQqtgYoXof0VIVyws,8771
63
63
  linkml/generators/shexgen.py,sha256=KzhaL-A4R4peSdhY6nlDWmS-DPfnZMxMzrXhHGnA_Ag,9874
64
64
  linkml/generators/sparqlgen.py,sha256=c7x8GFChKWprBx4rToSnu9qN8OleWTCenVUdZ8XSTWM,6138
65
65
  linkml/generators/sqlalchemy/__init__.py,sha256=mb9AC1rIFkSiNZhhG0TAk45ol9PjS1XvsrvCjgfVUpQ,249
66
66
  linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py,sha256=X_Ws1NUBikMI5HuNgEhl_PIeWM-B-c2B0W9KUBH4QTg,2542
67
67
  linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py,sha256=u4ZpponG1h6XROrOHGOf_0H2e6xL1_s8twAOA-gx94A,1622
68
- linkml/generators/sqlalchemygen.py,sha256=TTc1oLLwG0c1BdvDXvSfGWAJCDlVvq7Y0r0yjXcdiW4,9268
69
- linkml/generators/sqltablegen.py,sha256=XorqF6lfZX6tHfi6O2aIjd2GU_JZx5BuERuC3P6utVE,11626
68
+ linkml/generators/sqlalchemygen.py,sha256=5J9eQhTz7Ug-7WhZPv6zIaAbf7xlLi-0xMequ67pvgA,9320
69
+ linkml/generators/sqltablegen.py,sha256=87057FJi9EUVPXOjRfHuSqY0lKmepXRNQsose2fGts4,12343
70
70
  linkml/generators/sssomgen.py,sha256=yur3q7so9uwIELWZaZRzkJwNbz_ppBL7IQki9XLIM3k,6879
71
71
  linkml/generators/string_template.md,sha256=kRcfic6entgIaJdpSg6GF3jcjC9wbKsCVM6wVT2qipc,1788
72
72
  linkml/generators/summarygen.py,sha256=crH_Hegd4Z7G7byUtgyXoP2XtG_4iblzZsu_pE4aQrk,2887
@@ -95,7 +95,7 @@ linkml/reporting/model.py,sha256=-10yNfk8wuRC48ZI-akrWvtlJ9a6RYWET2TzlZV3XXo,862
95
95
  linkml/transformers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
96
  linkml/transformers/logical_model_transformer.py,sha256=Luc8cKWIGrRAXQ9DSLA03fgpIolLpsTT19OV9Coolvk,26522
97
97
  linkml/transformers/model_transformer.py,sha256=tK_MpRDI-q2qfe8KHT6qJHP8ZruKjYx1FcM-Fnjse-E,678
98
- linkml/transformers/relmodel_transformer.py,sha256=6tA-GkutJ0rgspjCF2NSCf2z-YdU2b4qPijabkroWfE,17704
98
+ linkml/transformers/relmodel_transformer.py,sha256=aiSI2xaqRz4f0Z_0WFaBP5bknzX0pTnvxsR5mESFysg,18857
99
99
  linkml/transformers/schema_renamer.py,sha256=DYRbgL9j41M9FdbHYLY3u4YIdDM98laD3cv_lHcgWHQ,5275
100
100
  linkml/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
101
  linkml/utils/cli_utils.py,sha256=MdVbox1qr6kDUHUCWAmFhV-D6ebs_nn8vVwB8KDQfew,742
@@ -103,17 +103,17 @@ linkml/utils/converter.py,sha256=snAF-pgZBSi4ANiaKXxqtZk5w3qonJjTfI4X5OVLmUE,628
103
103
  linkml/utils/datautils.py,sha256=QlbzwXykh5Fphfe5KxPo6_ekXfniLbHiEGJtLWjUrvY,3742
104
104
  linkml/utils/datavalidator.py,sha256=kBdWaVi8IZT1bOwEJgJYx-wZAb_PTBObB9nHpYORfKA,472
105
105
  linkml/utils/execute_tutorial.py,sha256=T4kHTSyz3ItJGEUZxVjR-3yLVKnOr5Ix4NMGE47-IuE,6912
106
- linkml/utils/generator.py,sha256=3q_PpZQxBTwpoN8lHHMkUhHm1gg0qXhiHgLWV9FIVLM,38363
106
+ linkml/utils/generator.py,sha256=ytpcq3s7cJMYpLAcL2H_F-1FWbog3FlUXkylm1It25M,38394
107
107
  linkml/utils/helpers.py,sha256=yR8n4zFA5wPcYC7xzRuNF3wO16vG80v6j7DM3qTNmIc,447
108
108
  linkml/utils/ifabsent_functions.py,sha256=IkcBcmRYu8sllx7_mTCqu4aHfTxX2AnQoccZ1KOODds,5843
109
109
  linkml/utils/logictools.py,sha256=GSmBiobC49TcQjE08RtXEE3JwJEOV7eEREio25uJiFs,21184
110
110
  linkml/utils/mergeutils.py,sha256=QVm2iQB4v_L2rSvPBsPe9C865R03BgV3TzlPoTTTwWQ,9044
111
- linkml/utils/rawloader.py,sha256=QB7Rdvy4o4ZJEWBWa2_2xzz2TOh_6Oe4slvUn8IBVIc,4329
111
+ linkml/utils/rawloader.py,sha256=nUB8Gfn8yEVwyo6bx0TtB2mBGkilC-fybi0xvQ73Gks,4417
112
112
  linkml/utils/schema_builder.py,sha256=4qhrur8O0xRZnPm7H1h2BZmTkut5R4q-IE_1KKiHy_c,9937
113
113
  linkml/utils/schema_fixer.py,sha256=Uz8M3AOQ_BputJr1f6lbfk1iyvmANUNG80-0B3BQKJ8,16761
114
- linkml/utils/schemaloader.py,sha256=XkH3ARPVcJ3ggnk4qgHJBPepyII4UlSRHzBlTWZbpCo,46062
114
+ linkml/utils/schemaloader.py,sha256=5fLM99v6RZ0OW4JHA0UAd_mtS_2HNubXAjlF5v8vHcw,46093
115
115
  linkml/utils/schemasynopsis.py,sha256=jKf5ZJxpNgSZ5x9oAcdGO3m1IwzcIU2Ni1ugm0SQq1g,18447
116
- linkml/utils/sqlutils.py,sha256=FsOC3Wut6qzzmsnIt9ROsiDt2wXOa1IZUUtJ_T4Nb6M,17071
116
+ linkml/utils/sqlutils.py,sha256=Cwoaz44MLcvAGUEBshT4jtn0Z3w7jVOp85zGkd6FTHY,17071
117
117
  linkml/utils/typereferences.py,sha256=8Yfuz9-HAwOPoJLbIcO_sY9zf32hvPRzGeSOzECfMWA,2232
118
118
  linkml/utils/validation.py,sha256=eLw1-d8x3N1V14bSc6H_mJJXo59ryKvvUIBcOJ1dVMw,1438
119
119
  linkml/validator/__init__.py,sha256=djKc1IyxGKvVYaX5jSxsqjfcEYaw3rFgKbFJxI2wDNk,5002
@@ -128,7 +128,7 @@ linkml/validator/plugins/__init__.py,sha256=F7Un7bfdpSRXvk8LTb7Z4Q22rCtJJKjNkqUf
128
128
  linkml/validator/plugins/jsonschema_validation_plugin.py,sha256=hMPlCyAdMg_N_OHiHMaxcG0fsOA50V4oBNqorNz_oqE,2528
129
129
  linkml/validator/plugins/pydantic_validation_plugin.py,sha256=C-Vp74bz5oqp5V-iuhXW8p0PPDoFY8NCU5x36Ur5Og4,1985
130
130
  linkml/validator/plugins/recommended_slots_plugin.py,sha256=kOdoYQyye47nLA7BjorVmydS84nGpiVy3MoCbPq1Ymo,2308
131
- linkml/validator/plugins/shacl_validation_plugin.py,sha256=A6x3tiX-p03_i_VmR2xnw28j0dpiUw6WIAG-gllwJ8Q,3624
131
+ linkml/validator/plugins/shacl_validation_plugin.py,sha256=X6h1O54m9P5USvqCvNqELD0YpzXkE6UrmxnV0ukU6E4,3888
132
132
  linkml/validator/plugins/validation_plugin.py,sha256=9SMHF8b2bgG9-8351e8bY676e0A4aEBJSXvMjMF5kXg,1548
133
133
  linkml/validator/report.py,sha256=kkkuh-IZF9--cO-2wGjwP3PDLvOcjjvC8AOlxXUIOAM,870
134
134
  linkml/validator/validation_context.py,sha256=Pmccq-LQ9iEeLyZhamp3mKjjtwp4ji1hIe5TT05AUT4,2732
@@ -141,8 +141,8 @@ linkml/workspaces/datamodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
141
141
  linkml/workspaces/datamodel/workspaces.py,sha256=4HdkqweGNfMPqnB1_Onc9DcTfkhoagTRcqruh08nRoI,14905
142
142
  linkml/workspaces/datamodel/workspaces.yaml,sha256=EjVrwPpeRZqJRjuGyyDRxxFzuv55SiLIXPBRUG6HStU,4233
143
143
  linkml/workspaces/example_runner.py,sha256=OmC_yZLIb4KXGQrstBVZL0UAQ9ZAaraguQF0RSf-snk,11611
144
- linkml-1.7.7.dist-info/entry_points.txt,sha256=7haDkIbyC7ZLhm5z-e3BhrLJpY2xoW1yuD8Y7QPNtVg,2093
145
- linkml-1.7.7.dist-info/METADATA,sha256=f74yR9Byjm8tw_y28edi2dJtVwthbMwMUocjhXiy0TQ,3656
146
- linkml-1.7.7.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
147
- linkml-1.7.7.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
148
- linkml-1.7.7.dist-info/RECORD,,
144
+ linkml-1.7.8.dist-info/METADATA,sha256=7Nrt0dXBtqeom1Q3W5wGtdrLzzEcIhoURNoTaZfRyiY,3724
145
+ linkml-1.7.8.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
146
+ linkml-1.7.8.dist-info/entry_points.txt,sha256=7haDkIbyC7ZLhm5z-e3BhrLJpY2xoW1yuD8Y7QPNtVg,2093
147
+ linkml-1.7.8.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
148
+ linkml-1.7.8.dist-info/RECORD,,
File without changes