linkml 1.8.0__py3-none-any.whl → 1.8.1__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.
@@ -435,8 +435,20 @@ class JsonSchemaGenerator(Generator):
435
435
  constraints.add_keyword("const", slot.equals_number)
436
436
  return constraints
437
437
 
438
- def get_subschema_for_slot(self, slot: SlotDefinition, omit_type: bool = False) -> JsonSchema:
438
+ def get_subschema_for_slot(
439
+ self, slot: Union[SlotDefinition, AnonymousSlotExpression], omit_type: bool = False
440
+ ) -> JsonSchema:
439
441
  prop = JsonSchema()
442
+ if isinstance(slot, SlotDefinition) and slot.array:
443
+ # TODO: this is currently too lax, in that it will validate ANY array.
444
+ # see https://github.com/linkml/linkml/issues/2188
445
+ prop = JsonSchema(
446
+ {
447
+ "type": ["null", "boolean", "object", "number", "string", "array"],
448
+ "additionalProperties": True,
449
+ }
450
+ )
451
+ return JsonSchema.array_of(prop)
440
452
  slot_is_multivalued = "multivalued" in slot and slot.multivalued
441
453
  slot_is_inlined = self.schemaview.is_inlined(slot)
442
454
  if not omit_type:
@@ -12,7 +12,11 @@ from typing import Callable, List, Optional, Set, cast
12
12
 
13
13
  import click
14
14
  import requests
15
- from linkml_runtime.linkml_model.meta import ClassDefinition, ClassDefinitionName, SlotDefinition
15
+ from linkml_runtime.linkml_model.meta import (
16
+ ClassDefinition,
17
+ ClassDefinitionName,
18
+ SlotDefinition,
19
+ )
16
20
  from linkml_runtime.utils.formatutils import camelcase, underscore
17
21
 
18
22
  from linkml import REQUESTS_TIMEOUT
@@ -46,6 +50,7 @@ class PlantumlGenerator(Generator):
46
50
  directory: Optional[str] = None
47
51
  kroki_server: Optional[str] = "https://kroki.io"
48
52
  load_image: bool = True
53
+ tooltips_flag: bool = False
49
54
 
50
55
  def visit_schema(
51
56
  self,
@@ -132,18 +137,28 @@ class PlantumlGenerator(Generator):
132
137
  " {field} "
133
138
  + underscore(self.aliased_slot_name(slot))
134
139
  + mod
135
- + ": "
140
+ + " : "
136
141
  + underscore(slot.range)
142
+ + " "
137
143
  + self.cardinality(slot)
138
144
  )
139
145
  self.class_generated.add(cn)
140
146
  self.referenced.add(cn)
141
147
  cls = self.schema.classes[cn]
148
+
149
+ tooltip_contents = str(cls.description)
150
+ first_newline_index = tooltip_contents.find("\n")
151
+ tooltip_contents = tooltip_contents if first_newline_index < 0 else tooltip_contents[0:first_newline_index]
152
+
153
+ if self.format == "svg" and len(tooltip_contents) > 200:
154
+ tooltip_contents = tooltip_contents[0:197] + " ... "
155
+
156
+ tooltip = " [[{" + tooltip_contents + "}]] "
142
157
  if cls.abstract:
143
158
  class_type = "abstract"
144
159
  else:
145
160
  class_type = "class"
146
- return class_type + ' "' + cn + ('" {\n' + "\n".join(slot_defs) + "\n}" if slot_defs else '"')
161
+ return class_type + ' "' + cn + '"' + tooltip + ("{\n" + "\n".join(slot_defs) + "\n}")
147
162
 
148
163
  def class_associations(self, cn: ClassDefinitionName, must_render: bool = False) -> str:
