linkml 1.5.5__py3-none-any.whl → 1.5.7__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 +2 -6
- linkml/_version.py +1 -1
- linkml/generators/PythonGenNotes.md +4 -4
- linkml/generators/__init__.py +26 -5
- linkml/generators/common/type_designators.py +27 -22
- linkml/generators/csvgen.py +4 -10
- linkml/generators/docgen/class.md.jinja2 +7 -0
- linkml/generators/docgen/class_diagram.md.jinja2 +0 -6
- linkml/generators/docgen/subset.md.jinja2 +54 -13
- linkml/generators/docgen.py +94 -92
- linkml/generators/dotgen.py +5 -9
- linkml/generators/erdiagramgen.py +58 -53
- linkml/generators/excelgen.py +10 -16
- linkml/generators/golanggen.py +11 -21
- linkml/generators/golrgen.py +4 -13
- linkml/generators/graphqlgen.py +3 -11
- linkml/generators/javagen.py +8 -15
- linkml/generators/jsonldcontextgen.py +7 -36
- linkml/generators/jsonldgen.py +14 -12
- linkml/generators/jsonschemagen.py +183 -136
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +40 -89
- linkml/generators/namespacegen.py +1 -2
- linkml/generators/oocodegen.py +22 -25
- linkml/generators/owlgen.py +48 -49
- linkml/generators/prefixmapgen.py +6 -14
- linkml/generators/projectgen.py +7 -14
- linkml/generators/protogen.py +3 -5
- linkml/generators/pydanticgen.py +85 -73
- linkml/generators/pythongen.py +89 -157
- linkml/generators/rdfgen.py +5 -11
- linkml/generators/shaclgen.py +32 -18
- linkml/generators/shexgen.py +19 -24
- linkml/generators/sparqlgen.py +5 -13
- linkml/generators/sqlalchemy/__init__.py +3 -2
- linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +7 -7
- linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +3 -3
- linkml/generators/sqlalchemygen.py +29 -27
- linkml/generators/sqlddlgen.py +34 -26
- linkml/generators/sqltablegen.py +21 -21
- linkml/generators/sssomgen.py +11 -13
- linkml/generators/summarygen.py +2 -4
- linkml/generators/terminusdbgen.py +7 -19
- linkml/generators/typescriptgen.py +10 -18
- linkml/generators/yamlgen.py +0 -2
- linkml/generators/yumlgen.py +23 -71
- linkml/linter/cli.py +4 -11
- linkml/linter/config/datamodel/config.py +17 -47
- linkml/linter/linter.py +2 -4
- linkml/linter/rules.py +34 -48
- linkml/reporting/__init__.py +2 -0
- linkml/reporting/model.py +9 -24
- linkml/transformers/relmodel_transformer.py +20 -33
- linkml/transformers/schema_renamer.py +14 -10
- linkml/utils/converter.py +15 -15
- linkml/utils/datautils.py +9 -24
- linkml/utils/datavalidator.py +2 -2
- linkml/utils/execute_tutorial.py +10 -12
- linkml/utils/generator.py +74 -92
- linkml/utils/helpers.py +4 -2
- linkml/utils/ifabsent_functions.py +23 -15
- linkml/utils/mergeutils.py +19 -35
- linkml/utils/rawloader.py +2 -6
- linkml/utils/schema_builder.py +31 -19
- linkml/utils/schema_fixer.py +28 -18
- linkml/utils/schemaloader.py +44 -89
- linkml/utils/schemasynopsis.py +50 -73
- linkml/utils/sqlutils.py +40 -30
- linkml/utils/typereferences.py +9 -6
- linkml/utils/validation.py +4 -5
- linkml/validators/__init__.py +2 -0
- linkml/validators/jsonschemavalidator.py +104 -53
- linkml/validators/sparqlvalidator.py +5 -15
- linkml/workspaces/datamodel/workspaces.py +13 -30
- linkml/workspaces/example_runner.py +75 -68
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/METADATA +2 -2
- linkml-1.5.7.dist-info/RECORD +109 -0
- linkml-1.5.5.dist-info/RECORD +0 -109
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/LICENSE +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/WHEEL +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/entry_points.txt +0 -0
linkml/generators/markdowngen.py
CHANGED
@@ -2,15 +2,19 @@ import os
|
|
2
2
|
from contextlib import redirect_stdout
|
3
3
|
from dataclasses import dataclass, field
|
4
4
|
from io import StringIO
|
5
|
-
from typing import Any, Callable, Dict, List, Optional, Set,
|
5
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
6
6
|
|
7
7
|
import click
|
8
8
|
from jsonasobj2 import JsonObj, values
|
9
|
-
from linkml_runtime.linkml_model.meta import (
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
from linkml_runtime.linkml_model.meta import (
|
10
|
+
ClassDefinition,
|
11
|
+
ClassDefinitionName,
|
12
|
+
Element,
|
13
|
+
EnumDefinition,
|
14
|
+
SlotDefinition,
|
15
|
+
SubsetDefinition,
|
16
|
+
TypeDefinition,
|
17
|
+
)
|
14
18
|
from linkml_runtime.utils.formatutils import be, camelcase, underscore
|
15
19
|
|
16
20
|
from linkml._version import __version__
|
@@ -30,7 +34,7 @@ class MarkdownGenerator(Generator):
|
|
30
34
|
The markdown is suitable for deployment as a MkDocs or Sphinx site
|
31
35
|
"""
|
32
36
|
|
33
|
-
#ClassVars
|
37
|
+
# ClassVars
|
34
38
|
generatorname = os.path.basename(__file__)
|
35
39
|
generatorversion = "0.2.1"
|
36
40
|
directory_output = True
|
@@ -71,7 +75,7 @@ class MarkdownGenerator(Generator):
|
|
71
75
|
if directory:
|
72
76
|
os.makedirs(directory, exist_ok=True)
|
73
77
|
elif image_dir:
|
74
|
-
raise ValueError(
|
78
|
+
raise ValueError("Image directory can only be used with '-d' option")
|
75
79
|
if image_dir:
|
76
80
|
self.image_directory = os.path.join(directory, "images")
|
77
81
|
if not noimages:
|
@@ -80,9 +84,7 @@ class MarkdownGenerator(Generator):
|
|
80
84
|
if not self.no_types_dir:
|
81
85
|
os.makedirs(os.path.join(directory, "types"), exist_ok=True)
|
82
86
|
|
83
|
-
with open(
|
84
|
-
self.exist_warning(directory, index_file), "w", encoding="UTF-8"
|
85
|
-
) as ixfile:
|
87
|
+
with open(self.exist_warning(directory, index_file), "w", encoding="UTF-8") as ixfile:
|
86
88
|
with redirect_stdout(ixfile):
|
87
89
|
self.frontmatter(f"{self.schema.name}")
|
88
90
|
self.para(
|
@@ -92,11 +94,7 @@ class MarkdownGenerator(Generator):
|
|
92
94
|
|
93
95
|
self.header(3, "Classes")
|
94
96
|
for cls in sorted(self.schema.classes.values(), key=lambda c: c.name):
|
95
|
-
if (
|
96
|
-
not cls.is_a
|
97
|
-
and not cls.mixin
|
98
|
-
and self.is_secondary_ref(cls.name)
|
99
|
-
):
|
97
|
+
if not cls.is_a and not cls.mixin and self.is_secondary_ref(cls.name):
|
100
98
|
self.class_hier(cls)
|
101
99
|
|
102
100
|
self.header(3, "Mixins")
|
@@ -114,9 +112,7 @@ class MarkdownGenerator(Generator):
|
|
114
112
|
self.enum_hier(enu)
|
115
113
|
|
116
114
|
self.header(3, "Subsets")
|
117
|
-
for subset in sorted(
|
118
|
-
self.schema.subsets.values(), key=lambda s: s.name
|
119
|
-
):
|
115
|
+
for subset in sorted(self.schema.subsets.values(), key=lambda s: s.name):
|
120
116
|
self.bullet(self.subset_link(subset, use_desc=True), 0)
|
121
117
|
|
122
118
|
self.header(3, "Types")
|
@@ -131,14 +127,9 @@ class MarkdownGenerator(Generator):
|
|
131
127
|
else:
|
132
128
|
typ_typ = f"**{typ.base}**"
|
133
129
|
|
134
|
-
self.bullet(
|
135
|
-
self.type_link(
|
136
|
-
typ, after_link=f" ({typ_typ})", use_desc=True
|
137
|
-
)
|
138
|
-
)
|
130
|
+
self.bullet(self.type_link(typ, after_link=f" ({typ_typ})", use_desc=True))
|
139
131
|
|
140
132
|
def visit_class(self, cls: ClassDefinition) -> bool:
|
141
|
-
|
142
133
|
# allow client to relabel metamodel
|
143
134
|
mixin_local_name = self.get_metamodel_slot_name("Mixin")
|
144
135
|
class_local_name = self.get_metamodel_slot_name("Class")
|
@@ -146,9 +137,7 @@ class MarkdownGenerator(Generator):
|
|
146
137
|
if self.gen_classes and cls.name not in self.gen_classes:
|
147
138
|
return False
|
148
139
|
|
149
|
-
with open(
|
150
|
-
self.exist_warning(self.dir_path(cls)), "w", encoding="UTF-8"
|
151
|
-
) as clsfile:
|
140
|
+
with open(self.exist_warning(self.dir_path(cls)), "w", encoding="UTF-8") as clsfile:
|
152
141
|
with redirect_stdout(clsfile):
|
153
142
|
class_curi = self.namespaces.uri_or_curie_for(
|
154
143
|
str(self.namespaces._base), camelcase(cls.name)
|
@@ -164,9 +153,7 @@ class MarkdownGenerator(Generator):
|
|
164
153
|
directory=self.image_directory,
|
165
154
|
load_image=not self.noimages,
|
166
155
|
)
|
167
|
-
img_url = os.path.join(
|
168
|
-
"images", os.path.basename(yg.output_file_name)
|
169
|
-
)
|
156
|
+
img_url = os.path.join("images", os.path.basename(yg.output_file_name))
|
170
157
|
else:
|
171
158
|
yg = YumlGenerator(self)
|
172
159
|
img_url = (
|
@@ -219,9 +206,7 @@ class MarkdownGenerator(Generator):
|
|
219
206
|
self.header(2, "Attributes")
|
220
207
|
|
221
208
|
# List all of the slots that directly belong to the class
|
222
|
-
slot_list = [
|
223
|
-
slot for slot in [self.schema.slots[sn] for sn in cls.slots]
|
224
|
-
]
|
209
|
+
slot_list = [slot for slot in [self.schema.slots[sn] for sn in cls.slots]]
|
225
210
|
own_slots = [slot for slot in slot_list if cls.name in slot.domain_of]
|
226
211
|
if own_slots:
|
227
212
|
self.header(3, "Own")
|
@@ -232,9 +217,7 @@ class MarkdownGenerator(Generator):
|
|
232
217
|
# List all of the inherited slots
|
233
218
|
ancestors = set(self.ancestors(cls))
|
234
219
|
inherited_slots = [
|
235
|
-
slot
|
236
|
-
for slot in slot_list
|
237
|
-
if set(slot.domain_of).intersection(ancestors)
|
220
|
+
slot for slot in slot_list if set(slot.domain_of).intersection(ancestors)
|
238
221
|
]
|
239
222
|
if inherited_slots:
|
240
223
|
self.header(3, "Inherited from " + cls.is_a + ":")
|
@@ -246,9 +229,7 @@ class MarkdownGenerator(Generator):
|
|
246
229
|
mixed_in_classes = set()
|
247
230
|
for mixin in cls.mixins:
|
248
231
|
mixed_in_classes.add(mixin)
|
249
|
-
mixed_in_classes.update(
|
250
|
-
set(self.ancestors(self.schema.classes[mixin]))
|
251
|
-
)
|
232
|
+
mixed_in_classes.update(set(self.ancestors(self.schema.classes[mixin])))
|
252
233
|
for slot in slot_list:
|
253
234
|
mixers = set(slot.domain_of).intersection(mixed_in_classes)
|
254
235
|
for mixer in mixers:
|
@@ -260,9 +241,7 @@ class MarkdownGenerator(Generator):
|
|
260
241
|
return False
|
261
242
|
|
262
243
|
def visit_type(self, typ: TypeDefinition) -> None:
|
263
|
-
with open(
|
264
|
-
self.exist_warning(self.dir_path(typ)), "w", encoding="UTF-8"
|
265
|
-
) as typefile:
|
244
|
+
with open(self.exist_warning(self.dir_path(typ)), "w", encoding="UTF-8") as typefile:
|
266
245
|
with redirect_stdout(typefile):
|
267
246
|
type_uri = typ.definition_uri
|
268
247
|
type_curie = self.namespaces.curie_for(type_uri)
|
@@ -278,12 +257,8 @@ class MarkdownGenerator(Generator):
|
|
278
257
|
self.element_properties(typ)
|
279
258
|
|
280
259
|
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> None:
|
281
|
-
with open(
|
282
|
-
self.exist_warning(self.dir_path(slot)), "w", encoding="UTF-8"
|
283
|
-
) as slotfile:
|
260
|
+
with open(self.exist_warning(self.dir_path(slot)), "w", encoding="UTF-8") as slotfile:
|
284
261
|
with redirect_stdout(slotfile):
|
285
|
-
import logging
|
286
|
-
|
287
262
|
slot_curie = self.namespaces.uri_or_curie_for(
|
288
263
|
str(self.namespaces._base), underscore(slot.name)
|
289
264
|
)
|
@@ -312,29 +287,26 @@ class MarkdownGenerator(Generator):
|
|
312
287
|
self.bullet(f"{self.class_link(rc)}")
|
313
288
|
if aliased_slot_name == "relation":
|
314
289
|
if slot.subproperty_of:
|
315
|
-
|
316
|
-
|
290
|
+
reifies = (
|
291
|
+
self.slot_link(slot.subproperty_of)
|
292
|
+
if slot.subproperty_of in self.schema.slots
|
293
|
+
else slot.subproperty_of
|
317
294
|
)
|
295
|
+
self.bullet(f" reifies: {reifies}")
|
318
296
|
self.element_properties(slot)
|
319
297
|
|
320
298
|
def visit_enum(self, enum: EnumDefinition) -> None:
|
321
|
-
with open(
|
322
|
-
self.exist_warning(self.dir_path(enum)), "w", encoding="UTF-8"
|
323
|
-
) as enumfile:
|
299
|
+
with open(self.exist_warning(self.dir_path(enum)), "w", encoding="UTF-8") as enumfile:
|
324
300
|
with redirect_stdout(enumfile):
|
325
301
|
enum_curie = self.namespaces.uri_or_curie_for(
|
326
302
|
str(self.namespaces._base), underscore(enum.name)
|
327
303
|
)
|
328
304
|
enum_uri = self.namespaces.uri_for(enum_curie)
|
329
|
-
self.element_header(
|
330
|
-
obj=enum, name=enum.name, curie=enum_curie, uri=enum_uri
|
331
|
-
)
|
305
|
+
self.element_header(obj=enum, name=enum.name, curie=enum_curie, uri=enum_uri)
|
332
306
|
self.element_properties(enum)
|
333
307
|
|
334
308
|
def visit_subset(self, subset: SubsetDefinition) -> None:
|
335
|
-
with open(
|
336
|
-
self.exist_warning(self.dir_path(subset)), "w", encoding="UTF-8"
|
337
|
-
) as subsetfile:
|
309
|
+
with open(self.exist_warning(self.dir_path(subset)), "w", encoding="UTF-8") as subsetfile:
|
338
310
|
with redirect_stdout(subsetfile):
|
339
311
|
curie = self.namespaces.uri_or_curie_for(
|
340
312
|
str(self.namespaces._base), underscore(subset.name)
|
@@ -367,7 +339,6 @@ class MarkdownGenerator(Generator):
|
|
367
339
|
self.element_properties(subset)
|
368
340
|
|
369
341
|
def element_header(self, obj: Element, name: str, curie: str, uri: str) -> None:
|
370
|
-
simple_name = curie.split(":", 1)[1]
|
371
342
|
if isinstance(obj, TypeDefinition):
|
372
343
|
obj_type = "Type"
|
373
344
|
elif isinstance(obj, ClassDefinition):
|
@@ -382,9 +353,7 @@ class MarkdownGenerator(Generator):
|
|
382
353
|
obj_type = "Class"
|
383
354
|
|
384
355
|
header_label = (
|
385
|
-
f"{obj_type}: ~~{name}~~ _(deprecated)_"
|
386
|
-
if obj.deprecated
|
387
|
-
else f"{obj_type}: {name}"
|
356
|
+
f"{obj_type}: ~~{name}~~ _(deprecated)_" if obj.deprecated else f"{obj_type}: {name}"
|
388
357
|
)
|
389
358
|
self.header(1, header_label)
|
390
359
|
|
@@ -413,7 +382,7 @@ class MarkdownGenerator(Generator):
|
|
413
382
|
def enum_list(title: str, obj: EnumDefinition) -> None:
|
414
383
|
# This data is from the enum provided in the YAML
|
415
384
|
self.header(2, title)
|
416
|
-
print(
|
385
|
+
print("| Text | Description | Meaning | Other Information |")
|
417
386
|
print("| :--- | :---: | :---: | ---: |")
|
418
387
|
|
419
388
|
for item, item_info in obj.permissible_values.items():
|
@@ -507,11 +476,7 @@ class MarkdownGenerator(Generator):
|
|
507
476
|
if isinstance(obj, EnumDefinition)
|
508
477
|
else camelcase(obj.name)
|
509
478
|
)
|
510
|
-
subdir = (
|
511
|
-
"/types"
|
512
|
-
if isinstance(obj, TypeDefinition) and not self.no_types_dir
|
513
|
-
else ""
|
514
|
-
)
|
479
|
+
subdir = "/types" if isinstance(obj, TypeDefinition) and not self.no_types_dir else ""
|
515
480
|
return f"{self.directory}{subdir}/{filename}.md"
|
516
481
|
|
517
482
|
def mappings(self, obj: Union[SlotDefinition, ClassDefinition]) -> None:
|
@@ -641,11 +606,7 @@ class MarkdownGenerator(Generator):
|
|
641
606
|
parent = self.schema.classes[obj.is_a]
|
642
607
|
else:
|
643
608
|
parent = None
|
644
|
-
return
|
645
|
-
""
|
646
|
-
if parent and obj.description == parent.description
|
647
|
-
else obj.description
|
648
|
-
)
|
609
|
+
return "" if parent and obj.description == parent.description else obj.description
|
649
610
|
return ""
|
650
611
|
|
651
612
|
def _link(
|
@@ -668,9 +629,7 @@ class MarkdownGenerator(Generator):
|
|
668
629
|
if obj is None or not self.is_secondary_ref(obj.name):
|
669
630
|
return self.bbin(obj)
|
670
631
|
if isinstance(obj, SlotDefinition):
|
671
|
-
link_name = (
|
672
|
-
(be(obj.domain) + "➞") if obj.alias else ""
|
673
|
-
) + self.aliased_slot_name(obj)
|
632
|
+
link_name = ((be(obj.domain) + "➞") if obj.alias else "") + self.aliased_slot_name(obj)
|
674
633
|
link_ref = underscore(obj.name)
|
675
634
|
elif isinstance(obj, TypeDefinition):
|
676
635
|
link_name = camelcase(obj.name)
|
@@ -825,25 +784,17 @@ class MarkdownGenerator(Generator):
|
|
825
784
|
@click.command()
|
826
785
|
@click.option("--dir", "-d", required=True, help="Output directory")
|
827
786
|
@click.option("--classes", "-c", multiple=True, help="Class(es) to emit")
|
828
|
-
@click.option(
|
829
|
-
|
830
|
-
)
|
831
|
-
@click.option(
|
832
|
-
"--img", "-i", is_flag=True, help="Download YUML images to 'image' directory"
|
833
|
-
)
|
787
|
+
@click.option("--map-fields", "-M", multiple=True, help="Map metamodel fields, e.g. slot=field")
|
788
|
+
@click.option("--img", "-i", is_flag=True, help="Download YUML images to 'image' directory")
|
834
789
|
@click.option("--index-file", "-I", help="Name of markdown file that holds index")
|
835
790
|
@click.option("--noimages", is_flag=True, help="Do not (re-)generate images")
|
836
791
|
@click.option("--noyuml", is_flag=True, help="Do not add yUML figures to pages")
|
837
|
-
@click.option(
|
838
|
-
"--notypesdir", is_flag=True, help="Do not create a separate types directory"
|
839
|
-
)
|
792
|
+
@click.option("--notypesdir", is_flag=True, help="Do not create a separate types directory")
|
840
793
|
@click.option("--warnonexist", is_flag=True, help="Warn if output file already exists")
|
841
794
|
@click.version_option(__version__, "-V", "--version")
|
842
795
|
def cli(yamlfile, map_fields, dir, img, index_file, notypesdir, warnonexist, **kwargs):
|
843
796
|
"""Generate markdown documentation of a LinkML model"""
|
844
|
-
gen = MarkdownGenerator(
|
845
|
-
yamlfile, no_types_dir=notypesdir, warn_on_exist=warnonexist, **kwargs
|
846
|
-
)
|
797
|
+
gen = MarkdownGenerator(yamlfile, no_types_dir=notypesdir, warn_on_exist=warnonexist, **kwargs)
|
847
798
|
if map_fields is not None:
|
848
799
|
gen.metamodel_name_map = {}
|
849
800
|
for mf in map_fields:
|
@@ -5,7 +5,6 @@ import click
|
|
5
5
|
from linkml_runtime.utils.formatutils import be, split_line
|
6
6
|
|
7
7
|
from linkml._version import __version__
|
8
|
-
from linkml.generators import PYTHON_GEN_VERSION
|
9
8
|
from linkml.generators.pythongen import PythonGenerator
|
10
9
|
from linkml.utils.generator import shared_arguments
|
11
10
|
|
@@ -13,7 +12,7 @@ from linkml.utils.generator import shared_arguments
|
|
13
12
|
@dataclass
|
14
13
|
class NamespaceGenerator(PythonGenerator):
|
15
14
|
generatorname = os.path.basename(__file__)
|
16
|
-
generatorversion =
|
15
|
+
generatorversion = "0.0.1"
|
17
16
|
valid_formats = ["py"]
|
18
17
|
visit_all_class_slots = False
|
19
18
|
|
linkml/generators/oocodegen.py
CHANGED
@@ -2,12 +2,16 @@ import abc
|
|
2
2
|
import re
|
3
3
|
import unicodedata
|
4
4
|
from dataclasses import dataclass, field
|
5
|
-
from typing import List, Optional
|
6
|
-
|
7
|
-
from linkml_runtime.linkml_model.meta import (
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
from typing import Dict, List, Optional
|
6
|
+
|
7
|
+
from linkml_runtime.linkml_model.meta import (
|
8
|
+
ClassDefinition,
|
9
|
+
EnumDefinition,
|
10
|
+
EnumDefinitionName,
|
11
|
+
SchemaDefinition,
|
12
|
+
SlotDefinition,
|
13
|
+
TypeDefinition,
|
14
|
+
)
|
11
15
|
from linkml_runtime.utils.formatutils import camelcase, lcamelcase, underscore
|
12
16
|
from linkml_runtime.utils.schemaview import SchemaView
|
13
17
|
|
@@ -67,7 +71,6 @@ class OOClass:
|
|
67
71
|
|
68
72
|
@dataclass
|
69
73
|
class OOCodeGenerator(Generator):
|
70
|
-
|
71
74
|
# ClassVars
|
72
75
|
java_style = True
|
73
76
|
visit_all_class_slots = False
|
@@ -120,7 +123,7 @@ class OOCodeGenerator(Generator):
|
|
120
123
|
return label
|
121
124
|
else:
|
122
125
|
# add an underscore if the value starts with a digit
|
123
|
-
label = re.sub("(?=^\d)", "number_", label)
|
126
|
+
label = re.sub(r"(?=^\d)", "number_", label)
|
124
127
|
|
125
128
|
safe_label = ""
|
126
129
|
for character in label:
|
@@ -128,33 +131,25 @@ class OOCodeGenerator(Generator):
|
|
128
131
|
|
129
132
|
return safe_label
|
130
133
|
|
131
|
-
def generate_enums(
|
132
|
-
self, all_enums: Dict[EnumDefinitionName, EnumDefinition]
|
133
|
-
) -> Dict:
|
134
|
+
def generate_enums(self, all_enums: Dict[EnumDefinitionName, EnumDefinition]) -> Dict:
|
134
135
|
# TODO: make an explicit class to represent how an enum is passed to the template
|
135
136
|
enums = {}
|
136
137
|
for enum_name, enum_original in all_enums.items():
|
137
|
-
enum = {
|
138
|
-
"name": camelcase(enum_name),
|
139
|
-
"values": {}
|
140
|
-
}
|
138
|
+
enum = {"name": camelcase(enum_name), "values": {}}
|
141
139
|
|
142
140
|
if hasattr(enum_original, "description"):
|
143
141
|
enum["description"] = enum_original.description
|
144
142
|
|
145
143
|
for pv in enum_original.permissible_values.values():
|
146
144
|
label = self.generate_enum_label(pv.text)
|
147
|
-
val = {
|
148
|
-
"label": label,
|
149
|
-
"value": pv.text.replace('"', '\\"')
|
150
|
-
}
|
145
|
+
val = {"label": label, "value": pv.text.replace('"', '\\"')}
|
151
146
|
if hasattr(pv, "description"):
|
152
147
|
val["description"] = pv.description
|
153
|
-
else
|
148
|
+
else:
|
154
149
|
val["description"] = None
|
155
150
|
|
156
151
|
enum["values"][label] = val
|
157
|
-
|
152
|
+
|
158
153
|
enums[enum_name] = enum
|
159
154
|
|
160
155
|
return enums
|
@@ -170,12 +165,14 @@ class OOCodeGenerator(Generator):
|
|
170
165
|
for cn in sv.all_classes(imports=False):
|
171
166
|
c = sv.get_class(cn)
|
172
167
|
safe_cn = camelcase(cn)
|
173
|
-
oodoc = OODocument(
|
174
|
-
name=safe_cn, package=self.package, source_schema=sv.schema
|
175
|
-
)
|
168
|
+
oodoc = OODocument(name=safe_cn, package=self.package, source_schema=sv.schema)
|
176
169
|
docs.append(oodoc)
|
177
170
|
ooclass = OOClass(
|
178
|
-
name=safe_cn,
|
171
|
+
name=safe_cn,
|
172
|
+
description=c.description,
|
173
|
+
package=self.package,
|
174
|
+
fields=[],
|
175
|
+
source_class=c,
|
179
176
|
)
|
180
177
|
# currently hardcoded for java style, one class per doc
|
181
178
|
oodoc.classes = [ooclass]
|
linkml/generators/owlgen.py
CHANGED
@@ -6,18 +6,23 @@ import logging
|
|
6
6
|
import os
|
7
7
|
from dataclasses import dataclass, field
|
8
8
|
from enum import Enum, unique
|
9
|
-
from typing import
|
9
|
+
from typing import Optional, TextIO, Union
|
10
10
|
|
11
11
|
import click
|
12
|
-
from linkml_runtime.linkml_model.meta import (
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
from linkml_runtime.linkml_model.meta import (
|
13
|
+
ClassDefinition,
|
14
|
+
ClassDefinitionName,
|
15
|
+
Definition,
|
16
|
+
Element,
|
17
|
+
ElementName,
|
18
|
+
EnumDefinition,
|
19
|
+
EnumDefinitionName,
|
20
|
+
SchemaDefinition,
|
21
|
+
SlotDefinition,
|
22
|
+
SlotDefinitionName,
|
23
|
+
TypeDefinition,
|
24
|
+
TypeDefinitionName,
|
25
|
+
)
|
21
26
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
22
27
|
from rdflib import OWL, RDF, BNode, Graph, Literal, URIRef
|
23
28
|
from rdflib.collection import Collection
|
@@ -28,7 +33,6 @@ from rdflib.plugin import plugins as rdflib_plugins
|
|
28
33
|
from linkml import METAMODEL_NAMESPACE, METAMODEL_NAMESPACE_NAME
|
29
34
|
from linkml._version import __version__
|
30
35
|
from linkml.utils.generator import Generator, shared_arguments
|
31
|
-
from linkml.utils.schemaloader import SchemaLoader
|
32
36
|
|
33
37
|
|
34
38
|
@unique
|
@@ -70,6 +74,7 @@ class OwlSchemaGenerator(Generator):
|
|
70
74
|
valid_formats = ["owl", "ttl"] + [
|
71
75
|
x.name for x in rdflib_plugins(None, rdflib_Parser) if "/" not in str(x.name)
|
72
76
|
]
|
77
|
+
file_extension = "owl"
|
73
78
|
visits_are_sorted = True
|
74
79
|
uses_schemaloader = True
|
75
80
|
requires_metamodel = True
|
@@ -82,6 +87,7 @@ class OwlSchemaGenerator(Generator):
|
|
82
87
|
graph: Optional[Graph] = None
|
83
88
|
top_value_uri: Optional[URIRef] = None
|
84
89
|
type_objects: bool = field(default_factory=lambda: True)
|
90
|
+
assert_equivalent_classes: bool = False
|
85
91
|
|
86
92
|
def visit_schema(self, output: Optional[str] = None, **_):
|
87
93
|
owl_id = self.schema.id
|
@@ -92,9 +98,7 @@ class OwlSchemaGenerator(Generator):
|
|
92
98
|
for prefix in self.metamodel.schema.emit_prefixes:
|
93
99
|
self.graph.bind(prefix, self.metamodel.namespaces[prefix])
|
94
100
|
for pfx in self.schema.prefixes.values():
|
95
|
-
self.graph.namespace_manager.bind(
|
96
|
-
pfx.prefix_prefix, URIRef(pfx.prefix_reference)
|
97
|
-
)
|
101
|
+
self.graph.namespace_manager.bind(pfx.prefix_prefix, URIRef(pfx.prefix_reference))
|
98
102
|
|
99
103
|
self.graph.add((base, RDF.type, OWL.Ontology))
|
100
104
|
self._add_element_properties(base, self.schema)
|
@@ -112,9 +116,7 @@ class OwlSchemaGenerator(Generator):
|
|
112
116
|
# add value placeholder
|
113
117
|
if self.type_objects:
|
114
118
|
# TODO: additional axioms, e.g. String subClassOf hasValue some string
|
115
|
-
self.top_value_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
|
116
|
-
"topValue"
|
117
|
-
]
|
119
|
+
self.top_value_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME]["topValue"]
|
118
120
|
self.graph.add((self.top_value_uri, RDF.type, OWL.DatatypeProperty))
|
119
121
|
self.graph.add((self.top_value_uri, RDFS.label, Literal("value")))
|
120
122
|
|
@@ -198,7 +200,7 @@ class OwlSchemaGenerator(Generator):
|
|
198
200
|
k_curie = k
|
199
201
|
try:
|
200
202
|
k_uri = self.namespaces.uri_for(k_curie)
|
201
|
-
except ValueError
|
203
|
+
except ValueError:
|
202
204
|
try:
|
203
205
|
k_uri = self.metamodel.namespaces.uri_for(k_curie)
|
204
206
|
except ValueError as me:
|
@@ -266,6 +268,13 @@ class OwlSchemaGenerator(Generator):
|
|
266
268
|
cls_uri,
|
267
269
|
)
|
268
270
|
)
|
271
|
+
|
272
|
+
if cls.class_uri and self.assert_equivalent_classes:
|
273
|
+
eq_class_uri = self.namespaces.uri_for(cls.class_uri)
|
274
|
+
if str(eq_class_uri) != cls.definition_uri:
|
275
|
+
self.graph.add((cls_uri, OWL.equivalentClass, eq_class_uri))
|
276
|
+
self.graph.remove((cls_uri, SKOS.exactMatch, eq_class_uri))
|
277
|
+
|
269
278
|
# If defining slots, we generate an equivalentClass entry
|
270
279
|
# equ_node = BNode()
|
271
280
|
# self.graph.add((cls_uri, OWL.equivalentClass, equ_node))
|
@@ -317,7 +326,7 @@ class OwlSchemaGenerator(Generator):
|
|
317
326
|
slot_uri = self.namespaces.uri_for(slot.slot_uri)
|
318
327
|
if slot_uri == "rdf:type":
|
319
328
|
logging.warning(
|
320
|
-
|
329
|
+
"rdflib may have issues serializing rdf:type with turtle serializer"
|
321
330
|
)
|
322
331
|
if slot.required:
|
323
332
|
if slot.multivalued:
|
@@ -346,9 +355,7 @@ class OwlSchemaGenerator(Generator):
|
|
346
355
|
if slot.multivalued:
|
347
356
|
# restriction(slot only type)
|
348
357
|
self.graph.add((slot_node, RDF.type, OWL.Restriction))
|
349
|
-
self.graph.add(
|
350
|
-
(slot_node, OWL.allValuesFrom, self._range_uri(slot))
|
351
|
-
)
|
358
|
+
self.graph.add((slot_node, OWL.allValuesFrom, self._range_uri(slot)))
|
352
359
|
self.graph.add((slot_node, OWL.onProperty, slot_uri))
|
353
360
|
else:
|
354
361
|
# intersectionOf(restriction(slot only type) restriction(slot max 1 type))
|
@@ -370,13 +377,10 @@ class OwlSchemaGenerator(Generator):
|
|
370
377
|
@param slot:
|
371
378
|
@return:
|
372
379
|
"""
|
373
|
-
# determine if this is a slot that has been induced by slot_usage; if so
|
374
|
-
# and should not be used for
|
375
|
-
|
376
|
-
|
377
|
-
and slot.alias != slot.name
|
378
|
-
and slot.alias in self.schema.slots
|
379
|
-
):
|
380
|
+
# determine if this is a slot that has been induced by slot_usage; if so
|
381
|
+
# the meaning of the slot is context-specific and should not be used for
|
382
|
+
# global properties
|
383
|
+
if slot.alias is not None and slot.alias != slot.name and slot.alias in self.schema.slots:
|
380
384
|
logging.debug(
|
381
385
|
f"SKIPPING slot induced by slot_usage: {slot.alias} // {slot.name} // {slot}"
|
382
386
|
)
|
@@ -492,9 +496,7 @@ class OwlSchemaGenerator(Generator):
|
|
492
496
|
g.add(
|
493
497
|
(
|
494
498
|
enum_uri,
|
495
|
-
self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
|
496
|
-
"permissible_values"
|
497
|
-
],
|
499
|
+
self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME]["permissible_values"],
|
498
500
|
pv_uri,
|
499
501
|
)
|
500
502
|
)
|
@@ -512,8 +514,7 @@ class OwlSchemaGenerator(Generator):
|
|
512
514
|
if k in metamodel.schema.slots:
|
513
515
|
defining_slot = self.metamodel.schema.slots[k]
|
514
516
|
if v is not None and (
|
515
|
-
"owl" in defining_slot.in_subset
|
516
|
-
or "OwlProfile" in defining_slot.in_subset
|
517
|
+
"owl" in defining_slot.in_subset or "OwlProfile" in defining_slot.in_subset
|
517
518
|
):
|
518
519
|
if isinstance(v, list):
|
519
520
|
ve = v
|
@@ -532,18 +533,12 @@ class OwlSchemaGenerator(Generator):
|
|
532
533
|
),
|
533
534
|
):
|
534
535
|
return
|
535
|
-
if (
|
536
|
-
k == "name"
|
537
|
-
and isinstance(el, SlotDefinition)
|
538
|
-
and el.alias is not None
|
539
|
-
):
|
536
|
+
if k == "name" and isinstance(el, SlotDefinition) and el.alias is not None:
|
540
537
|
prop_uri = RDFS.label
|
541
538
|
e = el.alias
|
542
539
|
else:
|
543
540
|
prop_uri = URIRef(
|
544
|
-
self.metamodel.namespaces.uri_for(
|
545
|
-
defining_slot.slot_uri
|
546
|
-
)
|
541
|
+
self.metamodel.namespaces.uri_for(defining_slot.slot_uri)
|
547
542
|
)
|
548
543
|
object = self._as_rdf_element(e, defining_slot)
|
549
544
|
if object is not None:
|
@@ -568,7 +563,7 @@ class OwlSchemaGenerator(Generator):
|
|
568
563
|
elif element_name in self.metamodel.schema.enums:
|
569
564
|
return self._enum_uri(element_name)
|
570
565
|
else:
|
571
|
-
logging.warning(f"Unknown range {
|
566
|
+
logging.warning(f"Unknown range {parent_slot.range}")
|
572
567
|
return None
|
573
568
|
|
574
569
|
def _range_is_datatype(self, slot: SlotDefinition) -> bool:
|
@@ -613,9 +608,7 @@ class OwlSchemaGenerator(Generator):
|
|
613
608
|
|
614
609
|
def _add_metamodel_class(self, cname: str) -> None:
|
615
610
|
metac = self.metamodel.schema.classes[cname]
|
616
|
-
metac_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
|
617
|
-
camelcase(metac.name)
|
618
|
-
]
|
611
|
+
metac_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][camelcase(metac.name)]
|
619
612
|
self.graph.add((metac_uri, RDF.type, OWL.Class))
|
620
613
|
# self._add_element_properties(metac_uri, metac)
|
621
614
|
|
@@ -679,6 +672,12 @@ class OwlSchemaGenerator(Generator):
|
|
679
672
|
show_default=True,
|
680
673
|
help="Suffix to append to schema id to generate OWL Ontology IRI",
|
681
674
|
)
|
675
|
+
@click.option(
|
676
|
+
"--assert-equivalent-classes/--no-assert-equivalent-classes",
|
677
|
+
default=False,
|
678
|
+
show_default=True,
|
679
|
+
help="If true, add owl:equivalentClass between a class and a class_uri",
|
680
|
+
)
|
682
681
|
@click.version_option(__version__, "-V", "--version")
|
683
682
|
def cli(yamlfile, metadata_profile: str, **kwargs):
|
684
683
|
"""Generate an OWL representation of a LinkML model
|
@@ -700,9 +699,9 @@ def cli(yamlfile, metadata_profile: str, **kwargs):
|
|
700
699
|
else:
|
701
700
|
metadata_profile = MetadataProfile.linkml
|
702
701
|
print(
|
703
|
-
OwlSchemaGenerator(
|
704
|
-
|
705
|
-
)
|
702
|
+
OwlSchemaGenerator(yamlfile, metadata_profile=metadata_profile, **kwargs).serialize(
|
703
|
+
**kwargs
|
704
|
+
)
|
706
705
|
)
|
707
706
|
|
708
707
|
|