linkml 1.8.4__py3-none-any.whl → 1.8.5__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/common/ifabsent_processor.py +98 -21
- linkml/generators/common/naming.py +106 -0
- linkml/generators/docgen.py +16 -7
- linkml/generators/graphqlgen.py +34 -2
- linkml/generators/pydanticgen/template.py +12 -1
- linkml/generators/python/python_ifabsent_processor.py +1 -1
- linkml/generators/pythongen.py +123 -21
- {linkml-1.8.4.dist-info → linkml-1.8.5.dist-info}/METADATA +1 -1
- {linkml-1.8.4.dist-info → linkml-1.8.5.dist-info}/RECORD +12 -11
- {linkml-1.8.4.dist-info → linkml-1.8.5.dist-info}/LICENSE +0 -0
- {linkml-1.8.4.dist-info → linkml-1.8.5.dist-info}/WHEEL +0 -0
- {linkml-1.8.4.dist-info → linkml-1.8.5.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,13 @@
|
|
1
1
|
import abc
|
2
2
|
import re
|
3
|
+
import sys
|
3
4
|
from abc import ABC
|
4
|
-
from typing import Any, Optional
|
5
|
+
from typing import Any, Optional, Type, Union
|
6
|
+
|
7
|
+
if sys.version_info < (3, 10):
|
8
|
+
from typing_extensions import TypeAlias
|
9
|
+
else:
|
10
|
+
from typing import TypeAlias
|
5
11
|
|
6
12
|
from linkml_runtime import SchemaView
|
7
13
|
from linkml_runtime.linkml_model import (
|
@@ -18,6 +24,7 @@ from linkml_runtime.linkml_model import (
|
|
18
24
|
String,
|
19
25
|
Time,
|
20
26
|
Uri,
|
27
|
+
types,
|
21
28
|
)
|
22
29
|
from linkml_runtime.linkml_model.types import (
|
23
30
|
Curie,
|
@@ -31,6 +38,34 @@ from linkml_runtime.linkml_model.types import (
|
|
31
38
|
Uriorcurie,
|
32
39
|
)
|
33
40
|
|
41
|
+
TYPES_TYPE: TypeAlias = Union[
|
42
|
+
Type[Boolean],
|
43
|
+
Type[Curie],
|
44
|
+
Type[Date],
|
45
|
+
Type[DateOrDatetime],
|
46
|
+
Type[Datetime],
|
47
|
+
Type[Decimal],
|
48
|
+
Type[Double],
|
49
|
+
Type[Float],
|
50
|
+
Type[Integer],
|
51
|
+
Type[Jsonpath],
|
52
|
+
Type[Jsonpointer],
|
53
|
+
Type[Ncname],
|
54
|
+
Type[Nodeidentifier],
|
55
|
+
Type[Objectidentifier],
|
56
|
+
Type[Sparqlpath],
|
57
|
+
Type[String],
|
58
|
+
Type[Time],
|
59
|
+
Type[Uri],
|
60
|
+
Type[Uriorcurie],
|
61
|
+
]
|
62
|
+
|
63
|
+
TYPES = [
|
64
|
+
t
|
65
|
+
for t in types.__dict__.values()
|
66
|
+
if isinstance(t, type) and t.__module__ == types.__name__ and hasattr(t, "type_name")
|
67
|
+
]
|
68
|
+
|
34
69
|
|
35
70
|
class IfAbsentProcessor(ABC):
|
36
71
|
"""
|
@@ -39,7 +74,7 @@ class IfAbsentProcessor(ABC):
|
|
39
74
|
See `<https://w3id.org/linkml/ifabsent>`_.
|
40
75
|
"""
|
41
76
|
|
42
|
-
ifabsent_regex = re.compile("""(?:(?P<type>\w+)\()?[\"\']?(?P<default_value>[^\(\)\"\')]*)[\"\']?\)?""")
|
77
|
+
ifabsent_regex = re.compile(r"""(?:(?P<type>\w+)\()?[\"\']?(?P<default_value>[^\(\)\"\')]*)[\"\']?\)?""")
|
43
78
|
|
44
79
|
def __init__(self, schema_view: SchemaView):
|
45
80
|
self.schema_view = schema_view
|
@@ -61,10 +96,12 @@ class IfAbsentProcessor(ABC):
|
|
61
96
|
if mapped:
|
62
97
|
return custom_default_value
|
63
98
|
|
64
|
-
|
99
|
+
base_type = self._base_type(slot.range)
|
100
|
+
|
101
|
+
if base_type is String:
|
65
102
|
return self.map_string_default_value(ifabsent_default_value, slot, cls)
|
66
103
|
|
67
|
-
if
|
104
|
+
if base_type is Boolean:
|
68
105
|
if re.match(r"^[Tt]rue$", ifabsent_default_value):
|
69
106
|
return self.map_boolean_true_default_value(slot, cls)
|
70
107
|
elif re.match(r"^[Ff]alse$", ifabsent_default_value):
|
@@ -75,19 +112,19 @@ class IfAbsentProcessor(ABC):
|
|
75
112
|
f"value"
|
76
113
|
)
|
77
114
|
|
78
|
-
if
|
115
|
+
if base_type is Integer:
|
79
116
|
return self.map_integer_default_value(ifabsent_default_value, slot, cls)
|
80
117
|
|
81
|
-
if
|
118
|
+
if base_type is Float:
|
82
119
|
return self.map_float_default_value(ifabsent_default_value, slot, cls)
|
83
120
|
|
84
|
-
if
|
121
|
+
if base_type is Double:
|
85
122
|
return self.map_double_default_value(ifabsent_default_value, slot, cls)
|
86
123
|
|
87
|
-
if
|
124
|
+
if base_type is Decimal:
|
88
125
|
return self.map_decimal_default_value(ifabsent_default_value, slot, cls)
|
89
126
|
|
90
|
-
if
|
127
|
+
if base_type is Time:
|
91
128
|
match = re.match(r"^(\d{2}):(\d{2}):(\d{2}).*$", ifabsent_default_value)
|
92
129
|
if match:
|
93
130
|
return self.map_time_default_value(match[1], match[2], match[3], slot, cls)
|
@@ -97,7 +134,7 @@ class IfAbsentProcessor(ABC):
|
|
97
134
|
)
|
98
135
|
|
99
136
|
# TODO manage timezones and offsets
|
100
|
-
if
|
137
|
+
if base_type is Date:
|
101
138
|
match = re.match(r"^(\d{4})-(\d{2})-(\d{2})$", ifabsent_default_value)
|
102
139
|
if match:
|
103
140
|
return self.map_date_default_value(match[1], match[2], match[3], slot, cls)
|
@@ -107,7 +144,7 @@ class IfAbsentProcessor(ABC):
|
|
107
144
|
)
|
108
145
|
|
109
146
|
# TODO manage timezones and offsets
|
110
|
-
if
|
147
|
+
if base_type is Datetime:
|
111
148
|
match = re.match(r"^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).*$", ifabsent_default_value)
|
112
149
|
if match:
|
113
150
|
return self.map_datetime_default_value(
|
@@ -120,7 +157,7 @@ class IfAbsentProcessor(ABC):
|
|
120
157
|
)
|
121
158
|
|
122
159
|
# TODO manage timezones and offsets
|
123
|
-
if
|
160
|
+
if base_type is DateOrDatetime:
|
124
161
|
match = re.match(r"^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2}))?.*$", ifabsent_default_value)
|
125
162
|
if match and (match[4] is None or match[5] is None or match[6] is None):
|
126
163
|
return self.map_date_default_value(match[1], match[2], match[3], slot, cls)
|
@@ -134,31 +171,31 @@ class IfAbsentProcessor(ABC):
|
|
134
171
|
f"datetime value"
|
135
172
|
)
|
136
173
|
|
137
|
-
if
|
174
|
+
if base_type is Uri:
|
138
175
|
return self.map_uri_default_value(ifabsent_default_value, slot, cls)
|
139
176
|
|
140
|
-
if
|
177
|
+
if base_type is Curie:
|
141
178
|
return self.map_curie_default_value(ifabsent_default_value, slot, cls)
|
142
179
|
|
143
|
-
if
|
180
|
+
if base_type is Uriorcurie:
|
144
181
|
return self.map_uri_or_curie_default_value(ifabsent_default_value, slot, cls)
|
145
182
|
|
146
|
-
if
|
183
|
+
if base_type is Ncname:
|
147
184
|
return self.map_nc_name_default_value(ifabsent_default_value, slot, cls)
|
148
185
|
|
149
|
-
if
|
186
|
+
if base_type is Objectidentifier:
|
150
187
|
return self.map_object_identifier_default_value(ifabsent_default_value, slot, cls)
|
151
188
|
|
152
|
-
if
|
189
|
+
if base_type is Nodeidentifier:
|
153
190
|
return self.map_node_identifier_default_value(ifabsent_default_value, slot, cls)
|
154
191
|
|
155
|
-
if
|
192
|
+
if base_type is Jsonpointer:
|
156
193
|
return self.map_json_pointer_default_value(ifabsent_default_value, slot, cls)
|
157
194
|
|
158
|
-
if
|
195
|
+
if base_type is Jsonpath:
|
159
196
|
return self.map_json_path_default_value(ifabsent_default_value, slot, cls)
|
160
197
|
|
161
|
-
if
|
198
|
+
if base_type is Sparqlpath:
|
162
199
|
return self.map_sparql_path_default_value(ifabsent_default_value, slot, cls)
|
163
200
|
|
164
201
|
# -----------------------
|
@@ -173,6 +210,46 @@ class IfAbsentProcessor(ABC):
|
|
173
210
|
|
174
211
|
raise ValueError(f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot could not be processed")
|
175
212
|
|
213
|
+
def _base_type(self, range_: str) -> Optional[TYPES_TYPE]:
|
214
|
+
"""
|
215
|
+
Find the linkml base type that corresponds to either a matching type_name or custom type
|
216
|
+
|
217
|
+
First check for an explicit match of the range == TypeDefinition.type_name
|
218
|
+
Then check for explicit inheritance via typeof
|
219
|
+
Finally check for implicit matching via matching base
|
220
|
+
|
221
|
+
Don't raise here, just return None in case another method of resolution like enums are
|
222
|
+
available
|
223
|
+
"""
|
224
|
+
# first check for matching type using type_name - ie. range is already a base type
|
225
|
+
|
226
|
+
for typ in TYPES:
|
227
|
+
if range_ == typ.type_name:
|
228
|
+
return typ
|
229
|
+
|
230
|
+
# if we're not a type, return None to indicate that, e.g. if an enum's permissible_value
|
231
|
+
if range_ not in self.schema_view.all_types(imports=True):
|
232
|
+
return
|
233
|
+
|
234
|
+
# then check explicit descendents of types
|
235
|
+
# base types do not inherit from one another, so the last ancestor is always a base type
|
236
|
+
# if it is inheriting from a base type
|
237
|
+
ancestor = self.schema_view.type_ancestors(range_)[-1]
|
238
|
+
for typ in TYPES:
|
239
|
+
if ancestor == typ.type_name:
|
240
|
+
return typ
|
241
|
+
|
242
|
+
# finally check if we have a matching base
|
243
|
+
induced_typ = self.schema_view.induced_type(range_)
|
244
|
+
if induced_typ.repr is None and induced_typ.base is None:
|
245
|
+
return None
|
246
|
+
for typ in TYPES:
|
247
|
+
# types always inherit from a single type, and that type is their base
|
248
|
+
# our range can match it with repr or base
|
249
|
+
typ_base = typ.__mro__[1].__name__
|
250
|
+
if typ_base == induced_typ.base:
|
251
|
+
return typ
|
252
|
+
|
176
253
|
@abc.abstractmethod
|
177
254
|
def map_custom_default_values(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition) -> (bool, str):
|
178
255
|
"""
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import logging
|
2
|
+
import re
|
3
|
+
import unicodedata
|
4
|
+
from enum import Enum
|
5
|
+
|
6
|
+
|
7
|
+
class NamingProfiles(str, Enum):
|
8
|
+
# GraphQL naming profile ensures compatibility with the GraphQL specification
|
9
|
+
# WRT names: https://spec.graphql.org/October2021/#Name
|
10
|
+
graphql = "graphql"
|
11
|
+
|
12
|
+
|
13
|
+
class NameCompatibility(object):
|
14
|
+
"""Make a name compatible to the given profile"""
|
15
|
+
|
16
|
+
# heading double underscores and digit reserved to names starting with a digit
|
17
|
+
re_reserved_heading_digit = re.compile("^__[0-9]")
|
18
|
+
# valid name between double underscores is reserved for unicode name transformations
|
19
|
+
re_reserved_unicode_name_transformation = re.compile("__[0-9a-zA-Z][0-9a-zA-Z_]*__")
|
20
|
+
# something like '__U_xxxx_' is reserved for unicode code transformations
|
21
|
+
re_reserved_unicode_code_transformation = re.compile("__U_[0-9a-eA-E]{4}_")
|
22
|
+
# strings starting with a digit
|
23
|
+
re_heading_digit = re.compile("^[0-9]")
|
24
|
+
# character that is neither alphanumeric nor underscore
|
25
|
+
re_no_alphanum_underscore = re.compile("[^0-9a-zA-Z_]")
|
26
|
+
|
27
|
+
def __init__(self, profile: NamingProfiles, do_not_fix: bool = False):
|
28
|
+
"""Specify the naming policy on instantiation"""
|
29
|
+
self.profile = profile
|
30
|
+
self.do_not_fix = do_not_fix
|
31
|
+
|
32
|
+
def _no_heading_digits(self, input: str) -> str:
|
33
|
+
"""Ensure name does not start with a heading digit"""
|
34
|
+
output = input
|
35
|
+
if self.re_heading_digit.match(input):
|
36
|
+
if self.do_not_fix:
|
37
|
+
raise ValueError(f"Name '{input}' starts with digit (illegal GraphQL) and will not be fixed!")
|
38
|
+
else:
|
39
|
+
logging.warning(
|
40
|
+
f"Name '{input}' starts with digit (illegal GraphQL), "
|
41
|
+
+ f"it has been prefixed with '__', resulting in {output}"
|
42
|
+
)
|
43
|
+
output = f"__{input}"
|
44
|
+
return output
|
45
|
+
|
46
|
+
def _transform_char(self, char: str) -> str:
|
47
|
+
"""Transform unsupported characters"""
|
48
|
+
if len(char) != 1:
|
49
|
+
raise Exception(f"Single character expected, but got '{char}'!!")
|
50
|
+
# replace whitespaces with underscores
|
51
|
+
# the transformation cannot be inverted, but is a well-established
|
52
|
+
# and expected transformation
|
53
|
+
if char == " ":
|
54
|
+
return "_"
|
55
|
+
# try to use names for ASCII characters
|
56
|
+
if ord(char) < 128:
|
57
|
+
try:
|
58
|
+
# unicodedata.lookup should be able to invert the transformation
|
59
|
+
return f"__{unicodedata.name(char).replace(' ', '_').replace('-', '_')}__"
|
60
|
+
except ValueError:
|
61
|
+
pass
|
62
|
+
# fallback to code-transformation if none of the previous has worked
|
63
|
+
return f"__U_{ord(char):04X}_"
|
64
|
+
|
65
|
+
def _only_alphanum_underscore(self, input: str) -> str:
|
66
|
+
"""Ensure name does not contain any unsupported characters"""
|
67
|
+
output = input
|
68
|
+
# with re.split and re.findall we get in the same order and separated in two arrays
|
69
|
+
# the substrings between special characters and the special characters
|
70
|
+
no_alphanum_underscore_match = self.re_no_alphanum_underscore.findall(input)
|
71
|
+
if no_alphanum_underscore_match:
|
72
|
+
if self.do_not_fix:
|
73
|
+
raise ValueError(f"Name '{input}' contains a character illegal in GraphQL and will not be fixed!")
|
74
|
+
else:
|
75
|
+
logging.warning(
|
76
|
+
f"Name '{input}' contains a character illegal in GraphQL, "
|
77
|
+
+ f"the resulting encoded name is {output}"
|
78
|
+
)
|
79
|
+
to_keep = self.re_no_alphanum_underscore.split(input)
|
80
|
+
# first comes first substring to keep
|
81
|
+
output = to_keep[0]
|
82
|
+
# each char replacement is followed by the next substring to keep
|
83
|
+
for offset in range(0, len(to_keep) - 1):
|
84
|
+
output = output + self._transform_char(no_alphanum_underscore_match[offset])
|
85
|
+
output = output + to_keep[offset + 1]
|
86
|
+
return output
|
87
|
+
|
88
|
+
def _graphql_compatibility(self, input: str) -> str:
|
89
|
+
"""Ensure name compatibility with GraphQL name policies"""
|
90
|
+
# as of now, some (hopefully) very rare patterns are reserved to mark transformations
|
91
|
+
if self.re_reserved_heading_digit.match(input):
|
92
|
+
raise NotImplementedError("Names starting with a double underscore followed by a digit are not supported!!")
|
93
|
+
if self.re_reserved_unicode_name_transformation.match(input):
|
94
|
+
raise NotImplementedError("Names containing valid names between double underscores are not supported!!")
|
95
|
+
if self.re_reserved_unicode_code_transformation.match(input):
|
96
|
+
raise NotImplementedError("Names containing strings like '__U_xxxx_' are not supported!!")
|
97
|
+
# apply transformation
|
98
|
+
output = input
|
99
|
+
output = self._no_heading_digits(output)
|
100
|
+
output = self._only_alphanum_underscore(output)
|
101
|
+
return output
|
102
|
+
|
103
|
+
def compatible(self, input: str) -> str:
|
104
|
+
"""Make given name compatible with the given naming policy."""
|
105
|
+
if self.profile == "graphql":
|
106
|
+
return self._graphql_compatibility(input)
|
linkml/generators/docgen.py
CHANGED
@@ -164,6 +164,7 @@ class DocGenerator(Generator):
|
|
164
164
|
use_slot_uris: bool = False
|
165
165
|
use_class_uris: bool = False
|
166
166
|
hierarchical_class_view: bool = False
|
167
|
+
render_imports: bool = False
|
167
168
|
|
168
169
|
def __post_init__(self):
|
169
170
|
dialect = self.dialect
|
@@ -759,7 +760,7 @@ class DocGenerator(Generator):
|
|
759
760
|
Ensures rank is non-null
|
760
761
|
:return: iterator
|
761
762
|
"""
|
762
|
-
elts = self.schemaview.all_classes(imports=self.
|
763
|
+
elts = self.schemaview.all_classes(imports=self.render_imports).values()
|
763
764
|
_ensure_ranked(elts)
|
764
765
|
for e in elts:
|
765
766
|
yield e
|
@@ -771,7 +772,7 @@ class DocGenerator(Generator):
|
|
771
772
|
Ensures rank is non-null
|
772
773
|
:return: iterator
|
773
774
|
"""
|
774
|
-
elts = self.schemaview.all_slots(imports=self.
|
775
|
+
elts = self.schemaview.all_slots(imports=self.render_imports).values()
|
775
776
|
_ensure_ranked(elts)
|
776
777
|
for e in elts:
|
777
778
|
yield e
|
@@ -783,7 +784,7 @@ class DocGenerator(Generator):
|
|
783
784
|
Ensures rank is non-null
|
784
785
|
:return: iterator
|
785
786
|
"""
|
786
|
-
elts = self.schemaview.all_types(imports=self.
|
787
|
+
elts = self.schemaview.all_types(imports=self.render_imports).values()
|
787
788
|
_ensure_ranked(elts)
|
788
789
|
for e in elts:
|
789
790
|
yield e
|
@@ -798,7 +799,7 @@ class DocGenerator(Generator):
|
|
798
799
|
Ensures rank is non-null
|
799
800
|
:return: iterator
|
800
801
|
"""
|
801
|
-
elts = self.schemaview.all_enums(imports=self.
|
802
|
+
elts = self.schemaview.all_enums(imports=self.render_imports).values()
|
802
803
|
_ensure_ranked(elts)
|
803
804
|
for e in elts:
|
804
805
|
yield e
|
@@ -810,7 +811,7 @@ class DocGenerator(Generator):
|
|
810
811
|
Ensures rank is non-null
|
811
812
|
:return: iterator
|
812
813
|
"""
|
813
|
-
elts = self.schemaview.all_subsets(imports=self.
|
814
|
+
elts = self.schemaview.all_subsets(imports=self.render_imports).values()
|
814
815
|
_ensure_ranked(elts)
|
815
816
|
for e in elts:
|
816
817
|
yield e
|
@@ -834,7 +835,7 @@ class DocGenerator(Generator):
|
|
834
835
|
:return: tuples (depth: int, cls: ClassDefinitionName)
|
835
836
|
"""
|
836
837
|
sv = self.schemaview
|
837
|
-
roots = sv.class_roots(mixins=False, imports=self.
|
838
|
+
roots = sv.class_roots(mixins=False, imports=self.render_imports)
|
838
839
|
|
839
840
|
# by default the classes are sorted alphabetically
|
840
841
|
roots = sorted(roots, key=str.casefold, reverse=True)
|
@@ -848,7 +849,7 @@ class DocGenerator(Generator):
|
|
848
849
|
depth, class_name = stack.pop()
|
849
850
|
yield depth, class_name
|
850
851
|
children = sorted(
|
851
|
-
sv.class_children(class_name=class_name, mixins=False, imports=self.
|
852
|
+
sv.class_children(class_name=class_name, mixins=False, imports=self.render_imports),
|
852
853
|
key=str.casefold,
|
853
854
|
reverse=True,
|
854
855
|
)
|
@@ -1008,6 +1009,12 @@ class DocGenerator(Generator):
|
|
1008
1009
|
default=True,
|
1009
1010
|
help="Render class table on index page in a hierarchically indented view",
|
1010
1011
|
)
|
1012
|
+
@click.option(
|
1013
|
+
"--render-imports/--no-render-imports",
|
1014
|
+
default=False,
|
1015
|
+
show_default=True,
|
1016
|
+
help="Render also the documentation of elements from imported schemas",
|
1017
|
+
)
|
1011
1018
|
@click.option(
|
1012
1019
|
"--example-directory",
|
1013
1020
|
help="Folder in which example files are found. These are used to make inline examples",
|
@@ -1037,6 +1044,7 @@ def cli(
|
|
1037
1044
|
use_class_uris,
|
1038
1045
|
hierarchical_class_view,
|
1039
1046
|
subfolder_type_separation,
|
1047
|
+
render_imports,
|
1040
1048
|
**args,
|
1041
1049
|
):
|
1042
1050
|
"""Generate documentation folder from a LinkML YAML schema
|
@@ -1068,6 +1076,7 @@ def cli(
|
|
1068
1076
|
hierarchical_class_view=hierarchical_class_view,
|
1069
1077
|
index_name=index_name,
|
1070
1078
|
subfolder_type_separation=subfolder_type_separation,
|
1079
|
+
render_imports=render_imports,
|
1071
1080
|
**args,
|
1072
1081
|
)
|
1073
1082
|
print(gen.serialize())
|
linkml/generators/graphqlgen.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
import logging
|
1
2
|
import os
|
3
|
+
import re
|
2
4
|
from dataclasses import dataclass
|
3
5
|
|
4
6
|
import click
|
5
|
-
from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition
|
7
|
+
from linkml_runtime.linkml_model.meta import ClassDefinition, EnumDefinition, SlotDefinition
|
6
8
|
from linkml_runtime.utils.formatutils import camelcase, lcamelcase
|
7
9
|
|
8
10
|
from linkml._version import __version__
|
11
|
+
from linkml.generators.common.naming import NameCompatibility, NamingProfiles
|
9
12
|
from linkml.utils.generator import Generator, shared_arguments
|
10
13
|
|
11
14
|
|
@@ -19,6 +22,13 @@ class GraphqlGenerator(Generator):
|
|
19
22
|
uses_schemaloader = True
|
20
23
|
requires_metamodel = False
|
21
24
|
|
25
|
+
strict_naming: bool = False
|
26
|
+
_permissible_value_valid_characters = re.compile("^[_A-Za-z][_0-9A-Za-z]*?$")
|
27
|
+
|
28
|
+
def __post_init__(self):
|
29
|
+
self.name_compatiblity = NameCompatibility(profile=NamingProfiles.graphql, do_not_fix=self.strict_naming)
|
30
|
+
super().__post_init__()
|
31
|
+
|
22
32
|
def visit_schema(self, **kwargs) -> str:
|
23
33
|
return self.generate_header()
|
24
34
|
|
@@ -50,13 +60,35 @@ class GraphqlGenerator(Generator):
|
|
50
60
|
slotrange = slotrange + "!"
|
51
61
|
return f"\n {lcamelcase(aliased_slot_name)}: {slotrange}"
|
52
62
|
|
63
|
+
def visit_enum(self, enum: EnumDefinition):
|
64
|
+
if enum.permissible_values:
|
65
|
+
permissible_values = []
|
66
|
+
for value in enum.permissible_values:
|
67
|
+
permissible_values.append(self.name_compatiblity.compatible(value))
|
68
|
+
values = "\n ".join(permissible_values)
|
69
|
+
return f"enum {camelcase(enum.name).replace(' ','')}\n {{\n {values}\n }}\n\n"
|
70
|
+
else:
|
71
|
+
logging.warning(
|
72
|
+
f"Enumeration {enum.name} using `reachable_from` instead of `permissible_values` "
|
73
|
+
+ "to specify permissible values is not supported yet."
|
74
|
+
+ "Enumeration {enum.name} will be silently ignored!!"
|
75
|
+
)
|
76
|
+
return ""
|
77
|
+
|
53
78
|
|
54
79
|
@shared_arguments(GraphqlGenerator)
|
55
80
|
@click.command(name="graphql")
|
81
|
+
@click.option(
|
82
|
+
"--strict-naming",
|
83
|
+
is_flag=True,
|
84
|
+
show_default=True,
|
85
|
+
help="Treat warnings about invalid names or schema elements as errors.",
|
86
|
+
)
|
56
87
|
@click.version_option(__version__, "-V", "--version")
|
57
88
|
def cli(yamlfile, **args):
|
58
89
|
"""Generate graphql representation of a LinkML model"""
|
59
|
-
|
90
|
+
generator = GraphqlGenerator(yamlfile, **args)
|
91
|
+
print(generator.serialize(**args))
|
60
92
|
|
61
93
|
|
62
94
|
if __name__ == "__main__":
|
@@ -675,7 +675,18 @@ class PydanticModule(PydanticTemplateModel):
|
|
675
675
|
return [c.name for c in self.classes.values()]
|
676
676
|
|
677
677
|
|
678
|
-
_some_stdlib_module_names = {
|
678
|
+
_some_stdlib_module_names = {
|
679
|
+
"copy",
|
680
|
+
"datetime",
|
681
|
+
"decimal",
|
682
|
+
"enum",
|
683
|
+
"inspect",
|
684
|
+
"os",
|
685
|
+
"re",
|
686
|
+
"sys",
|
687
|
+
"typing",
|
688
|
+
"dataclasses",
|
689
|
+
}
|
679
690
|
"""
|
680
691
|
sys.stdlib_module_names is only present in 3.10 and later
|
681
692
|
so we make a cheap copy of the stdlib modules that we commonly use here,
|
@@ -71,7 +71,7 @@ class PythonIfAbsentProcessor(IfAbsentProcessor):
|
|
71
71
|
def map_enum_default_value(
|
72
72
|
self, enum_name: EnumDefinitionName, permissible_value_name: str, slot: SlotDefinition, cls: ClassDefinition
|
73
73
|
):
|
74
|
-
return f"{
|
74
|
+
return f"'{permissible_value_name}'"
|
75
75
|
|
76
76
|
def map_nc_name_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
77
77
|
raise NotImplementedError()
|
linkml/generators/pythongen.py
CHANGED
@@ -29,6 +29,7 @@ from rdflib import URIRef
|
|
29
29
|
|
30
30
|
import linkml
|
31
31
|
from linkml._version import __version__
|
32
|
+
from linkml.generators.pydanticgen.template import Import, Imports, ObjectImport
|
32
33
|
from linkml.generators.python.python_ifabsent_processor import PythonIfAbsentProcessor
|
33
34
|
from linkml.utils.generator import Generator, shared_arguments
|
34
35
|
|
@@ -127,13 +128,120 @@ class PythonGenerator(Generator):
|
|
127
128
|
self.emit_prefixes.add(type_prefix)
|
128
129
|
|
129
130
|
def gen_schema(self) -> str:
|
131
|
+
all_imports = Imports()
|
132
|
+
# generic imports
|
133
|
+
all_imports = (
|
134
|
+
all_imports
|
135
|
+
+ Import(module="dataclasses")
|
136
|
+
+ Import(module="re")
|
137
|
+
+ Import(
|
138
|
+
module="jsonasobj2",
|
139
|
+
objects=[
|
140
|
+
ObjectImport(name="JsonObj"),
|
141
|
+
ObjectImport(name="as_dict"),
|
142
|
+
],
|
143
|
+
)
|
144
|
+
+ Import(
|
145
|
+
module="typing",
|
146
|
+
objects=[
|
147
|
+
ObjectImport(name="Optional"),
|
148
|
+
ObjectImport(name="List"),
|
149
|
+
ObjectImport(name="Union"),
|
150
|
+
ObjectImport(name="Dict"),
|
151
|
+
ObjectImport(name="ClassVar"),
|
152
|
+
ObjectImport(name="Any"),
|
153
|
+
],
|
154
|
+
)
|
155
|
+
+ Import(
|
156
|
+
module="dataclasses",
|
157
|
+
objects=[
|
158
|
+
ObjectImport(name="dataclass"),
|
159
|
+
],
|
160
|
+
)
|
161
|
+
+ Import(
|
162
|
+
module="datetime",
|
163
|
+
objects=[
|
164
|
+
ObjectImport(name="date"),
|
165
|
+
ObjectImport(name="datetime"),
|
166
|
+
ObjectImport(name="time"),
|
167
|
+
],
|
168
|
+
)
|
169
|
+
)
|
170
|
+
|
130
171
|
# The metamodel uses Enumerations to define itself, so don't import if we are generating the metamodel
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
172
|
+
if not self.genmeta:
|
173
|
+
all_imports = all_imports + Import(
|
174
|
+
module="linkml_runtime.linkml_model.meta",
|
175
|
+
objects=[
|
176
|
+
ObjectImport(name="EnumDefinition"),
|
177
|
+
ObjectImport(name="PermissibleValue"),
|
178
|
+
ObjectImport(name="PvFormulaOptions"),
|
179
|
+
],
|
180
|
+
)
|
181
|
+
# linkml imports
|
182
|
+
all_imports = (
|
183
|
+
all_imports
|
184
|
+
+ Import(
|
185
|
+
module="linkml_runtime.utils.slot",
|
186
|
+
objects=[
|
187
|
+
ObjectImport(name="Slot"),
|
188
|
+
],
|
189
|
+
)
|
190
|
+
+ Import(
|
191
|
+
module="linkml_runtime.utils.metamodelcore",
|
192
|
+
objects=[
|
193
|
+
ObjectImport(name="empty_list"),
|
194
|
+
ObjectImport(name="empty_dict"),
|
195
|
+
ObjectImport(name="bnode"),
|
196
|
+
],
|
197
|
+
)
|
198
|
+
+ Import(
|
199
|
+
module="linkml_runtime.utils.yamlutils",
|
200
|
+
objects=[
|
201
|
+
ObjectImport(name="YAMLRoot"),
|
202
|
+
ObjectImport(name="extended_str"),
|
203
|
+
ObjectImport(name="extended_float"),
|
204
|
+
ObjectImport(name="extended_int"),
|
205
|
+
],
|
206
|
+
)
|
207
|
+
+ Import(
|
208
|
+
module="linkml_runtime.utils.dataclass_extensions_376",
|
209
|
+
objects=[
|
210
|
+
ObjectImport(name="dataclasses_init_fn_with_kwargs"),
|
211
|
+
],
|
212
|
+
)
|
213
|
+
+ Import(
|
214
|
+
module="linkml_runtime.utils.formatutils",
|
215
|
+
objects=[
|
216
|
+
ObjectImport(name="camelcase"),
|
217
|
+
ObjectImport(name="underscore"),
|
218
|
+
ObjectImport(name="sfx"),
|
219
|
+
],
|
220
|
+
)
|
221
|
+
)
|
222
|
+
|
223
|
+
# handler import
|
224
|
+
all_imports = all_imports + Import(
|
225
|
+
module="linkml_runtime.utils.enumerations", objects=[ObjectImport(name="EnumDefinitionImpl")]
|
226
|
+
)
|
227
|
+
# other imports
|
228
|
+
all_imports = (
|
229
|
+
all_imports
|
230
|
+
+ Import(
|
231
|
+
module="rdflib",
|
232
|
+
objects=[
|
233
|
+
ObjectImport(name="Namespace"),
|
234
|
+
ObjectImport(name="URIRef"),
|
235
|
+
],
|
236
|
+
)
|
237
|
+
+ Import(
|
238
|
+
module="linkml_runtime.utils.curienamespace",
|
239
|
+
objects=[
|
240
|
+
ObjectImport(name="CurieNamespace"),
|
241
|
+
],
|
242
|
+
)
|
135
243
|
)
|
136
|
-
|
244
|
+
|
137
245
|
split_description = ""
|
138
246
|
if self.schema.description:
|
139
247
|
split_description = "\n# ".join(d for d in self.schema.description.split("\n") if d is not None)
|
@@ -151,21 +259,7 @@ class PythonGenerator(Generator):
|
|
151
259
|
# description: {split_description}
|
152
260
|
# license: {be(self.schema.license)}
|
153
261
|
|
154
|
-
|
155
|
-
import re
|
156
|
-
from jsonasobj2 import JsonObj, as_dict
|
157
|
-
from typing import Optional, List, Union, Dict, ClassVar, Any
|
158
|
-
from dataclasses import dataclass
|
159
|
-
from datetime import date, datetime, time
|
160
|
-
{enumimports}
|
161
|
-
from linkml_runtime.utils.slot import Slot
|
162
|
-
from linkml_runtime.utils.metamodelcore import empty_list, empty_dict, bnode
|
163
|
-
from linkml_runtime.utils.yamlutils import YAMLRoot, extended_str, extended_float, extended_int
|
164
|
-
from linkml_runtime.utils.dataclass_extensions_376 import dataclasses_init_fn_with_kwargs
|
165
|
-
from linkml_runtime.utils.formatutils import camelcase, underscore, sfx
|
166
|
-
{handlerimport}
|
167
|
-
from rdflib import Namespace, URIRef
|
168
|
-
from linkml_runtime.utils.curienamespace import CurieNamespace
|
262
|
+
{all_imports.render()}
|
169
263
|
{self.gen_imports()}
|
170
264
|
|
171
265
|
metamodel_version = "{self.schema.metamodel_version}"
|
@@ -836,7 +930,15 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
836
930
|
):
|
837
931
|
rlines.append(f"\tself.{aliased_slot_name} = {base_type_name}()")
|
838
932
|
else:
|
839
|
-
if
|
933
|
+
if slot.range in self.schema.enums and slot.ifabsent:
|
934
|
+
# `ifabsent` for an enumeration cannot be assigned to
|
935
|
+
# the dataclass field default, because it would be a
|
936
|
+
# mutable. `python_ifabsent_processor.py` can specify
|
937
|
+
# the default as string and here that string gets
|
938
|
+
# converted into an object attribute invocation
|
939
|
+
# TODO: fix according https://github.com/linkml/linkml/pull/2329#discussion_r1797534588
|
940
|
+
rlines.append(f"\tself.{aliased_slot_name} = getattr({slot.range}, self.{aliased_slot_name})")
|
941
|
+
elif (
|
840
942
|
(self.class_identifier(slot.range) and not slot.inlined)
|
841
943
|
or slot.range in self.schema.types
|
842
944
|
or slot.range in self.schema.enums
|
@@ -8,8 +8,9 @@ linkml/generators/README.md,sha256=RMzT8EblC_GEdPy5WyfXHDBXlFI6k6mz3Cx2sdpcyWI,4
|
|
8
8
|
linkml/generators/__init__.py,sha256=2qSOnibL6KtzBW3KNqVX1gx1LQttrXlOPbbVDLLV2go,1587
|
9
9
|
linkml/generators/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
linkml/generators/common/build.py,sha256=hXf2gf1ox2hiR-D6N-qXt5w5QkISLlo1A-_GurY3yHo,2897
|
11
|
-
linkml/generators/common/ifabsent_processor.py,sha256=
|
11
|
+
linkml/generators/common/ifabsent_processor.py,sha256=hVm6mKq8qciL5Fa53TAtGO8ZJnbRuktvJnaA6-HutTk,13533
|
12
12
|
linkml/generators/common/lifecycle.py,sha256=UOJXlIyWQkvsn0Dao9_ZDzlamJtiINyVJQ6SRj66L9s,4910
|
13
|
+
linkml/generators/common/naming.py,sha256=AsLJ7e87TU_SnAM0Sy7q9HZc-qXvyIuqHt1CoHV-Y3Y,5089
|
13
14
|
linkml/generators/common/template.py,sha256=bjcSNiC85MYl9oGhk0ZpqwZpRxxy_NqxG8XBV-TXXDY,3338
|
14
15
|
linkml/generators/common/type_designators.py,sha256=lgVcIRJJ-yCvVGeP9U_gQZpm77UmJBQo9Bh3lGJITno,1956
|
15
16
|
linkml/generators/csvgen.py,sha256=O20K3IuDxomGJAyNFJmm1_HbPyl6xUK0l-aKY0A3KcM,3253
|
@@ -23,13 +24,13 @@ linkml/generators/docgen/schema.md.jinja2,sha256=xlENfnzNRYgPT_0tdqNFxgklVM4Qf5B
|
|
23
24
|
linkml/generators/docgen/slot.md.jinja2,sha256=dJHugIv9OPTi3FIn8Iez7CzjQizdXrcn0hcmCmXSgV4,3339
|
24
25
|
linkml/generators/docgen/subset.md.jinja2,sha256=EPln_fjlhlkmwEGEp2iPX-9Xm2vVodPZLlRBlYCB_sA,2705
|
25
26
|
linkml/generators/docgen/type.md.jinja2,sha256=QmCMJZrFwP33eHkggBVtypbyrxTb-XZn9vHOYojVaYk,635
|
26
|
-
linkml/generators/docgen.py,sha256=
|
27
|
+
linkml/generators/docgen.py,sha256=iDSu2LsZ7MStrzXdxFwK-oBlVevq2MwBc1n5WzHzTyw,38996
|
27
28
|
linkml/generators/dotgen.py,sha256=vjHveFtKBItHRkowi1I4FHffcn4u4wLpBydYgVxB-vE,5028
|
28
29
|
linkml/generators/erdiagramgen.py,sha256=5_VEPvRL0I819wCjk7T5hWffdBPc_qw3XpQdOYpO7uE,11357
|
29
30
|
linkml/generators/excelgen.py,sha256=MQRRm1wDbg7auUQH9tGb5aRyrp-e4gjXsY10aoLb9LI,7708
|
30
31
|
linkml/generators/golanggen.py,sha256=Qx1GZRIrKn8jNoO-8fJMjkrwV9ZLwR4NwYBW5kGGp9c,5840
|
31
32
|
linkml/generators/golrgen.py,sha256=JB92VDhTuwyaOyxHxB4goIsjZbq4NMf36ESprC3GeBs,3454
|
32
|
-
linkml/generators/graphqlgen.py,sha256
|
33
|
+
linkml/generators/graphqlgen.py,sha256=belU4f89vBHoHBipaMkHiE_ND42IV7L0Zp4akL9sQWk,3484
|
33
34
|
linkml/generators/javagen/example_template.java.jinja2,sha256=ec4CVTv_0zS7V5Y-1E6H4lRraya10gfX7BEMBlu38X4,444
|
34
35
|
linkml/generators/javagen/java_record_template.jinja2,sha256=OQZffLSy_xR3FIhQMltvrYyVeut7l2Q-tzK7AOiVmWs,1729
|
35
36
|
linkml/generators/javagen.py,sha256=BTAXgvOOvRa3AOfaaiVFyuyaqg4XFR_JbO6_7tT_AnQ,5335
|
@@ -52,7 +53,7 @@ linkml/generators/pydanticgen/black.py,sha256=BaJ7b-kMUbIN_cKRT3yCaMEbFwxzj1_U7w
|
|
52
53
|
linkml/generators/pydanticgen/build.py,sha256=xWNnxaNz0LqfNpgUuaUeVdvmXyFASwv5QED3bEaUVaM,4093
|
53
54
|
linkml/generators/pydanticgen/includes.py,sha256=kDAwonKbhTFzrFa2TrmTYuOSHXuzDy5WHgBaI5nVY6c,504
|
54
55
|
linkml/generators/pydanticgen/pydanticgen.py,sha256=IEmSPr9zu1dkgIMFvVKDswokhQvM_weXdmVNQ3l3eNQ,49938
|
55
|
-
linkml/generators/pydanticgen/template.py,sha256=
|
56
|
+
linkml/generators/pydanticgen/template.py,sha256=wjUMiTay_EFh8zp5LfytBsgZJGlKjV96PVnBgI21KVc,24886
|
56
57
|
linkml/generators/pydanticgen/templates/attribute.py.jinja,sha256=uMyVKpO5uQkAFlIvIBwA6FFZl_BSIotfwCorrSwMA3Q,873
|
57
58
|
linkml/generators/pydanticgen/templates/base_model.py.jinja,sha256=6KRQ1ZVHh1uwZnYL3qxBnyit8_DNE7D-4l2-1wsRsG0,406
|
58
59
|
linkml/generators/pydanticgen/templates/class.py.jinja,sha256=GqR2t5y0vLmvUpT2vyP5VX42PO9AkAnGE-U2RZPa9AI,690
|
@@ -63,8 +64,8 @@ linkml/generators/pydanticgen/templates/imports.py.jinja,sha256=vwAOWV98rM4reAUX
|
|
63
64
|
linkml/generators/pydanticgen/templates/module.py.jinja,sha256=hqZKsJ72yUioBkZAunscVgLSe58OFHf0z6-so_b43U4,526
|
64
65
|
linkml/generators/pydanticgen/templates/validator.py.jinja,sha256=5vDpqdfxLpie4BzcyjE82agO6NsThIZjNlcz6gq_kFg,465
|
65
66
|
linkml/generators/python/__init__.py,sha256=NS9fPEuAo0punDplD91F9mcViXMJJv04Q5NkFcKPNvY,40
|
66
|
-
linkml/generators/python/python_ifabsent_processor.py,sha256=
|
67
|
-
linkml/generators/pythongen.py,sha256
|
67
|
+
linkml/generators/python/python_ifabsent_processor.py,sha256=io-bLevkdy2xgKl0kqGB3o5iR7O7P1uE4qWEhKt-GcM,3838
|
68
|
+
linkml/generators/pythongen.py,sha256=-W8ZgvgDP-Hm4Q9weQiB7xtXMJyadtyfZ6BHC1CmClw,56127
|
68
69
|
linkml/generators/rdfgen.py,sha256=OT8oS8MPUVf5UK3OKvRWgAh9iQiWwdyWJSA69PncOw4,2969
|
69
70
|
linkml/generators/shacl/__init__.py,sha256=KgMOyESGTdKk2Vhx9uuUYgEJPuJQ-iT1vDQVIMoiXCM,58
|
70
71
|
linkml/generators/shacl/shacl_data_type.py,sha256=gX3Y2KyTVPwMyef0cJMTEpU9h0oH-H0ThVDORdNW170,1828
|
@@ -152,8 +153,8 @@ linkml/workspaces/datamodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
152
153
|
linkml/workspaces/datamodel/workspaces.py,sha256=4HdkqweGNfMPqnB1_Onc9DcTfkhoagTRcqruh08nRoI,14905
|
153
154
|
linkml/workspaces/datamodel/workspaces.yaml,sha256=EjVrwPpeRZqJRjuGyyDRxxFzuv55SiLIXPBRUG6HStU,4233
|
154
155
|
linkml/workspaces/example_runner.py,sha256=T7sHQetV6a-dG5Kjuh2L4rHQ6-SPG3Kj1J1nPB_1uoQ,11757
|
155
|
-
linkml-1.8.
|
156
|
-
linkml-1.8.
|
157
|
-
linkml-1.8.
|
158
|
-
linkml-1.8.
|
159
|
-
linkml-1.8.
|
156
|
+
linkml-1.8.5.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
|
157
|
+
linkml-1.8.5.dist-info/entry_points.txt,sha256=jvnPJ8UTca4_L-Dh8OdUm5td8I3ZFlKRhMPjqRLtaSg,2084
|
158
|
+
linkml-1.8.5.dist-info/METADATA,sha256=9isA7CyNTTGO9A1qH-RvsfN0TH2JGw6ss0RBEwuRa5k,3873
|
159
|
+
linkml-1.8.5.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
160
|
+
linkml-1.8.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|