149
164
  """Emit all associations for a focus class. If none are specified, all classes are generated
@@ -217,7 +232,7 @@ class PlantumlGenerator(Generator):
217
232
  classes.append(self.add_class(cn))
218
233
  if mixin not in self.class_generated:
219
234
  classes.append(self.add_class(mixin))
220
- assocs.append(cn + plantuml_uses[0] + mixin + plantuml_uses[1])
235
+ assocs.append('"' + cn + '" ' + plantuml_uses[0] + ' "' + mixin + '" ' + plantuml_uses[1])
221
236
 
222
237
  # Classes that use the class as a mixin
223
238
  if cls.name in self.synopsis.mixinrefs:
@@ -226,7 +241,9 @@ class PlantumlGenerator(Generator):
226
241
  classes.append(self.add_class(ClassDefinitionName(mixin)))
227
242
  if cn not in self.class_generated:
228
243
  classes.append(self.add_class(cn))
229
- assocs.append(ClassDefinitionName(mixin) + plantuml_uses[0] + cn + plantuml_uses[1])
244
+ assocs.append(
245
+ '"' + ClassDefinitionName(mixin) + '" ' + plantuml_uses[0] + ' "' + cn + '" ' + plantuml_uses[1]
246
+ )
230
247
 
231
248
  # Classes that inject information
232
249
  if cn in self.synopsis.applytos.classrefs:
@@ -265,7 +282,7 @@ class PlantumlGenerator(Generator):
265
282
  if slot.multivalued:
266
283
  return " [1..*]" if slot.required else " [0..*]"
267
284
  else:
268
- return " [req]" if slot.required else " [opt]"
285
+ return " "
269
286
  else:
270
287
  if slot.multivalued:
271
288
  return '"1..*" ' if slot.required else '"0..*" '
@@ -1,4 +1,4 @@
1
- from linkml.generators.pydanticgen.pydanticgen import DEFAULT_IMPORTS, PydanticGenerator, cli
1
+ from linkml.generators.pydanticgen.pydanticgen import DEFAULT_IMPORTS, MetadataMode, PydanticGenerator, cli
2
2
  from linkml.generators.pydanticgen.template import (
3
3
  ConditionalImport,
4
4
  Import,
@@ -18,6 +18,7 @@ __all__ = [
18
18
  "DEFAULT_IMPORTS",
19
19
  "Import",
20
20
  "Imports",
21
+ "MetadataMode",
21
22
  "PydanticAttribute",
22
23
  "PydanticBaseModel",
23
24
  "PydanticClass",
@@ -19,6 +19,9 @@ class LinkMLMeta(BaseModel):
19
19
 
20
20
  def __setitem__(self, key:str, value):
21
21
  self.__root__[key] = value
22
+
23
+ def __contains__(self, key:str) -> bool:
24
+ return key in self.__root__
22
25
 
23
26
  class Config:
24
27
  allow_mutation = False
@@ -37,6 +40,9 @@ class LinkMLMeta(RootModel):
37
40
 
38
41
  def __setitem__(self, key:str, value):
39
42
  self.root[key] = value
43
+
44
+ def __contains__(self, key:str) -> bool:
45
+ return key in self.root
40
46
 
41
47
  """
42
48
 
@@ -582,17 +582,22 @@ class PydanticGenerator(OOCodeGenerator):
582
582
  elif self.metadata_mode in (MetadataMode.EXCEPT_CHILDREN, MetadataMode.EXCEPT_CHILDREN.value):
583
583
  meta = {}
584
584
  for k, v in remove_empty_items(source).items():
585
+ if k in ("slots", "classes") and isinstance(model, PydanticModule):
586
+ # FIXME: Special-case removal of slots until we generate class-level slots
587
+ continue
588
+
585
589
  if not hasattr(model, k):
586
590
  meta[k] = v
587
- elif isinstance(getattr(model, k), list) and not any(
588
- [isinstance(item, TemplateModel) for item in getattr(model, k)]
589
- ):
591
+ continue
592
+
593
+ model_attr = getattr(model, k)
594
+ if isinstance(model_attr, list) and not any([isinstance(item, TemplateModel) for item in model_attr]):
590
595
  meta[k] = v
