linkml 1.7.8__py3-none-any.whl → 1.7.10__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/generators/csvgen.py +15 -5
- linkml/generators/docgen/class_diagram.md.jinja2 +19 -4
- linkml/generators/docgen.py +59 -14
- linkml/generators/graphqlgen.py +14 -16
- linkml/generators/jsonldcontextgen.py +3 -3
- linkml/generators/jsonldgen.py +3 -2
- linkml/generators/jsonschemagen.py +9 -0
- linkml/generators/markdowngen.py +341 -301
- linkml/generators/owlgen.py +87 -20
- linkml/generators/plantumlgen.py +9 -8
- linkml/generators/prefixmapgen.py +15 -23
- linkml/generators/protogen.py +23 -18
- linkml/generators/pydanticgen/pydanticgen.py +11 -2
- linkml/generators/pythongen.py +2 -5
- linkml/generators/rdfgen.py +5 -4
- linkml/generators/shaclgen.py +8 -6
- linkml/generators/shexgen.py +9 -7
- linkml/generators/summarygen.py +8 -2
- linkml/generators/terminusdbgen.py +2 -2
- linkml/generators/yumlgen.py +2 -2
- linkml/utils/__init__.py +3 -0
- linkml/utils/deprecation.py +255 -0
- linkml/utils/generator.py +78 -56
- linkml/validator/cli.py +11 -0
- linkml/validator/plugins/jsonschema_validation_plugin.py +2 -0
- linkml/validator/report.py +1 -0
- linkml/workspaces/example_runner.py +2 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/METADATA +1 -1
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/RECORD +32 -31
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/LICENSE +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/WHEEL +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
"""
|
2
|
+
Utilities for deprecating functionality and dependencies.
|
3
|
+
|
4
|
+
- Emitting DeprecationWarnings
|
5
|
+
- Tracking deprecated and removed in versions
|
6
|
+
- Fail tests when something marked as removed_in is still present in the specified version
|
7
|
+
|
8
|
+
Initial draft for deprecating Pydantic 1, to make more general, needs
|
9
|
+
- function wrapper version
|
10
|
+
- ...
|
11
|
+
|
12
|
+
To deprecate something:
|
13
|
+
|
14
|
+
- Create a :class:`.Deprecation` object within the `DEPRECATIONS` tuple
|
15
|
+
- Use the :func:`.deprecation_warning` function wherever the deprecated feature would be used to emit the warning
|
16
|
+
|
17
|
+
"""
|
18
|
+
|
19
|
+
import re
|
20
|
+
import warnings
|
21
|
+
from dataclasses import dataclass
|
22
|
+
from importlib.metadata import version
|
23
|
+
from typing import Optional
|
24
|
+
|
25
|
+
# Stolen from https://github.com/pypa/packaging/blob/main/src/packaging/version.py
|
26
|
+
# Updated to include major, minor, and patch versions
|
27
|
+
PEP440_PATTERN = r"""
|
28
|
+
v?
|
29
|
+
(?:
|
30
|
+
(?:(?P<epoch>[0-9]+)!)? # epoch
|
31
|
+
(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)
|
32
|
+
(?P<pre> # pre-release
|
33
|
+
[-_\.]?
|
34
|
+
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
35
|
+
[-_\.]?
|
36
|
+
(?P<pre_n>[0-9]+)?
|
37
|
+
)?
|
38
|
+
(?P<post> # post release
|
39
|
+
(?:-(?P<post_n1>[0-9]+))
|
40
|
+
|
|
41
|
+
(?:
|
42
|
+
[-_\.]?
|
43
|
+
(?P<post_l>post|rev|r)
|
44
|
+
[-_\.]?
|
45
|
+
(?P<post_n2>[0-9]+)?
|
46
|
+
)
|
47
|
+
)?
|
48
|
+
(?P<dev> # dev release
|
49
|
+
[-_\.]?
|
50
|
+
(?P<dev_l>dev)
|
51
|
+
[-_\.]?
|
52
|
+
(?P<dev_n>[0-9]+)?
|
53
|
+
)?
|
54
|
+
)
|
55
|
+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
56
|
+
"""
|
57
|
+
PEP440 = re.compile(r"^\s*" + PEP440_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
58
|
+
|
59
|
+
|
60
|
+
@dataclass
|
61
|
+
class SemVer:
|
62
|
+
"""
|
63
|
+
Representation of semantic version that supports inequality comparisons.
|
64
|
+
|
65
|
+
.. note::
|
66
|
+
|
67
|
+
The inequality methods _only_ test the numeric major, minor, and patch components
|
68
|
+
of the version - ie. they do not evaluate the prerelease versions as described in the semver
|
69
|
+
spec. This is not intended to be a general SemVer inequality calculator, but used
|
70
|
+
only for testing deprecations
|
71
|
+
|
72
|
+
"""
|
73
|
+
|
74
|
+
major: int = 0
|
75
|
+
minor: int = 0
|
76
|
+
patch: int = 0
|
77
|
+
epoch: Optional[int] = None
|
78
|
+
pre: Optional[str] = None
|
79
|
+
pre_l: Optional[str] = None
|
80
|
+
pre_n: Optional[str] = None
|
81
|
+
post: Optional[str] = None
|
82
|
+
post_n1: Optional[str] = None
|
83
|
+
post_l: Optional[str] = None
|
84
|
+
post_n2: Optional[str] = None
|
85
|
+
dev: Optional[str] = None
|
86
|
+
dev_l: Optional[str] = None
|
87
|
+
dev_n: Optional[str] = None
|
88
|
+
local: Optional[str] = None
|
89
|
+
|
90
|
+
def __post_init__(self):
|
91
|
+
self.major = int(self.major)
|
92
|
+
self.minor = int(self.minor)
|
93
|
+
self.patch = int(self.patch)
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def from_str(cls, v: str) -> Optional["SemVer"]:
|
97
|
+
"""
|
98
|
+
Create a SemVer from a string using `PEP 440 <https://peps.python.org/pep-0440/>`_
|
99
|
+
syntax.
|
100
|
+
|
101
|
+
Examples:
|
102
|
+
|
103
|
+
.. code-block:: python
|
104
|
+
|
105
|
+
>>> version = SemVer.from_str("v0.1.0")
|
106
|
+
>>> print(version)
|
107
|
+
0.1.0
|
108
|
+
|
109
|
+
"""
|
110
|
+
match = PEP440.search(v)
|
111
|
+
if match is None:
|
112
|
+
return None
|
113
|
+
return SemVer(**match.groupdict())
|
114
|
+
|
115
|
+
@classmethod
|
116
|
+
def from_package(cls, package: str) -> "SemVer":
|
117
|
+
"""Get semver from package name"""
|
118
|
+
v = version(package)
|
119
|
+
return SemVer.from_str(v)
|
120
|
+
|
121
|
+
def __eq__(self, other: "SemVer"):
|
122
|
+
return self.major == other.major and self.minor == other.minor and self.patch == other.patch
|
123
|
+
|
124
|
+
def __lt__(self, other: "SemVer"):
|
125
|
+
# fall through each if elif only if version component is equal
|
126
|
+
for field in ("major", "minor", "patch"):
|
127
|
+
if getattr(self, field) < getattr(other, field):
|
128
|
+
return True
|
129
|
+
elif getattr(self, field) > getattr(other, field):
|
130
|
+
return False
|
131
|
+
|
132
|
+
# otherwise, equal (which is False)
|
133
|
+
return False
|
134
|
+
|
135
|
+
def __gt__(self, other: "SemVer"):
|
136
|
+
return not (self < other) and not (self == other)
|
137
|
+
|
138
|
+
def __le__(self, other: "SemVer"):
|
139
|
+
return (self < other) or (self == other)
|
140
|
+
|
141
|
+
def __ge__(self, other: "SemVer"):
|
142
|
+
return (self > other) or (self == other)
|
143
|
+
|
144
|
+
def __str__(self) -> str:
|
145
|
+
return ".".join([str(item) for item in [self.major, self.minor, self.patch]])
|
146
|
+
|
147
|
+
|
148
|
+
@dataclass
|
149
|
+
class Deprecation:
|
150
|
+
"""
|
151
|
+
Parameterization of a deprecation.
|
152
|
+
"""
|
153
|
+
|
154
|
+
name: str
|
155
|
+
"""Shorthand, unique name used to refer to this deprecation"""
|
156
|
+
message: str
|
157
|
+
"""Message to be displayed explaining the deprecation"""
|
158
|
+
deprecated_in: SemVer
|
159
|
+
"""Version that the feature was deprecated in"""
|
160
|
+
removed_in: Optional[SemVer] = None
|
161
|
+
"""Version that the feature will be removed in"""
|
162
|
+
recommendation: Optional[str] = None
|
163
|
+
"""Recommendation about what to do to replace the deprecated behavior"""
|
164
|
+
issue: Optional[int] = None
|
165
|
+
"""GitHub version describing deprecation"""
|
166
|
+
|
167
|
+
def __post_init__(self):
|
168
|
+
if self.deprecated_in is not None and isinstance(self.deprecated_in, str):
|
169
|
+
self.deprecated_in = SemVer.from_str(self.deprecated_in)
|
170
|
+
if self.removed_in is not None and isinstance(self.removed_in, str):
|
171
|
+
self.removed_in = SemVer.from_str(self.removed_in)
|
172
|
+
|
173
|
+
def __str__(self) -> str:
|
174
|
+
msg = f"[{self.name}] "
|
175
|
+
if self.removed:
|
176
|
+
msg += "REMOVED"
|
177
|
+
elif self.deprecated:
|
178
|
+
msg += "DEPRECATED"
|
179
|
+
|
180
|
+
msg += f"\n{self.message}"
|
181
|
+
msg += f"\nDeprecated In: {str(self.deprecated_in)}"
|
182
|
+
if self.removed_in is not None:
|
183
|
+
msg += f"\nRemoved In: {str(self.removed_in)}"
|
184
|
+
if self.recommendation is not None:
|
185
|
+
msg += f"\nRecommendation: {self.recommendation}"
|
186
|
+
if self.issue is not None:
|
187
|
+
msg += f"\nSee: https://github.com/linkml/linkml/issues/{self.issue}"
|
188
|
+
return msg
|
189
|
+
|
190
|
+
@property
|
191
|
+
def deprecated(self) -> bool:
|
192
|
+
return SemVer.from_package("linkml") >= self.deprecated_in
|
193
|
+
|
194
|
+
@property
|
195
|
+
def removed(self) -> bool:
|
196
|
+
if self.removed_in is None:
|
197
|
+
return False
|
198
|
+
return SemVer.from_package("linkml") >= self.removed_in
|
199
|
+
|
200
|
+
def warn(self, **kwargs):
|
201
|
+
if self.deprecated:
|
202
|
+
warnings.warn(message=str(self), category=DeprecationWarning, stacklevel=3, **kwargs)
|
203
|
+
|
204
|
+
|
205
|
+
DEPRECATIONS = (
|
206
|
+
Deprecation(
|
207
|
+
name="pydanticgen-v1",
|
208
|
+
deprecated_in=SemVer.from_str("1.7.5"),
|
209
|
+
removed_in=SemVer.from_str("1.8.0"),
|
210
|
+
message="Support for generating Pydantic v1.*.* models with pydanticgen is deprecated",
|
211
|
+
recommendation="Migrate any existing models to Pydantic v2",
|
212
|
+
issue=1925,
|
213
|
+
),
|
214
|
+
Deprecation(
|
215
|
+
name="pydantic-v1",
|
216
|
+
deprecated_in=SemVer.from_str("1.7.5"),
|
217
|
+
removed_in=SemVer.from_str("1.9.0"),
|
218
|
+
message=(
|
219
|
+
"LinkML will set a dependency of pydantic>=2 and become incompatible "
|
220
|
+
"with packages with pydantic<2 as a runtime dependency"
|
221
|
+
),
|
222
|
+
recommendation="Update dependent packages to use pydantic>=2",
|
223
|
+
issue=1925,
|
224
|
+
),
|
225
|
+
) # type: tuple[Deprecation, ...]
|
226
|
+
|
227
|
+
EMITTED = set() # type: set[str]
|
228
|
+
|
229
|
+
|
230
|
+
def deprecation_warning(name: str):
|
231
|
+
"""
|
232
|
+
Call this with the name of the deprecation object wherever the deprecated functionality will be used
|
233
|
+
|
234
|
+
This function will
|
235
|
+
|
236
|
+
- emit a warning if the current version is greater than ``deprecated_in``
|
237
|
+
- log that the deprecated feature was accessed in ``EMITTED`` for testing deprecations and muting warnings
|
238
|
+
|
239
|
+
"""
|
240
|
+
global DEPRECATIONS
|
241
|
+
global EMITTED
|
242
|
+
|
243
|
+
dep = [dep for dep in DEPRECATIONS if dep.name == name]
|
244
|
+
if len(dep) == 1:
|
245
|
+
dep = dep[0]
|
246
|
+
elif len(dep) > 1:
|
247
|
+
raise RuntimeError(f"Duplicate deprecations found with name {name}")
|
248
|
+
else:
|
249
|
+
EMITTED.add(name)
|
250
|
+
return
|
251
|
+
|
252
|
+
if dep.name not in EMITTED:
|
253
|
+
dep.warn()
|
254
|
+
|
255
|
+
EMITTED.add(name)
|
linkml/utils/generator.py
CHANGED
@@ -20,10 +20,8 @@ import logging
|
|
20
20
|
import os
|
21
21
|
import re
|
22
22
|
import sys
|
23
|
-
from contextlib import redirect_stdout
|
24
23
|
from dataclasses import dataclass, field
|
25
24
|
from functools import lru_cache
|
26
|
-
from io import StringIO
|
27
25
|
from pathlib import Path
|
28
26
|
from typing import Callable, ClassVar, Dict, List, Mapping, Optional, Set, TextIO, Type, Union, cast
|
29
27
|
|
@@ -176,6 +174,9 @@ class Generator(metaclass=abc.ABCMeta):
|
|
176
174
|
stacktrace: bool = False
|
177
175
|
"""True means print stack trace, false just error message"""
|
178
176
|
|
177
|
+
include: Optional[Union[str, Path, SchemaDefinition]] = None
|
178
|
+
"""If set, include extra schema outside of the imports mechanism"""
|
179
|
+
|
179
180
|
def __post_init__(self) -> None:
|
180
181
|
if not self.logger:
|
181
182
|
self.logger = logging.getLogger()
|
@@ -204,7 +205,12 @@ class Generator(metaclass=abc.ABCMeta):
|
|
204
205
|
else:
|
205
206
|
logging.info(f"Using SchemaView with im={self.importmap} // base_dir={self.base_dir}")
|
206
207
|
self.schemaview = SchemaView(schema, importmap=self.importmap, base_dir=self.base_dir)
|
208
|
+
if self.include:
|
209
|
+
if isinstance(self.include, (str, Path)):
|
210
|
+
self.include = SchemaView(self.include, importmap=self.importmap, base_dir=self.base_dir).schema
|
211
|
+
self.schemaview.merge_schema(self.include)
|
207
212
|
self.schema = self.schemaview.schema
|
213
|
+
|
208
214
|
self._init_namespaces()
|
209
215
|
|
210
216
|
def _initialize_using_schemaloader(self, schema: Union[str, TextIO, SchemaDefinition, "Generator"]):
|
@@ -267,66 +273,82 @@ class Generator(metaclass=abc.ABCMeta):
|
|
267
273
|
:param kwargs: Generator specific parameters
|
268
274
|
:return: Generated output
|
269
275
|
"""
|
270
|
-
|
271
|
-
|
272
|
-
#
|
273
|
-
#
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
)
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
)
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
):
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
)
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
self.
|
313
|
-
|
314
|
-
|
315
|
-
|
276
|
+
out = ""
|
277
|
+
|
278
|
+
# the default is to use the Visitor Pattern; each individual generator may
|
279
|
+
# choose to override methods {visit,end}_{element}.
|
280
|
+
# See https://github.com/linkml/linkml/issues/923
|
281
|
+
sub_out = self.visit_schema(**kwargs)
|
282
|
+
if sub_out is not None:
|
283
|
+
out += sub_out
|
284
|
+
for sn, ss in (
|
285
|
+
sorted(self.schema.subsets.items(), key=lambda s: s[0].lower())
|
286
|
+
if self.visits_are_sorted
|
287
|
+
else self.schema.subsets.items()
|
288
|
+
):
|
289
|
+
sub_out = self.visit_subset(ss)
|
290
|
+
if sub_out is not None:
|
291
|
+
out += sub_out
|
292
|
+
for tn, typ in (
|
293
|
+
sorted(self.schema.types.items(), key=lambda s: s[0].lower())
|
294
|
+
if self.visits_are_sorted
|
295
|
+
else self.schema.types.items()
|
296
|
+
):
|
297
|
+
sub_out = self.visit_type(typ)
|
298
|
+
if sub_out is not None:
|
299
|
+
out += sub_out
|
300
|
+
for enum in (
|
301
|
+
sorted(self.schema.enums.values(), key=lambda e: e.name.lower())
|
302
|
+
if self.visits_are_sorted
|
303
|
+
else self.schema.enums.values()
|
304
|
+
):
|
305
|
+
sub_out = self.visit_enum(enum)
|
306
|
+
if sub_out is not None:
|
307
|
+
out += sub_out
|
308
|
+
for sn, slot in (
|
309
|
+
sorted(self.schema.slots.items(), key=lambda c: c[0].lower())
|
310
|
+
if self.visits_are_sorted
|
311
|
+
else self.schema.slots.items()
|
312
|
+
):
|
313
|
+
sub_out = self.visit_slot(self.aliased_slot_name(slot), slot)
|
314
|
+
if sub_out is not None:
|
315
|
+
out += sub_out
|
316
|
+
for cls in (
|
317
|
+
sorted(self.schema.classes.values(), key=lambda c: c.name.lower())
|
318
|
+
if self.visits_are_sorted
|
319
|
+
else self.schema.classes.values()
|
320
|
+
):
|
321
|
+
cls_out = self.visit_class(cls)
|
322
|
+
if cls_out:
|
323
|
+
if isinstance(cls_out, str):
|
324
|
+
out += cls_out
|
325
|
+
for slot in self.all_slots(cls) if self.visit_all_class_slots else self.own_slots(cls):
|
326
|
+
sub_out = self.visit_class_slot(cls, self.aliased_slot_name(slot), slot)
|
327
|
+
if sub_out is not None:
|
328
|
+
out += sub_out
|
329
|
+
sub_out = self.end_class(cls)
|
330
|
+
if sub_out is not None:
|
331
|
+
out += sub_out
|
332
|
+
sub_out = self.end_schema(**kwargs)
|
333
|
+
if sub_out is not None:
|
334
|
+
out += sub_out
|
335
|
+
return out
|
336
|
+
|
337
|
+
def visit_schema(self, **kwargs) -> Optional[str]:
|
316
338
|
"""Visited once at the beginning of generation
|
317
339
|
|
318
340
|
@param kwargs: Arguments passed through from CLI -- implementation dependent
|
319
341
|
"""
|
320
342
|
...
|
321
343
|
|
322
|
-
def end_schema(self, **kwargs) ->
|
344
|
+
def end_schema(self, **kwargs) -> Optional[str]:
|
323
345
|
"""Visited once at the end of generation
|
324
346
|
|
325
347
|
@param kwargs: Arguments passed through from CLI -- implementation dependent
|
326
348
|
"""
|
327
349
|
...
|
328
350
|
|
329
|
-
def visit_class(self, cls: ClassDefinition) -> bool:
|
351
|
+
def visit_class(self, cls: ClassDefinition) -> Optional[Union[str, bool]]:
|
330
352
|
"""Visited once per schema class
|
331
353
|
|
332
354
|
@param cls: class being visited
|
@@ -334,14 +356,14 @@ class Generator(metaclass=abc.ABCMeta):
|
|
334
356
|
"""
|
335
357
|
return True
|
336
358
|
|
337
|
-
def end_class(self, cls: ClassDefinition) ->
|
359
|
+
def end_class(self, cls: ClassDefinition) -> Optional[str]:
|
338
360
|
"""Visited after visit_class_slots (if visit_class returned true)
|
339
361
|
|
340
362
|
@param cls: class being visited
|
341
363
|
"""
|
342
364
|
...
|
343
365
|
|
344
|
-
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) ->
|
366
|
+
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> Optional[str]:
|
345
367
|
"""Visited for each slot in a class. If class level visit_all_slots is true, this is visited once
|
346
368
|
for any class that is inherited (class itself, is_a, mixin, apply_to). Otherwise, just the own slots.
|
347
369
|
|
@@ -351,7 +373,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
351
373
|
"""
|
352
374
|
...
|
353
375
|
|
354
|
-
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) ->
|
376
|
+
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> Optional[str]:
|
355
377
|
"""Visited once for every slot definition in the schema.
|
356
378
|
|
357
379
|
@param aliased_slot_name: Aliased name of the slot. May not be unique
|
@@ -359,21 +381,21 @@ class Generator(metaclass=abc.ABCMeta):
|
|
359
381
|
"""
|
360
382
|
...
|
361
383
|
|
362
|
-
def visit_type(self, typ: TypeDefinition) ->
|
384
|
+
def visit_type(self, typ: TypeDefinition) -> Optional[str]:
|
363
385
|
"""Visited once for every type definition in the schema
|
364
386
|
|
365
387
|
@param typ: Type definition
|
366
388
|
"""
|
367
389
|
...
|
368
390
|
|
369
|
-
def visit_subset(self, subset: SubsetDefinition) ->
|
391
|
+
def visit_subset(self, subset: SubsetDefinition) -> Optional[str]:
|
370
392
|
"""Visited once for every subset definition in the schema
|
371
393
|
|
372
394
|
#param subset: Subset definition
|
373
395
|
"""
|
374
396
|
...
|
375
397
|
|
376
|
-
def visit_enum(self, enum: EnumDefinition) ->
|
398
|
+
def visit_enum(self, enum: EnumDefinition) -> Optional[str]:
|
377
399
|
"""Visited once for every enum definition in the schema
|
378
400
|
|
379
401
|
@param enum: Enum definition
|
linkml/validator/cli.py
CHANGED
@@ -108,6 +108,13 @@ DEPRECATED = "[DEPRECATED: only used in legacy mode]"
|
|
108
108
|
help=f"{DEPRECATED} When handling range constraints, include all descendants of the range "
|
109
109
|
"class instead of just the range class",
|
110
110
|
)
|
111
|
+
@click.option(
|
112
|
+
"--include-context/--no-include-context",
|
113
|
+
"-D",
|
114
|
+
default=False,
|
115
|
+
show_default=True,
|
116
|
+
help="Include additional context when reporting of validation errors.",
|
117
|
+
)
|
111
118
|
@click.argument("data_sources", nargs=-1, type=click.Path(exists=True))
|
112
119
|
@click.version_option(__version__, "-V", "--version")
|
113
120
|
@click.pass_context
|
@@ -123,6 +130,7 @@ def cli(
|
|
123
130
|
input_format: Optional[str],
|
124
131
|
index_slot: Optional[str],
|
125
132
|
include_range_class_descendants: bool,
|
133
|
+
include_context: bool,
|
126
134
|
):
|
127
135
|
if legacy_mode:
|
128
136
|
from linkml.validators import jsonschemavalidator
|
@@ -183,6 +191,9 @@ def cli(
|
|
183
191
|
for result in validator.iter_results_from_source(loader, config.target_class):
|
184
192
|
severity_counter[result.severity] += 1
|
185
193
|
click.echo(f"[{result.severity.value}] [{loader.source}/{result.instance_index}] {result.message}")
|
194
|
+
if include_context:
|
195
|
+
for ctx in result.context:
|
196
|
+
click.echo(f"[CONTEXT] {ctx}")
|
186
197
|
|
187
198
|
if sum(severity_counter.values()) == 0:
|
188
199
|
click.echo("No issues found")
|
@@ -46,6 +46,7 @@ class JsonschemaValidationPlugin(ValidationPlugin):
|
|
46
46
|
path_override=self.json_schema_path,
|
47
47
|
)
|
48
48
|
for error in validator.iter_errors(instance):
|
49
|
+
error_context = [ctx.message for ctx in error.context]
|
49
50
|
best_error = best_match([error])
|
50
51
|
yield ValidationResult(
|
51
52
|
type="jsonschema validation",
|
@@ -53,4 +54,5 @@ class JsonschemaValidationPlugin(ValidationPlugin):
|
|
53
54
|
instance=instance,
|
54
55
|
instantiates=context.target_class,
|
55
56
|
message=f"{best_error.message} in /{'/'.join(str(p) for p in best_error.absolute_path)}",
|
57
|
+
context=error_context,
|
56
58
|
)
|
linkml/validator/report.py
CHANGED
@@ -20,6 +20,7 @@ from linkml_runtime.dumpers import json_dumper, rdflib_dumper, yaml_dumper
|
|
20
20
|
from linkml_runtime.linkml_model import ElementName
|
21
21
|
from linkml_runtime.utils.formatutils import camelcase
|
22
22
|
|
23
|
+
from linkml._version import __version__
|
23
24
|
from linkml.generators.pythongen import PythonGenerator
|
24
25
|
from linkml.validator import Validator, _get_default_validator
|
25
26
|
|
@@ -298,6 +299,7 @@ class ExampleRunner:
|
|
298
299
|
show_default=True,
|
299
300
|
help="If true use type_designators to deepen ranges",
|
300
301
|
)
|
302
|
+
@click.version_option(__version__, "-V", "--version")
|
301
303
|
def cli(schema, prefixes, output: TextIO, **kwargs):
|
302
304
|
"""Process a folder of examples and a folder of counter examples.
|
303
305
|
|