shacl2code 0.0.12__tar.gz → 0.0.13__tar.gz
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.
- {shacl2code-0.0.12 → shacl2code-0.0.13}/.github/workflows/test.yaml +5 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/PKG-INFO +1 -2
- {shacl2code-0.0.12 → shacl2code-0.0.13}/pyproject.toml +0 -1
- shacl2code-0.0.13/src/shacl2code/context.py +277 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/common.py +9 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/templates/jsonschema.j2 +5 -5
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/templates/python.j2 +49 -46
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/model.py +1 -1
- shacl2code-0.0.13/src/shacl2code/version.py +1 -0
- shacl2code-0.0.13/tests/__init__.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/conftest.py +4 -5
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/context.j2 +2 -2
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/model/test-context.json +61 -8
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/model/test.ttl +0 -2
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/python/roundtrip.json +3 -3
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/expect/jsonschema/test-context.json +17 -17
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/expect/python/test-context.py +52 -49
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/expect/python/test.py +31 -26
- shacl2code-0.0.13/tests/http.py +74 -0
- shacl2code-0.0.13/tests/test_context.py +136 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/test_jsonschema.py +6 -6
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/test_model_source.py +60 -7
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/test_python.py +14 -5
- shacl2code-0.0.12/src/shacl2code/context.py +0 -167
- shacl2code-0.0.12/src/shacl2code/version.py +0 -1
- shacl2code-0.0.12/tests/test_context.py +0 -144
- {shacl2code-0.0.12 → shacl2code-0.0.13}/.flake8 +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/.github/workflows/coverage-generate.yaml +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/.github/workflows/coverage-report.yaml +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/.github/workflows/publish.yaml +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/.gitignore +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/LICENSE +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/README.md +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/__init__.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/__main__.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/__init__.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/jinja.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/jsonschema.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/lang.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/lang/python.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/main.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/src/shacl2code/urlcontext.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/abort.j2 +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/bad-id.j2 +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/bad-node-kind.ttl +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/bad-pattern-class.ttl +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/bad-pattern-integer.ttl +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/bad-reference.jsonld +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/context-url.j2 +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/missing-range.ttl +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/python/bad-object-type-inline.json +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/python/bad-object-type-ref-after.json +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/python/bad-object-type-ref-before.json +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/python/links.json +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/data/raw.j2 +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/expect/jsonschema/test.json +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/expect/make_expect.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/expect/raw/test-context.txt +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/expect/raw/test.txt +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/test_cli.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/test_common_jinja.py +0 -0
- {shacl2code-0.0.12 → shacl2code-0.0.13}/tests/test_common_prefix.py +0 -0
|
@@ -22,10 +22,15 @@ jobs:
|
|
|
22
22
|
uses: actions/setup-python@v4
|
|
23
23
|
with:
|
|
24
24
|
python-version: ${{ matrix.python-version }}
|
|
25
|
+
- name: Setup Node
|
|
26
|
+
uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
25
29
|
- name: Install dependencies
|
|
26
30
|
run: |
|
|
27
31
|
python -m pip install --upgrade pip
|
|
28
32
|
pip install build
|
|
33
|
+
npm install -g jsonld-cli
|
|
29
34
|
- name: Build package
|
|
30
35
|
run: |
|
|
31
36
|
python -m build
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: shacl2code
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.13
|
|
4
4
|
Summary: Convert SHACL model file to code bindings
|
|
5
5
|
Project-URL: Homepage, https://github.com/JPEWdev/shacl2code
|
|
6
6
|
Project-URL: Repository, https://github.com/JPEWdev/shacl2code.git
|
|
@@ -25,7 +25,6 @@ Requires-Dist: flake8>=7.0.0; extra == 'dev'
|
|
|
25
25
|
Requires-Dist: jsonschema>=4.21.1; extra == 'dev'
|
|
26
26
|
Requires-Dist: pyshacl>=0.25.0; extra == 'dev'
|
|
27
27
|
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
28
|
-
Requires-Dist: pytest-server-fixtures>=1.7; extra == 'dev'
|
|
29
28
|
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
30
29
|
Description-Content-Type: text/markdown
|
|
31
30
|
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Copyright (c) 2024 Joshua Watt
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def foreach_context(contexts):
|
|
10
|
+
for ctx in contexts:
|
|
11
|
+
for name, value in ctx.items():
|
|
12
|
+
yield name, value
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Context(object):
|
|
16
|
+
def __init__(self, contexts=[]):
|
|
17
|
+
self.contexts = [c for c in contexts if c]
|
|
18
|
+
self.__vocabs = []
|
|
19
|
+
self.__expanded_iris = {}
|
|
20
|
+
self.__expanded_ids = {}
|
|
21
|
+
self.__expanded_vocabs = {}
|
|
22
|
+
self.__compacted_iris = {}
|
|
23
|
+
self.__compacted_ids = {}
|
|
24
|
+
self.__compacted_vocabs = {}
|
|
25
|
+
|
|
26
|
+
@contextmanager
|
|
27
|
+
def vocab_push(self, vocab):
|
|
28
|
+
if not vocab:
|
|
29
|
+
yield self
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
self.__vocabs.append(vocab)
|
|
33
|
+
try:
|
|
34
|
+
yield self
|
|
35
|
+
finally:
|
|
36
|
+
self.__vocabs.pop()
|
|
37
|
+
|
|
38
|
+
def __vocab_key(self):
|
|
39
|
+
if not self.__vocabs:
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
return self.__vocabs[-1]
|
|
43
|
+
|
|
44
|
+
def __get_vocab_contexts(self):
|
|
45
|
+
contexts = []
|
|
46
|
+
|
|
47
|
+
for v in self.__vocabs:
|
|
48
|
+
for name, value in foreach_context(self.contexts):
|
|
49
|
+
if (
|
|
50
|
+
isinstance(value, dict)
|
|
51
|
+
and value["@type"] == "@vocab"
|
|
52
|
+
and v == self.expand_iri(value["@id"])
|
|
53
|
+
):
|
|
54
|
+
if "@context" in value:
|
|
55
|
+
contexts.insert(0, value["@context"])
|
|
56
|
+
|
|
57
|
+
contexts.extend(self.contexts)
|
|
58
|
+
|
|
59
|
+
return contexts
|
|
60
|
+
|
|
61
|
+
def __choose_possible(
|
|
62
|
+
self,
|
|
63
|
+
term,
|
|
64
|
+
default,
|
|
65
|
+
contexts,
|
|
66
|
+
*,
|
|
67
|
+
vocab=False,
|
|
68
|
+
base=False,
|
|
69
|
+
exact=False,
|
|
70
|
+
prefix=False,
|
|
71
|
+
):
|
|
72
|
+
def remove_prefix(_id, value):
|
|
73
|
+
expanded_id = self.expand_iri(_id)
|
|
74
|
+
expanded_value = self.expand_iri(value)
|
|
75
|
+
|
|
76
|
+
possible = set()
|
|
77
|
+
if expanded_id.startswith(expanded_value):
|
|
78
|
+
tmp_id = _id[len(expanded_value) :]
|
|
79
|
+
possible.add(tmp_id)
|
|
80
|
+
return possible
|
|
81
|
+
|
|
82
|
+
def helper(term):
|
|
83
|
+
possible = set()
|
|
84
|
+
for name, value in foreach_context(contexts):
|
|
85
|
+
if name == "@vocab":
|
|
86
|
+
if vocab:
|
|
87
|
+
possible |= remove_prefix(term, value)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if name == "@base":
|
|
91
|
+
if base:
|
|
92
|
+
possible |= remove_prefix(term, value)
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if isinstance(value, dict):
|
|
96
|
+
value = value["@id"]
|
|
97
|
+
|
|
98
|
+
if term == self.expand_iri(value):
|
|
99
|
+
if exact and name not in possible:
|
|
100
|
+
possible.add(name)
|
|
101
|
+
possible |= helper(name)
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
if not prefix:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
if term.startswith(value) and value.endswith("/"):
|
|
108
|
+
tmp_id = name + ":" + term[len(value) :].lstrip("/")
|
|
109
|
+
if tmp_id not in possible:
|
|
110
|
+
possible.add(tmp_id)
|
|
111
|
+
possible |= helper(tmp_id)
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
if term.startswith(value + ":") and self.expand_iri(value).endswith(
|
|
115
|
+
"/"
|
|
116
|
+
):
|
|
117
|
+
tmp_id = name + term[len(value) :]
|
|
118
|
+
if tmp_id not in possible:
|
|
119
|
+
possible.add(tmp_id)
|
|
120
|
+
possible |= helper(tmp_id)
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
return possible
|
|
124
|
+
|
|
125
|
+
possible = helper(term)
|
|
126
|
+
|
|
127
|
+
if not possible:
|
|
128
|
+
return default
|
|
129
|
+
|
|
130
|
+
# To select from the possible identifiers, choose the one that has the
|
|
131
|
+
# least context (fewest ":"), then the shortest, and finally
|
|
132
|
+
# alphabetically
|
|
133
|
+
possible = list(possible)
|
|
134
|
+
possible.sort(key=lambda p: (p.count(":"), len(p), p))
|
|
135
|
+
return possible[0]
|
|
136
|
+
|
|
137
|
+
def compact_iri(self, iri):
|
|
138
|
+
if iri not in self.__compacted_iris:
|
|
139
|
+
self.__compacted_iris[iri] = self.__choose_possible(
|
|
140
|
+
iri,
|
|
141
|
+
iri,
|
|
142
|
+
self.contexts,
|
|
143
|
+
exact=True,
|
|
144
|
+
prefix=True,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return self.__compacted_iris[iri]
|
|
148
|
+
|
|
149
|
+
def compact_id(self, _id):
|
|
150
|
+
if ":" not in _id:
|
|
151
|
+
return _id
|
|
152
|
+
|
|
153
|
+
if _id not in self.__compacted_ids:
|
|
154
|
+
self.__compacted_ids[_id] = self.__choose_possible(
|
|
155
|
+
_id,
|
|
156
|
+
_id,
|
|
157
|
+
self.contexts,
|
|
158
|
+
base=True,
|
|
159
|
+
prefix=True,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return self.__compacted_ids[_id]
|
|
163
|
+
|
|
164
|
+
def compact_vocab(self, term, vocab=None):
|
|
165
|
+
with self.vocab_push(vocab):
|
|
166
|
+
v = self.__vocab_key()
|
|
167
|
+
if v in self.__compacted_vocabs and term in self.__compacted_vocabs[v]:
|
|
168
|
+
return self.__compacted_vocabs[v][term]
|
|
169
|
+
|
|
170
|
+
compact = self.__choose_possible(
|
|
171
|
+
term,
|
|
172
|
+
None,
|
|
173
|
+
self.__get_vocab_contexts(),
|
|
174
|
+
vocab=True,
|
|
175
|
+
exact=True,
|
|
176
|
+
)
|
|
177
|
+
if compact is not None:
|
|
178
|
+
self.__compacted_vocabs.setdefault(v, {})[term] = self.compact_id(
|
|
179
|
+
compact
|
|
180
|
+
)
|
|
181
|
+
return compact
|
|
182
|
+
|
|
183
|
+
# If unable to compact with a vocabulary, compact as an ID
|
|
184
|
+
return self.compact_id(term)
|
|
185
|
+
|
|
186
|
+
def expand_iri(self, iri):
|
|
187
|
+
if iri not in self.__expanded_iris:
|
|
188
|
+
self.__expanded_iris[iri] = self.__expand(
|
|
189
|
+
iri,
|
|
190
|
+
self.contexts,
|
|
191
|
+
exact=True,
|
|
192
|
+
prefix=True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return self.__expanded_iris[iri]
|
|
196
|
+
|
|
197
|
+
def expand_id(self, _id):
|
|
198
|
+
if _id not in self.__expanded_ids:
|
|
199
|
+
self.__expanded_ids[_id] = self.__expand(
|
|
200
|
+
_id,
|
|
201
|
+
self.contexts,
|
|
202
|
+
base=True,
|
|
203
|
+
prefix=True,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return self.__expanded_ids[_id]
|
|
207
|
+
|
|
208
|
+
def expand_vocab(self, term, vocab=None):
|
|
209
|
+
with self.vocab_push(vocab):
|
|
210
|
+
v = self.__vocab_key()
|
|
211
|
+
if v not in self.__expanded_vocabs or term not in self.__expanded_vocabs[v]:
|
|
212
|
+
value = self.__expand(
|
|
213
|
+
term,
|
|
214
|
+
self.__get_vocab_contexts(),
|
|
215
|
+
vocab=True,
|
|
216
|
+
exact=True,
|
|
217
|
+
)
|
|
218
|
+
self.__expanded_vocabs.setdefault(v, {})[term] = self.expand_id(value)
|
|
219
|
+
|
|
220
|
+
return self.__expanded_vocabs[v][term]
|
|
221
|
+
|
|
222
|
+
def __expand(
|
|
223
|
+
self,
|
|
224
|
+
term,
|
|
225
|
+
contexts,
|
|
226
|
+
*,
|
|
227
|
+
base=False,
|
|
228
|
+
exact=False,
|
|
229
|
+
prefix=False,
|
|
230
|
+
vocab=False,
|
|
231
|
+
):
|
|
232
|
+
def helper(term):
|
|
233
|
+
vocabs = []
|
|
234
|
+
bases = []
|
|
235
|
+
prefixes = []
|
|
236
|
+
exacts = []
|
|
237
|
+
is_short = not re.match(r"[^:]+:", term)
|
|
238
|
+
|
|
239
|
+
for name, value in foreach_context(contexts):
|
|
240
|
+
if name == "@vocab":
|
|
241
|
+
if vocab and is_short:
|
|
242
|
+
vocabs.append(helper(value))
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
if name == "@base":
|
|
246
|
+
if base and is_short:
|
|
247
|
+
bases.append(value)
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
if isinstance(value, dict):
|
|
251
|
+
value = value["@id"]
|
|
252
|
+
|
|
253
|
+
if term == name:
|
|
254
|
+
if exact:
|
|
255
|
+
exacts.append(helper(value))
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
if prefix:
|
|
259
|
+
prefixes.append(name)
|
|
260
|
+
|
|
261
|
+
for e in exacts:
|
|
262
|
+
return e
|
|
263
|
+
|
|
264
|
+
if ":" in term:
|
|
265
|
+
p, suffix = term.split(":", 1)
|
|
266
|
+
for name in prefixes:
|
|
267
|
+
if p == name:
|
|
268
|
+
p = self.expand_iri(p)
|
|
269
|
+
if p.endswith("/"):
|
|
270
|
+
return p + suffix
|
|
271
|
+
|
|
272
|
+
for value in vocabs + bases:
|
|
273
|
+
return value + term
|
|
274
|
+
|
|
275
|
+
return term
|
|
276
|
+
|
|
277
|
+
return helper(term)
|
|
@@ -114,6 +114,14 @@ class BasicJinjaRender(object):
|
|
|
114
114
|
d.sort()
|
|
115
115
|
return d
|
|
116
116
|
|
|
117
|
+
def get_all_named_individuals(cls):
|
|
118
|
+
ni = set(i._id for i in cls.named_individuals)
|
|
119
|
+
|
|
120
|
+
for d in get_all_derived(cls):
|
|
121
|
+
ni |= set(i._id for i in classes.get(d).named_individuals)
|
|
122
|
+
|
|
123
|
+
return ni
|
|
124
|
+
|
|
117
125
|
classes = ObjectList(model.classes)
|
|
118
126
|
concrete_classes = ObjectList(
|
|
119
127
|
list(c for c in model.classes if not c.is_abstract)
|
|
@@ -132,6 +140,7 @@ class BasicJinjaRender(object):
|
|
|
132
140
|
|
|
133
141
|
env = {
|
|
134
142
|
"get_all_derived": get_all_derived,
|
|
143
|
+
"get_all_named_individuals": get_all_named_individuals,
|
|
135
144
|
**self.get_extra_env(),
|
|
136
145
|
}
|
|
137
146
|
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
{%- endif %}
|
|
70
70
|
"properties": {
|
|
71
71
|
"{{ class.id_property or "@id" }}": { "$ref": "#/$defs/{{ class.node_kind.split("#")[-1] }}" },
|
|
72
|
-
"{{ context.
|
|
72
|
+
"{{ context.compact_iri("@type") }}": {
|
|
73
73
|
{#- Abstract Extensible classes are weird; any type _except_ the specific class type is allowed #}
|
|
74
74
|
{%- if class.is_abstract and class.is_extensible %}
|
|
75
75
|
"allOf": [
|
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
{%- for d in get_all_derived(class) + [class._id] %}
|
|
99
99
|
{%- set ns.json_refs = ns.json_refs + ["#/$defs/" + varname(*classes.get(d).clsname)] %}
|
|
100
100
|
{%- for n in classes.get(d).named_individuals %}
|
|
101
|
-
{%- if context.
|
|
102
|
-
{%- set ns.named_individuals = ns.named_individuals + [context.
|
|
101
|
+
{%- if context.compact_iri(n._id) != n._id %}
|
|
102
|
+
{%- set ns.named_individuals = ns.named_individuals + [context.compact_iri(n._id)] %}
|
|
103
103
|
{%- endif %}
|
|
104
104
|
{%- endfor %}
|
|
105
105
|
{%- endfor %}
|
|
@@ -267,7 +267,7 @@
|
|
|
267
267
|
"SHACLClass": {
|
|
268
268
|
"type": "object",
|
|
269
269
|
"properties": {
|
|
270
|
-
"{{ context.
|
|
270
|
+
"{{ context.compact_iri("@type") }}": {
|
|
271
271
|
"oneOf": [
|
|
272
272
|
{ "$ref": "#/$defs/IRI" },
|
|
273
273
|
{
|
|
@@ -280,7 +280,7 @@
|
|
|
280
280
|
]
|
|
281
281
|
}
|
|
282
282
|
},
|
|
283
|
-
"required": ["{{ context.
|
|
283
|
+
"required": ["{{ context.compact_iri("@type") }}"]
|
|
284
284
|
},
|
|
285
285
|
"AnyClass": {
|
|
286
286
|
"anyOf": [
|
|
@@ -220,13 +220,34 @@ class FloatProp(Property):
|
|
|
220
220
|
return decoder.read_float()
|
|
221
221
|
|
|
222
222
|
|
|
223
|
-
class
|
|
223
|
+
class IRIProp(Property):
|
|
224
|
+
def __init__(self, context=[], *, pattern=None):
|
|
225
|
+
super().__init__(pattern=pattern)
|
|
226
|
+
self.context = context
|
|
227
|
+
|
|
228
|
+
def compact(self, value):
|
|
229
|
+
for iri, compact in self.context:
|
|
230
|
+
if value == iri:
|
|
231
|
+
return compact
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
def expand(self, value):
|
|
235
|
+
for iri, compact in self.context:
|
|
236
|
+
if value == compact:
|
|
237
|
+
return iri
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
def iri_values(self):
|
|
241
|
+
return (iri for iri, _ in self.context)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class ObjectProp(IRIProp):
|
|
224
245
|
"""
|
|
225
246
|
A scalar SHACL object property of a SHACL object
|
|
226
247
|
"""
|
|
227
248
|
|
|
228
|
-
def __init__(self, cls, required):
|
|
229
|
-
super().__init__()
|
|
249
|
+
def __init__(self, cls, required, context=[]):
|
|
250
|
+
super().__init__(context)
|
|
230
251
|
self.cls = cls
|
|
231
252
|
self.required = required
|
|
232
253
|
|
|
@@ -264,8 +285,7 @@ class ObjectProp(Property):
|
|
|
264
285
|
raise ValueError("Object cannot be None")
|
|
265
286
|
|
|
266
287
|
if isinstance(value, str):
|
|
267
|
-
|
|
268
|
-
encoder.write_iri(value)
|
|
288
|
+
encoder.write_iri(value, self.compact(value))
|
|
269
289
|
return
|
|
270
290
|
|
|
271
291
|
return value.encode(encoder, state)
|
|
@@ -275,7 +295,7 @@ class ObjectProp(Property):
|
|
|
275
295
|
if iri is None:
|
|
276
296
|
return self.cls.decode(decoder, objectset=objectset)
|
|
277
297
|
|
|
278
|
-
iri =
|
|
298
|
+
iri = self.expand(iri) or iri
|
|
279
299
|
|
|
280
300
|
if objectset is None:
|
|
281
301
|
return iri
|
|
@@ -445,36 +465,27 @@ class ListProp(Property):
|
|
|
445
465
|
return ListProxy(self.prop, data=data)
|
|
446
466
|
|
|
447
467
|
|
|
448
|
-
class EnumProp(
|
|
468
|
+
class EnumProp(IRIProp):
|
|
449
469
|
VALID_TYPES = str
|
|
450
470
|
|
|
451
471
|
def __init__(self, values, *, pattern=None):
|
|
452
|
-
super().__init__(pattern=pattern)
|
|
453
|
-
self.values = values
|
|
472
|
+
super().__init__(values, pattern=pattern)
|
|
454
473
|
|
|
455
474
|
def validate(self, value):
|
|
456
475
|
super().validate(value)
|
|
457
476
|
|
|
458
|
-
valid_values =
|
|
477
|
+
valid_values = self.iri_values()
|
|
459
478
|
if value not in valid_values:
|
|
460
479
|
raise ValueError(
|
|
461
480
|
f"'{value}' is not a valid value. Choose one of {' '.join(valid_values)}"
|
|
462
481
|
)
|
|
463
482
|
|
|
464
483
|
def encode(self, encoder, value, state):
|
|
465
|
-
|
|
466
|
-
if iri == value:
|
|
467
|
-
encoder.write_enum(value, self, compact)
|
|
468
|
-
return
|
|
469
|
-
|
|
470
|
-
encoder.write_enum(value, self)
|
|
484
|
+
encoder.write_enum(value, self, self.compact(value))
|
|
471
485
|
|
|
472
486
|
def decode(self, decoder, *, objectset=None):
|
|
473
487
|
v = decoder.read_enum(self)
|
|
474
|
-
|
|
475
|
-
if v == compact:
|
|
476
|
-
return iri
|
|
477
|
-
return v
|
|
488
|
+
return self.expand(v) or v
|
|
478
489
|
|
|
479
490
|
|
|
480
491
|
class NodeKind(Enum):
|
|
@@ -1518,14 +1529,14 @@ class JSONLDDecoder(Decoder):
|
|
|
1518
1529
|
|
|
1519
1530
|
def object_keys(self):
|
|
1520
1531
|
for key in self.data.keys():
|
|
1521
|
-
if key in ("@type", "{{ context.
|
|
1532
|
+
if key in ("@type", "{{ context.compact_iri('@type') }}"):
|
|
1522
1533
|
continue
|
|
1523
1534
|
if self.root and key == "@context":
|
|
1524
1535
|
continue
|
|
1525
1536
|
yield key
|
|
1526
1537
|
|
|
1527
1538
|
def read_object(self):
|
|
1528
|
-
typ = self.__get_value("@type", "{{ context.
|
|
1539
|
+
typ = self.__get_value("@type", "{{ context.compact_iri('@type') }}")
|
|
1529
1540
|
if typ is not None:
|
|
1530
1541
|
return typ, self
|
|
1531
1542
|
|
|
@@ -1717,7 +1728,7 @@ class JSONLDEncoder(Encoder):
|
|
|
1717
1728
|
@contextmanager
|
|
1718
1729
|
def write_object(self, o, _id, needs_id):
|
|
1719
1730
|
self.data = {
|
|
1720
|
-
"{{ context.
|
|
1731
|
+
"{{ context.compact_iri('@type') }}": o.COMPACT_TYPE or o.TYPE,
|
|
1721
1732
|
}
|
|
1722
1733
|
if needs_id:
|
|
1723
1734
|
self.data[o.ID_ALIAS or "@id"] = _id
|
|
@@ -1841,7 +1852,7 @@ class JSONLDInlineEncoder(Encoder):
|
|
|
1841
1852
|
self._write_comma()
|
|
1842
1853
|
|
|
1843
1854
|
self.write("{")
|
|
1844
|
-
self.write_string("{{ context.
|
|
1855
|
+
self.write_string("{{ context.compact_iri('@type') }}")
|
|
1845
1856
|
self.write(":")
|
|
1846
1857
|
self.write_string(o.COMPACT_TYPE or o.TYPE)
|
|
1847
1858
|
self.comma = True
|
|
@@ -1963,26 +1974,6 @@ CONTEXT_URLS = [
|
|
|
1963
1974
|
{%- endfor %}
|
|
1964
1975
|
]
|
|
1965
1976
|
|
|
1966
|
-
_NI_ENCODE_CONTEXT = {
|
|
1967
|
-
{%- for class in classes %}
|
|
1968
|
-
{%- for member in class.named_individuals %}
|
|
1969
|
-
{%- if context.compact(member._id) != member._id %}
|
|
1970
|
-
"{{ member._id }}": "{{ context.compact(member._id) }}",
|
|
1971
|
-
{%- endif %}
|
|
1972
|
-
{%- endfor %}
|
|
1973
|
-
{%- endfor %}
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
_NI_DECODE_CONTEXT = {
|
|
1977
|
-
{%- for class in classes %}
|
|
1978
|
-
{%- for member in class.named_individuals %}
|
|
1979
|
-
{%- if context.compact(member._id) != member._id %}
|
|
1980
|
-
"{{ context.compact(member._id) }}": "{{ member._id }}",
|
|
1981
|
-
{%- endif %}
|
|
1982
|
-
{%- endfor %}
|
|
1983
|
-
{%- endfor %}
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
1977
|
|
|
1987
1978
|
# CLASSES
|
|
1988
1979
|
{%- for class in classes %}
|
|
@@ -1991,7 +1982,7 @@ _NI_DECODE_CONTEXT = {
|
|
|
1991
1982
|
#{{ (" " + l).rstrip() }}
|
|
1992
1983
|
{%- endfor %}
|
|
1993
1984
|
{%- endif %}
|
|
1994
|
-
@register("{{ class._id }}"{%- if context.
|
|
1985
|
+
@register("{{ class._id }}"{%- if context.compact_iri(class._id) != class._id %}, compact_type="{{ context.compact_iri(class._id) }}"{%- endif %}, abstract={{ class.is_abstract }})
|
|
1995
1986
|
class {{ varname(*class.clsname) }}(
|
|
1996
1987
|
{%- if class.is_extensible -%}
|
|
1997
1988
|
SHACLExtensibleObject{{", "}}
|
|
@@ -2042,7 +2033,19 @@ class {{ varname(*class.clsname) }}(
|
|
|
2042
2033
|
{%- endfor %}
|
|
2043
2034
|
])
|
|
2044
2035
|
{%- elif prop.class_id -%}
|
|
2045
|
-
|
|
2036
|
+
{%- set ctx = [] %}
|
|
2037
|
+
{%- for value in get_all_named_individuals(classes.get(prop.class_id)) %}
|
|
2038
|
+
{%- if context.compact_vocab(value, prop.path) != value %}
|
|
2039
|
+
{{- ctx.append((value, context.compact_vocab(value, prop.path))) or "" }}
|
|
2040
|
+
{%- endif %}
|
|
2041
|
+
{%- endfor -%}
|
|
2042
|
+
ObjectProp({{ varname(*classes.get(prop.class_id).clsname) }}, {% if prop.min_count and not is_list %}True{% else %}False{% endif %}{% if ctx %}, context=[
|
|
2043
|
+
{%- for value, compact in ctx %}
|
|
2044
|
+
("{{ value }}", "{{ compact }}"),
|
|
2045
|
+
{%- endfor %}
|
|
2046
|
+
],
|
|
2047
|
+
{%- endif -%}
|
|
2048
|
+
)
|
|
2046
2049
|
{%- else -%}
|
|
2047
2050
|
{% if not prop.datatype in DATATYPE_CLASSES -%}
|
|
2048
2051
|
{{ abort("Unknown data type " + prop.datatype) -}}
|
|
@@ -280,7 +280,7 @@ class Model(object):
|
|
|
280
280
|
"""
|
|
281
281
|
_id = str(_id)
|
|
282
282
|
if _id not in self.compact_ids:
|
|
283
|
-
self.compact_ids[_id] = self.context.
|
|
283
|
+
self.compact_ids[_id] = self.context.compact_iri(_id)
|
|
284
284
|
|
|
285
285
|
if self.compact_ids[_id] == _id and fallback is not None:
|
|
286
286
|
return fallback
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.0.13"
|
|
File without changes
|
|
@@ -9,8 +9,8 @@ import pytest
|
|
|
9
9
|
import shutil
|
|
10
10
|
import subprocess
|
|
11
11
|
import time
|
|
12
|
+
from .http import HTTPTestServer
|
|
12
13
|
|
|
13
|
-
from pytest_server_fixtures.http import SimpleHTTPTestServer
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
THIS_FILE = Path(__file__)
|
|
@@ -21,19 +21,18 @@ MODEL_DIR = THIS_DIR / "data" / "model"
|
|
|
21
21
|
|
|
22
22
|
@pytest.fixture
|
|
23
23
|
def http_server():
|
|
24
|
-
with
|
|
24
|
+
with HTTPTestServer() as s:
|
|
25
25
|
s.start()
|
|
26
26
|
yield s
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@pytest.fixture(scope="session")
|
|
30
30
|
def model_server():
|
|
31
|
-
with
|
|
32
|
-
root = Path(s.document_root)
|
|
31
|
+
with HTTPTestServer() as s:
|
|
33
32
|
for p in MODEL_DIR.iterdir():
|
|
34
33
|
if not p.is_file():
|
|
35
34
|
continue
|
|
36
|
-
shutil.copyfile(p,
|
|
35
|
+
shutil.copyfile(p, s.document_root / p.name)
|
|
37
36
|
s.start()
|
|
38
37
|
yield s.uri
|
|
39
38
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Context: {{ url }}
|
|
3
3
|
{% endfor -%}
|
|
4
4
|
{% for enum in enums %}
|
|
5
|
-
{{ enum._id }}: {{ context.
|
|
5
|
+
{{ enum._id }}: {{ context.compact_iri(enum._id) }}
|
|
6
6
|
{% endfor %}
|
|
7
7
|
{% for class in classes %}
|
|
8
|
-
{{ class._id }}: {{ context.
|
|
8
|
+
{{ class._id }}: {{ context.compact_iri(class._id) }}
|
|
9
9
|
{% endfor %}
|