linkml 1.8.0rc2__py3-none-any.whl → 1.8.2__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/cli/__init__.py +0 -0
- linkml/cli/__main__.py +4 -0
- linkml/cli/main.py +126 -0
- linkml/generators/common/build.py +105 -0
- linkml/generators/common/lifecycle.py +124 -0
- linkml/generators/common/template.py +89 -0
- linkml/generators/csvgen.py +1 -1
- linkml/generators/docgen/slot.md.jinja2 +4 -0
- linkml/generators/docgen.py +1 -1
- linkml/generators/dotgen.py +1 -1
- linkml/generators/erdiagramgen.py +1 -1
- linkml/generators/excelgen.py +1 -1
- linkml/generators/golanggen.py +1 -1
- linkml/generators/golrgen.py +1 -1
- linkml/generators/graphqlgen.py +1 -1
- linkml/generators/javagen.py +1 -1
- linkml/generators/jsonldcontextgen.py +4 -4
- linkml/generators/jsonldgen.py +1 -1
- linkml/generators/jsonschemagen.py +80 -21
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +1 -1
- linkml/generators/namespacegen.py +1 -1
- linkml/generators/oocodegen.py +2 -1
- linkml/generators/owlgen.py +1 -1
- linkml/generators/plantumlgen.py +24 -7
- linkml/generators/prefixmapgen.py +1 -1
- linkml/generators/projectgen.py +1 -1
- linkml/generators/protogen.py +1 -1
- linkml/generators/pydanticgen/__init__.py +9 -3
- linkml/generators/pydanticgen/array.py +114 -194
- linkml/generators/pydanticgen/build.py +64 -25
- linkml/generators/pydanticgen/includes.py +4 -28
- linkml/generators/pydanticgen/pydanticgen.py +635 -283
- linkml/generators/pydanticgen/template.py +153 -180
- linkml/generators/pydanticgen/templates/attribute.py.jinja +9 -7
- linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -13
- linkml/generators/pydanticgen/templates/class.py.jinja +2 -2
- linkml/generators/pydanticgen/templates/footer.py.jinja +2 -10
- linkml/generators/pydanticgen/templates/module.py.jinja +3 -3
- linkml/generators/pydanticgen/templates/validator.py.jinja +0 -4
- linkml/generators/pythongen.py +12 -2
- linkml/generators/rdfgen.py +1 -1
- linkml/generators/shaclgen.py +6 -2
- linkml/generators/shexgen.py +1 -1
- linkml/generators/sparqlgen.py +1 -1
- linkml/generators/sqlalchemygen.py +1 -1
- linkml/generators/sqltablegen.py +1 -1
- linkml/generators/sssomgen.py +1 -1
- linkml/generators/summarygen.py +1 -1
- linkml/generators/terminusdbgen.py +7 -4
- linkml/generators/typescriptgen.py +1 -1
- linkml/generators/yamlgen.py +1 -1
- linkml/generators/yumlgen.py +1 -1
- linkml/linter/cli.py +1 -1
- linkml/transformers/logical_model_transformer.py +117 -18
- linkml/utils/converter.py +1 -1
- linkml/utils/execute_tutorial.py +2 -0
- linkml/utils/logictools.py +142 -29
- linkml/utils/schema_builder.py +7 -6
- linkml/utils/schema_fixer.py +1 -1
- linkml/utils/sqlutils.py +1 -1
- linkml/validator/cli.py +4 -1
- linkml/validators/jsonschemavalidator.py +1 -1
- linkml/validators/sparqlvalidator.py +1 -1
- linkml/workspaces/example_runner.py +1 -1
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/METADATA +2 -2
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/RECORD +70 -64
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/entry_points.txt +1 -1
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/LICENSE +0 -0
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/WHEEL +0 -0
linkml/generators/shaclgen.py
CHANGED
@@ -11,7 +11,7 @@ from linkml_runtime.utils.schemaview import SchemaView
|
|
11
11
|
from linkml_runtime.utils.yamlutils import TypedNode, extended_float, extended_int, extended_str
|
12
12
|
from rdflib import BNode, Graph, Literal, URIRef
|
13
13
|
from rdflib.collection import Collection
|
14
|
-
from rdflib.namespace import RDF, SH, XSD
|
14
|
+
from rdflib.namespace import RDF, RDFS, SH, XSD
|
15
15
|
|
16
16
|
from linkml._version import __version__
|
17
17
|
from linkml.generators.shacl.ifabsent_processor import IfAbsentProcessor
|
@@ -73,6 +73,10 @@ class ShaclGenerator(Generator):
|
|
73
73
|
class_uri_with_suffix += self.suffix
|
74
74
|
shape_pv(RDF.type, SH.NodeShape)
|
75
75
|
shape_pv(SH.targetClass, class_uri) # TODO
|
76
|
+
|
77
|
+
if c.is_a:
|
78
|
+
shape_pv(RDFS.subClassOf, URIRef(sv.get_uri(c.is_a, expand=True)))
|
79
|
+
|
76
80
|
if self.closed:
|
77
81
|
if c.mixin or c.abstract:
|
78
82
|
shape_pv(SH.closed, Literal(False))
|
@@ -303,7 +307,7 @@ def add_simple_data_type(func: Callable, r: ElementName) -> None:
|
|
303
307
|
|
304
308
|
|
305
309
|
@shared_arguments(ShaclGenerator)
|
306
|
-
@click.command()
|
310
|
+
@click.command(name="shacl")
|
307
311
|
@click.option(
|
308
312
|
"--closed/--non-closed",
|
309
313
|
default=True,
|
linkml/generators/shexgen.py
CHANGED
@@ -223,7 +223,7 @@ class ShExGenerator(Generator):
|
|
223
223
|
|
224
224
|
|
225
225
|
@shared_arguments(ShExGenerator)
|
226
|
-
@click.command()
|
226
|
+
@click.command(name="shex")
|
227
227
|
@click.option("-o", "--output", help="Output file name")
|
228
228
|
@click.version_option(__version__, "-V", "--version")
|
229
229
|
def cli(yamlfile, **args):
|
linkml/generators/sparqlgen.py
CHANGED
@@ -188,7 +188,7 @@ class SparqlGenerator(Generator):
|
|
188
188
|
|
189
189
|
|
190
190
|
@shared_arguments(SparqlGenerator)
|
191
|
-
@click.command()
|
191
|
+
@click.command(name="sparql")
|
192
192
|
@click.option("--dir", "-d", help="Directory in which queries will be deposited")
|
193
193
|
@click.version_option(__version__, "-V", "--version")
|
194
194
|
def cli(yamlfile, dir, **kwargs):
|
@@ -216,7 +216,7 @@ class SQLAlchemyGenerator(Generator):
|
|
216
216
|
help="Emit FK declarations",
|
217
217
|
)
|
218
218
|
@click.version_option(__version__, "-V", "--version")
|
219
|
-
@click.command()
|
219
|
+
@click.command(name="sqla")
|
220
220
|
def cli(yamlfile, declarative, generate_classes, pydantic, use_foreign_keys=True, **args):
|
221
221
|
"""Generate SQL DDL representation"""
|
222
222
|
if pydantic:
|
linkml/generators/sqltablegen.py
CHANGED
linkml/generators/sssomgen.py
CHANGED
@@ -180,7 +180,7 @@ class SSSOMGenerator(Generator):
|
|
180
180
|
|
181
181
|
|
182
182
|
@shared_arguments(SSSOMGenerator)
|
183
|
-
@click.command()
|
183
|
+
@click.command(name="sssom")
|
184
184
|
@click.option("-o", "--output", help="Output file name")
|
185
185
|
@click.version_option(__version__, "-V", "--version")
|
186
186
|
def cli(yamlfile, **kwargs):
|
linkml/generators/summarygen.py
CHANGED
@@ -88,7 +88,7 @@ class SummaryGenerator(Generator):
|
|
88
88
|
|
89
89
|
@shared_arguments(SummaryGenerator)
|
90
90
|
@click.version_option(__version__, "-V", "--version")
|
91
|
-
@click.command()
|
91
|
+
@click.command(name="summary")
|
92
92
|
def cli(yamlfile, **args):
|
93
93
|
"""Generate TSV summary files for viewing in Excel and the like"""
|
94
94
|
print(SummaryGenerator(yamlfile, **args).serialize(**args))
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
|
+
import warnings
|
3
4
|
from dataclasses import dataclass
|
4
5
|
from typing import List
|
5
6
|
|
@@ -10,9 +11,7 @@ from linkml_runtime.utils.formatutils import be, camelcase, underscore
|
|
10
11
|
try:
|
11
12
|
from terminusdb_client.woqlquery import WOQLQuery as WQ
|
12
13
|
except ImportError:
|
13
|
-
|
14
|
-
|
15
|
-
warnings.warn("terminusdb_client is not a requirement of this package, please install it separately")
|
14
|
+
WQ = None
|
16
15
|
|
17
16
|
from linkml._version import __version__
|
18
17
|
from linkml.utils.generator import Generator, shared_arguments
|
@@ -67,6 +66,10 @@ class TerminusdbGenerator(Generator):
|
|
67
66
|
raw_additions: List = None
|
68
67
|
clswq: str = None
|
69
68
|
|
69
|
+
def __post_init__(self):
|
70
|
+
if WQ is None:
|
71
|
+
warnings.warn("terminusdb_client is not a requirement of this package, please install it separately")
|
72
|
+
|
70
73
|
def visit_schema(self, inline: bool = False, **kwargs) -> None:
|
71
74
|
self.classes = []
|
72
75
|
self.raw_additions = []
|
@@ -134,7 +137,7 @@ class TerminusdbGenerator(Generator):
|
|
134
137
|
|
135
138
|
@shared_arguments(TerminusdbGenerator)
|
136
139
|
@click.version_option(__version__, "-V", "--version")
|
137
|
-
@click.command()
|
140
|
+
@click.command(name="terminusdb")
|
138
141
|
def cli(yamlfile, **args):
|
139
142
|
"""Generate graphql representation of a LinkML model"""
|
140
143
|
print(TerminusdbGenerator(yamlfile, **args).serialize(**args))
|
@@ -264,7 +264,7 @@ class TypescriptGenerator(OOCodeGenerator):
|
|
264
264
|
@click.version_option(__version__, "-V", "--version")
|
265
265
|
@click.option("--gen-type-utils/", "-u", help="Generate Type checking utils", is_flag=True)
|
266
266
|
@click.option("--include-induced-slots/", help="Generate slots induced through inheritance", is_flag=True)
|
267
|
-
@click.command()
|
267
|
+
@click.command(name="typescript")
|
268
268
|
def cli(yamlfile, gen_type_utils=False, include_induced_slots=False, **args):
|
269
269
|
"""Generate typescript interfaces and types
|
270
270
|
|
linkml/generators/yamlgen.py
CHANGED
linkml/generators/yumlgen.py
CHANGED
linkml/linter/cli.py
CHANGED
@@ -33,7 +33,7 @@ def get_yaml_files(root: Path, accept_dot_files: bool) -> Iterable[str]:
|
|
33
33
|
yield str(path)
|
34
34
|
|
35
35
|
|
36
|
-
@click.command()
|
36
|
+
@click.command(name="lint")
|
37
37
|
@click.argument(
|
38
38
|
"schema",
|
39
39
|
type=click.Path(exists=True, dir_okay=True, file_okay=True, resolve_path=True, path_type=Path),
|
@@ -1,23 +1,67 @@
|
|
1
1
|
"""
|
2
|
-
A model transformer that transforms a schema into logical form
|
2
|
+
A model transformer that transforms a schema into logical form.
|
3
3
|
|
4
4
|
Formally, this transformer generates a logical model of the schema in which inheritance
|
5
|
-
of slots/attributes is replaced by boolean conjunctions.
|
5
|
+
of slots/attributes is replaced by boolean conjunctions, and the final form is simplified.
|
6
|
+
|
7
|
+
For example, given a slot `s`:
|
8
|
+
|
9
|
+
```
|
10
|
+
slots:
|
11
|
+
s:
|
12
|
+
range: integer
|
13
|
+
minimum_value: 1
|
14
|
+
```
|
15
|
+
|
16
|
+
Which is reused and refined by C and C (where C is_a D):
|
17
|
+
|
18
|
+
```
|
19
|
+
classes:
|
20
|
+
D:
|
21
|
+
slots:
|
22
|
+
- s
|
23
|
+
slot_usage:
|
24
|
+
s:
|
25
|
+
minimum_value: 2
|
26
|
+
C:
|
27
|
+
is_a: D
|
28
|
+
slot_usage:
|
29
|
+
s:
|
30
|
+
minimum_value: 3
|
31
|
+
```
|
32
|
+
|
33
|
+
The hierarchy unrolling step will create an attribute `s` for C that is the conjunction of usages for that
|
34
|
+
class, ancestors, and the base slot:
|
35
|
+
|
36
|
+
```
|
37
|
+
s:
|
38
|
+
range: integer
|
39
|
+
all_of:
|
40
|
+
- minimum_value: 1
|
41
|
+
- minimum_value: 2
|
42
|
+
- minimum_value: 3
|
43
|
+
```
|
44
|
+
|
45
|
+
This is then simplified using symbolic reasoning to:
|
46
|
+
|
47
|
+
```
|
48
|
+
s:
|
49
|
+
range: integer
|
50
|
+
minimum_value: 3
|
51
|
+
```
|
52
|
+
|
53
|
+
See logictools.py for the symbolic reasoning engine.
|
54
|
+
|
6
55
|
|
7
|
-
For example, given a slot s and two classes C and D, where C is_a D. If both C and D
|
8
|
-
place constraints on s (for example, C may refine the range, or make more restricted
|
9
|
-
value constraints), then an attribute is generated for C that for which the conjunction
|
10
|
-
of constraints in both classes hold.
|
11
56
|
|
12
|
-
These logical constraints are then simplified by translating to disjunctive normal form,
|
13
|
-
and applying simplification rules.
|
14
57
|
"""
|
15
58
|
|
16
59
|
import logging
|
17
60
|
from copy import deepcopy
|
18
61
|
from dataclasses import dataclass
|
19
|
-
from typing import Any, Callable, Dict, Iterator, List, Union
|
62
|
+
from typing import Any, Callable, Dict, Iterator, List, Optional, Union
|
20
63
|
|
64
|
+
from linkml_runtime import SchemaView
|
21
65
|
from linkml_runtime.dumpers import json_dumper
|
22
66
|
from linkml_runtime.linkml_model import ClassDefinitionName, SchemaDefinition
|
23
67
|
from linkml_runtime.linkml_model.meta import (
|
@@ -37,6 +81,8 @@ HERITABLE_METASLOT = [
|
|
37
81
|
"range_expression",
|
38
82
|
"minimum_value",
|
39
83
|
"maximum_value",
|
84
|
+
"minimum_cardinality",
|
85
|
+
"maximum_cardinality",
|
40
86
|
"pattern",
|
41
87
|
"structured_pattern",
|
42
88
|
"required",
|
@@ -64,6 +110,8 @@ DIRECT_ROLL_DOWN = ["multivalued", "key", "identifier", "designates_type"]
|
|
64
110
|
HERITABLE_TYPE_METASLOT = [
|
65
111
|
"minimum_value",
|
66
112
|
"maximum_value",
|
113
|
+
# "minimum_cardinality",
|
114
|
+
# "maximum_cardinality",
|
67
115
|
"pattern",
|
68
116
|
"structured_pattern",
|
69
117
|
"all_of",
|
@@ -103,6 +151,7 @@ class LogicalModelTransformer(ModelTransformer):
|
|
103
151
|
>>> _ = sb.add_class("Person", slots={"age": {"range": "integer"}}, is_a="Thing")
|
104
152
|
>>> _ = sb.add_class("Organization", slots={"category": {"range": "OrganizationType"}}, is_a="Thing")
|
105
153
|
>>> _ = sb.add_enum("OrganizationType", ["commercial", "non-profit"])
|
154
|
+
>>> _ = sb.add_defaults()
|
106
155
|
>>> from linkml.transformers.logical_model_transformer import LogicalModelTransformer
|
107
156
|
>>> tr = LogicalModelTransformer()
|
108
157
|
>>> tr.set_schema(sb.schema)
|
@@ -113,8 +162,10 @@ class LogicalModelTransformer(ModelTransformer):
|
|
113
162
|
attributes:
|
114
163
|
id:
|
115
164
|
name: id
|
165
|
+
range: string
|
116
166
|
name:
|
117
167
|
name: name
|
168
|
+
range: string
|
118
169
|
age:
|
119
170
|
name: age
|
120
171
|
range: integer
|
@@ -142,6 +193,10 @@ class LogicalModelTransformer(ModelTransformer):
|
|
142
193
|
|
143
194
|
...
|
144
195
|
|
196
|
+
This has "compiled away" the hierarchy. The type of the values of 'entities' for Container can
|
197
|
+
be checked against the any_of expression, without needing to know the full class hierarchy.
|
198
|
+
This is useful when compiling to a framework that does not support inheritance.
|
199
|
+
|
145
200
|
If you are compiling to a framework that does have inheritance, then you can specify that these are
|
146
201
|
preserved:
|
147
202
|
|
@@ -173,6 +228,9 @@ class LogicalModelTransformer(ModelTransformer):
|
|
173
228
|
name: entities2
|
174
229
|
multivalued: true
|
175
230
|
any_of:
|
231
|
+
- range: Organization
|
232
|
+
- range: Person
|
233
|
+
|
176
234
|
...
|
177
235
|
|
178
236
|
Note that defaults are always applied after reasoning. So if you set a default_range to
|
@@ -211,6 +269,10 @@ class LogicalModelTransformer(ModelTransformer):
|
|
211
269
|
minimum_value: 40
|
212
270
|
maximum_value: 60
|
213
271
|
|
272
|
+
Here the slot-level constraints seem to have been "overridden". In fact, LinkML is monotonic, and there
|
273
|
+
is no overriding. In fact the constraints have been combined as a conjunction (And), and then the resulting
|
274
|
+
entailed attribute has been *simplified* (age >= 40 & age >= 0 ==> age >= 40).
|
275
|
+
|
214
276
|
The reasoning engine can also detect unsatisfiable constraints:
|
215
277
|
|
216
278
|
>>> from linkml.transformers.logical_model_transformer import UnsatisfiableAttribute
|
@@ -227,6 +289,9 @@ class LogicalModelTransformer(ModelTransformer):
|
|
227
289
|
Let's get rid of the problematic class:
|
228
290
|
|
229
291
|
>>> del sb.schema.classes["YoungAdult"]
|
292
|
+
|
293
|
+
And try a different example - we will try and override the range of the age slot with a string:
|
294
|
+
|
230
295
|
>>> _ = sb.add_class("Person2", is_a="Person", slot_usage={"age": {"range": "string"}})
|
231
296
|
>>> tr.set_schema(sb.schema)
|
232
297
|
>>> try:
|
@@ -237,8 +302,6 @@ class LogicalModelTransformer(ModelTransformer):
|
|
237
302
|
Attribute Person2.age is unsatisfiable
|
238
303
|
<BLANKLINE>
|
239
304
|
|
240
|
-
|
241
|
-
|
242
305
|
"""
|
243
306
|
|
244
307
|
preserve_class_is_a: bool = False
|
@@ -277,16 +340,24 @@ class LogicalModelTransformer(ModelTransformer):
|
|
277
340
|
a lack of range assignment.
|
278
341
|
"""
|
279
342
|
|
280
|
-
|
343
|
+
reason_over_metamodel_slots: Optional[List[str]] = None
|
344
|
+
"""
|
345
|
+
If set, only reason over the specified metamodel slots.
|
346
|
+
"""
|
347
|
+
|
348
|
+
def transform(self, tgt_schema_name: str = None, simplify=True, force_any_of=False, **kwargs) -> Any:
|
281
349
|
"""
|
282
|
-
Transform the schema
|
350
|
+
Transform the schema using logical reasoning, materializing entailed simple attributes.
|
283
351
|
|
284
352
|
:param tgt_schema_name:
|
285
353
|
:param simplify: If True (default), simplify the schema after transformation
|
354
|
+
:param force_any_of: If True, force the use of any_of expressions for range, even for singletons
|
286
355
|
:return:
|
287
356
|
"""
|
288
357
|
sv = self.schemaview
|
289
|
-
|
358
|
+
target_schemaview = SchemaView(deepcopy(self.source_schema))
|
359
|
+
target_schemaview.merge_imports()
|
360
|
+
target_schema = target_schemaview.schema
|
290
361
|
for tn, typ in target_schema.types.items():
|
291
362
|
ancs = sv.type_ancestors(tn, reflexive=False)
|
292
363
|
logging.debug(f"Unrolling type {tn}, merging {len(ancs)}")
|
@@ -312,7 +383,7 @@ class LogicalModelTransformer(ModelTransformer):
|
|
312
383
|
self._roll_down(target_schema, cn, ancs)
|
313
384
|
self.apply_defaults(target_schema)
|
314
385
|
if simplify:
|
315
|
-
self.simplify(target_schema)
|
386
|
+
self.simplify(target_schema, force_any_of=force_any_of)
|
316
387
|
if self.tidy_slots:
|
317
388
|
target_schema.slots = {}
|
318
389
|
if self.tidy_inheritance:
|
@@ -366,7 +437,8 @@ class LogicalModelTransformer(ModelTransformer):
|
|
366
437
|
:return:
|
367
438
|
"""
|
368
439
|
new_slot_dict = {}
|
369
|
-
|
440
|
+
heritable_metaslots = self.reason_over_metamodel_slots or HERITABLE_METASLOT
|
441
|
+
for p in heritable_metaslots:
|
370
442
|
pv = getattr(source, p)
|
371
443
|
if pv is not None and pv != [] and pv != {} and pv is not False:
|
372
444
|
new_slot_dict[p] = pv
|
@@ -377,7 +449,7 @@ class LogicalModelTransformer(ModelTransformer):
|
|
377
449
|
sv = self.schemaview
|
378
450
|
if source.range in sv.all_types():
|
379
451
|
typ = sv.get_type(source.range)
|
380
|
-
for p in set(HERITABLE_TYPE_METASLOT).intersection(
|
452
|
+
for p in set(HERITABLE_TYPE_METASLOT).intersection(heritable_metaslots):
|
381
453
|
pv = getattr(typ, p)
|
382
454
|
if pv is not None and pv != [] and pv != {} and pv is not False:
|
383
455
|
new_slot_dict[p] = pv
|
@@ -395,6 +467,9 @@ class LogicalModelTransformer(ModelTransformer):
|
|
395
467
|
"""
|
396
468
|
new_slot_dict = {}
|
397
469
|
for p in HERITABLE_TYPE_METASLOT:
|
470
|
+
if p in ["repr"]:
|
471
|
+
# not on anonymous types
|
472
|
+
continue
|
398
473
|
pv = getattr(source, p)
|
399
474
|
if pv is not None and pv != [] and pv != {} and pv is not False:
|
400
475
|
new_slot_dict[p] = pv
|
@@ -431,7 +506,7 @@ class LogicalModelTransformer(ModelTransformer):
|
|
431
506
|
for sx in att.all_of + att.exactly_one_of + att.none_of + att.any_of:
|
432
507
|
yield from self._collection_slot_expressions(sx, filter_function)
|
433
508
|
|
434
|
-
def simplify(self, target_schema: SchemaDefinition):
|
509
|
+
def simplify(self, target_schema: SchemaDefinition, force_any_of=False):
|
435
510
|
for cls in target_schema.classes.values():
|
436
511
|
for att in cls.attributes.values():
|
437
512
|
x = self._as_logical_expression(att)
|
@@ -441,6 +516,9 @@ class LogicalModelTransformer(ModelTransformer):
|
|
441
516
|
if logictools.is_contradiction(x):
|
442
517
|
raise UnsatisfiableAttribute(f"Attribute {cls.name}.{att.name} is unsatisfiable")
|
443
518
|
self._simplify_member_ofs(x)
|
519
|
+
if force_any_of:
|
520
|
+
if not isinstance(x, logictools.Or):
|
521
|
+
x = logictools.Or(x)
|
444
522
|
logger.debug(f"Simplified member of: {x}")
|
445
523
|
simplified_att = self._from_logical_expression(x)
|
446
524
|
for k, v in simplified_att.__dict__.items():
|
@@ -524,6 +602,10 @@ class LogicalModelTransformer(ModelTransformer):
|
|
524
602
|
def _value_var(self) -> logictools.Variable:
|
525
603
|
return logictools.Variable("value")
|
526
604
|
|
605
|
+
@property
|
606
|
+
def _length_var(self) -> logictools.Variable:
|
607
|
+
return logictools.Variable("length")
|
608
|
+
|
527
609
|
@property
|
528
610
|
def _type_var(self) -> logictools.Variable:
|
529
611
|
return logictools.Variable("type")
|
@@ -535,10 +617,15 @@ class LogicalModelTransformer(ModelTransformer):
|
|
535
617
|
exprs = []
|
536
618
|
value_var = self._value_var
|
537
619
|
type_var = self._type_var
|
620
|
+
length_var = self._length_var
|
538
621
|
if slot_expression.minimum_value is not None:
|
539
622
|
exprs.append(value_var >= slot_expression.minimum_value)
|
540
623
|
if slot_expression.maximum_value is not None:
|
541
624
|
exprs.append(value_var <= slot_expression.maximum_value)
|
625
|
+
if slot_expression.minimum_cardinality is not None:
|
626
|
+
exprs.append(length_var >= slot_expression.minimum_cardinality)
|
627
|
+
if slot_expression.maximum_cardinality is not None:
|
628
|
+
exprs.append(length_var <= slot_expression.maximum_cardinality)
|
542
629
|
if slot_expression.pattern is not None:
|
543
630
|
exprs.append(logictools.Term(MATCHES_PREDICATE, value_var, slot_expression.pattern))
|
544
631
|
if slot_expression.range:
|
@@ -576,6 +663,7 @@ class LogicalModelTransformer(ModelTransformer):
|
|
576
663
|
"none_of",
|
577
664
|
"any_of",
|
578
665
|
]:
|
666
|
+
# already accounted for
|
579
667
|
continue
|
580
668
|
v = getattr(slot_expression, p, None)
|
581
669
|
if v is not None and v != [] and v != {}:
|
@@ -624,6 +712,17 @@ class LogicalModelTransformer(ModelTransformer):
|
|
624
712
|
return AnonymousSlotExpression(maximum_value=val - 1)
|
625
713
|
else:
|
626
714
|
raise ValueError(f"Unknown predicate {expr.predicate} for {var}")
|
715
|
+
elif var == self._length_var:
|
716
|
+
if expr.predicate == ">=":
|
717
|
+
return AnonymousSlotExpression(minimum_cardinality=val)
|
718
|
+
elif expr.predicate == ">":
|
719
|
+
return AnonymousSlotExpression(minimum_cardinality=val + 1)
|
720
|
+
elif expr.predicate == "<=":
|
721
|
+
return AnonymousSlotExpression(maximum_cardinality=val)
|
722
|
+
elif expr.predicate == "<":
|
723
|
+
return AnonymousSlotExpression(maximum_cardinality=val - 1)
|
724
|
+
else:
|
725
|
+
raise ValueError(f"Unknown predicate {expr.predicate} for {var}")
|
627
726
|
elif var == self._type_var:
|
628
727
|
if expr.predicate == "in":
|
629
728
|
if val == logictools.UniversalSet():
|
linkml/utils/converter.py
CHANGED
linkml/utils/execute_tutorial.py
CHANGED
@@ -189,6 +189,8 @@ def parse_file_to_blocks(input) -> List[Block]:
|
|
189
189
|
@click.version_option(__version__, "-V", "--version")
|
190
190
|
def cli(inputs, directory):
|
191
191
|
"""
|
192
|
+
Execute a tutorial markdown file (eg. those in the /docs/intro/ directory) and
|
193
|
+
save the outputs in the given directory
|
192
194
|
|
193
195
|
Example:
|
194
196
|
|