591
- elif isinstance(getattr(model, k), dict) and not any(
592
- [isinstance(item, TemplateModel) for item in getattr(model, k).values()]
596
+ elif isinstance(model_attr, dict) and not any(
597
+ [isinstance(item, TemplateModel) for item in model_attr.values()]
593
598
  ):
594
599
  meta[k] = v
595
- elif not isinstance(getattr(model, k), TemplateModel):
600
+ elif not isinstance(model_attr, (list, dict, TemplateModel)):
596
601
  meta[k] = v
597
602
 
598
603
  elif self.metadata_mode in (MetadataMode.FULL, MetadataMode.FULL.value):
@@ -600,7 +605,7 @@ class PydanticGenerator(OOCodeGenerator):
600
605
  else:
601
606
  raise ValueError(
602
607
  f"Unknown metadata mode '{self.metadata_mode}', needs to be one of "
603
- f"{[mode.value for mode in MetadataMode]}"
608
+ f"{[mode for mode in MetadataMode]}"
604
609
  )
605
610
 
606
611
  model.meta = meta
@@ -753,22 +758,17 @@ class PydanticGenerator(OOCodeGenerator):
753
758
  new_class.bases = bases[k]
754
759
  classes[k] = new_class
755
760
 
756
- schema_meta = {
757
- k: v for k, v in remove_empty_items(schema).items() if k not in PydanticModule.exclude_from_meta()
758
- }
759
-
760
761
  module = PydanticModule(
761
762
  pydantic_ver=self.pydantic_version,
762
763
  metamodel_version=self.schema.metamodel_version,
763
764
  version=self.schema.version,
764
- imports=imports.imports,
765
+ python_imports=imports.imports,
765
766
  base_model=base_model,
766
767
  injected_classes=injected_classes,
767
768
  enums=enums,
768
769
  classes=classes,
769
- meta=schema_meta,
770
770
  )
771
- module = self.include_metadata(module, pyschema)
771
+ module = self.include_metadata(module, schema)
772
772
  return module
773
773
 
774
774
  def serialize(self) -> str:
@@ -830,6 +830,14 @@ Available templates to override:
830
830
  default=False,
831
831
  help="Format generated models with black (must be present in the environment)",
832
832
  )
833
+ @click.option(
834
+ "--meta",
835
+ type=click.Choice([k for k in MetadataMode]),
836
+ default="auto",
837
+ help="How to include linkml schema metadata in generated pydantic classes. "
838
+ "See docs for MetadataMode for full description of choices. "
839
+ "Default (auto) is to include all metadata that can't be otherwise represented",
840
+ )
833
841
  @click.version_option(__version__, "-V", "--version")
834
842
  @click.command()
835
843
  def cli(
@@ -844,6 +852,7 @@ def cli(
844
852
  pydantic_version=int(PYDANTIC_VERSION[0]),
845
853
  extra_fields: Literal["allow", "forbid", "ignore"] = "forbid",
846
854
  black: bool = False,
855
+ meta: MetadataMode = "auto",
847
856
  **args,
848
857
  ):
849
858
  """Generate pydantic classes to represent a LinkML model"""
@@ -870,6 +879,7 @@ def cli(
870
879
  gen_slots=slots,
871
880
  template_dir=template_dir,
872
881
  black=black,
882
+ metadata_mode=meta,
873
883
  **args,
874
884
  )
875
885
  print(gen.serialize())
@@ -322,6 +322,11 @@ class PydanticClass(TemplateModel):
322
322
  def validators(self) -> Optional[Dict[str, PydanticValidator]]:
323
323
  return self._validators()
324
324
 
325
+ @computed_field
326
+ def slots(self) -> Optional[Dict[str, PydanticAttribute]]:
327
+ """alias of attributes"""
328
+ return self.attributes
329
+
325
330
  else:
326
331
  validators: Optional[Dict[str, PydanticValidator]]
327
332
 
@@ -575,7 +580,7 @@ class PydanticModule(TemplateModel):
575
580
  version: Optional[str] = None
576
581
  base_model: PydanticBaseModel = PydanticBaseModel()
577
582
  injected_classes: Optional[List[str]] = None
578
- imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
583
+ python_imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
579
584
  enums: Dict[str, PydanticEnum] = Field(default_factory=dict)
580
585
  classes: Dict[str, PydanticClass] = Field(default_factory=dict)
581
586
  meta: Optional[Dict[str, Any]] = None
@@ -1,4 +1,4 @@
1
- {% for import in imports %}
1
+ {% for import in python_imports %}
2
2
  {{ import }}
3
3
  {%- endfor -%}
4
4
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.8.0
3
+ Version: 1.8.1
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
@@ -28,24 +28,24 @@ linkml/generators/javagen/java_record_template.jinja2,sha256=OQZffLSy_xR3FIhQMlt
28
28
  linkml/generators/javagen.py,sha256=KxwupMztyCRHcSsbtTnOovuj1WamsAny0mxbYWvTiDs,5324
29
29
  linkml/generators/jsonldcontextgen.py,sha256=y_AE1JF0QS75VDfOAquToClrWGAWb_ltsGN5r-Q20F8,8638
30
30
  linkml/generators/jsonldgen.py,sha256=FamA0pbbc9U3kEslsjvgJoJXjQ80VzoPbHmClVGPvKE,7757
31
- linkml/generators/jsonschemagen.py,sha256=Oi55uJJmKiSLoZWn_snN5rx51q0DEGqzHv6VgCLfodY,27747
31
+ linkml/generators/jsonschemagen.py,sha256=sQXlaT_UOiJfbRoxH-hFY4nM0ELdjRlCFnS8HqVzqb8,28263
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
34
34
  linkml/generators/markdowngen.py,sha256=mwydWkrF-WzsTiKwJqqe9NUya5VaTj7ywKqodP85A44,34449
35
35
  linkml/generators/namespacegen.py,sha256=vVcIyM0zlKd7XRvtdzwTwHjG4Pg49801gy4FUmjJlqQ,6450
36
36
  linkml/generators/oocodegen.py,sha256=r73QI08ajbTZTobc9OIG6BMWZft3zdu76vKVR33pyYg,7774
37
37
  linkml/generators/owlgen.py,sha256=NHIcL1d79WoeqfiZRj6kRk5aTXcQ6JDNXV_VAtH6Vt0,57554
38
- linkml/generators/plantumlgen.py,sha256=2e4ejw0B72Sl_-mipgpKz743LVMs0kRZc8E0MK7gxFY,14919
38
+ linkml/generators/plantumlgen.py,sha256=JgAox2o_HQgXR4ZxdHwdRUzcHSbpsAdv15Qsq1vnEZs,15463
39
39
  linkml/generators/prefixmapgen.py,sha256=w8gMCFOI0DmZsNvo9IkbhOqVWqqWYEN6SXnAP3BgFSk,4240
40
40
  linkml/generators/projectgen.py,sha256=OuT_AneoZFNMCn50GllfZafadUHt50u61JcftM2eed4,9750
41
41
  linkml/generators/protogen.py,sha256=i4sEhNdAiFFP12fwfRdype1MX8yU-E5fF7XzkcpBCd0,2512
42
- linkml/generators/pydanticgen/__init__.py,sha256=uKGaaQSaeKqinHImXGFE448i-t8Oi0No8suIO76sep8,629
42
+ linkml/generators/pydanticgen/__init__.py,sha256=-972x-uV0atgveW_n5qvG1WelKANvsk9ZseZOIH_tNI,663
43
43
  linkml/generators/pydanticgen/array.py,sha256=EYqAkT2_wE82kZ-niUWAC5U3V8EvjjyfSH7tz5GYKWo,19461
44
44
  linkml/generators/pydanticgen/black.py,sha256=BaJ7b-kMUbIN_cKRT3yCaMEbFwxzj1_U7w4fKQnkL44,723
45
45
  linkml/generators/pydanticgen/build.py,sha256=Ia8qy4C16b-KqO_fw4tGQW_Eo4grCVyX7RsuJ3uRTtk,2681
46
- linkml/generators/pydanticgen/includes.py,sha256=PIVbACrtqQS6LFrXqQsJC6dQUcGBM3uY4E4qLQWJrnk,998
47
- linkml/generators/pydanticgen/pydanticgen.py,sha256=WZxFfLvXhHXVtDQpLjMf63eoBMUxcW0oEMS14jbDOWo,34458
48
- linkml/generators/pydanticgen/template.py,sha256=HESLY1Rd7N_e92dqrh_fif_yp3oDl0n1UAghklMyphA,20982
46
+ linkml/generators/pydanticgen/includes.py,sha256=0CZi8vs6Z2eVvMOWhbTphCYoI3X8E9-8LgSaCBeiuCY,1174
47
+ linkml/generators/pydanticgen/pydanticgen.py,sha256=kaUBENnVEGpFs3gGK8kQbR2ZoZ1eg9Jiz0yNtXajbEw,34900
48
+ linkml/generators/pydanticgen/template.py,sha256=gffWyzJNmjw2Kf1UszRp97qGrnkAGd4UnLimU2nEsEA,21154
49
49
  linkml/generators/pydanticgen/templates/attribute.py.jinja,sha256=S_T-M7n_eeM3LW5gQUuTPH1-Oc1CEtL7Bve7qAFouYQ,688
50
50
  linkml/generators/pydanticgen/templates/base_model.py.jinja,sha256=48y64MnC9rjNSV-nKLMeDuHN4gm15UsInhnKxh65zoM,834
51
51
  linkml/generators/pydanticgen/templates/class.py.jinja,sha256=V_lyvFcZ9QhlKZi2bL5iff8RjHir2SQTTypOzQMOfmk,744
@@ -53,7 +53,7 @@ linkml/generators/pydanticgen/templates/conditional_import.py.jinja,sha256=Yhekn
53
53
  linkml/generators/pydanticgen/templates/enum.py.jinja,sha256=572XFQyEMZfL-G_Cj68T-NI_mUnDoFOAVJOGIKu2Hb8,338
54
54
  linkml/generators/pydanticgen/templates/footer.py.jinja,sha256=WuWGJjkWDQcEPL2bGk5fdGuhO0gQRUJzpo4CQHHxQNQ,385
55
55
  linkml/generators/pydanticgen/templates/imports.py.jinja,sha256=d1XFna2eOpkH8cgJML3vXwqGcocczvOcrbg6jjd4kP0,945
56
- linkml/generators/pydanticgen/templates/module.py.jinja,sha256=nymjtSX4jqV9deSsXrsh1HWR90ygECHE4CV9MCyNrTY,615
56
+ linkml/generators/pydanticgen/templates/module.py.jinja,sha256=gYauXrq8ML3ieaN_DFGB9yj9VfZV2yHG5LRQUzWy4VA,622
57
57
  linkml/generators/pydanticgen/templates/validator.py.jinja,sha256=Yo4dubQal-HwEoJKztQkLYM5qCz1I8na2oaDlXuEpwU,532
58
58
  linkml/generators/pythongen.py,sha256=CiLmc964siGeJWZC_bOffsqrSnJ5qzn7Eu961C47xgo,52850
59
59
  linkml/generators/rdfgen.py,sha256=eQi3XXjWk6xliTLncQ4G0VYzGCJwCU_FFG-O7tA8DSA,2959
@@ -143,8 +143,8 @@ linkml/workspaces/datamodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
143
143
  linkml/workspaces/datamodel/workspaces.py,sha256=4HdkqweGNfMPqnB1_Onc9DcTfkhoagTRcqruh08nRoI,14905
144
144
  linkml/workspaces/datamodel/workspaces.yaml,sha256=EjVrwPpeRZqJRjuGyyDRxxFzuv55SiLIXPBRUG6HStU,4233
145
145
  linkml/workspaces/example_runner.py,sha256=hDk2RGxRE5-WYNriWknugtbm3Mhn8bFcAcXGMlkYxVk,11705
146
- linkml-1.8.0.dist-info/entry_points.txt,sha256=7haDkIbyC7ZLhm5z-e3BhrLJpY2xoW1yuD8Y7QPNtVg,2093
147
- linkml-1.8.0.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
148
- linkml-1.8.0.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
149
- linkml-1.8.0.dist-info/METADATA,sha256=ImZiqQezJfM8leU0WgCimYjcJXydh1XO8T22tlC9zpw,3724
150
- linkml-1.8.0.dist-info/RECORD,,
146
+ linkml-1.8.1.dist-info/entry_points.txt,sha256=7haDkIbyC7ZLhm5z-e3BhrLJpY2xoW1yuD8Y7QPNtVg,2093
147
+ linkml-1.8.1.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
148
+ linkml-1.8.1.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
149
+ linkml-1.8.1.dist-info/METADATA,sha256=-Nk6qRvku2ysnCvxuObK_ILTulyQzPAKyvbbUbzSSfk,3724
150
+ linkml-1.8.1.dist-info/RECORD,,
File without changes