rdf4j-python 0.1.2__py3-none-any.whl → 0.1.4__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.
- rdf4j_python/_driver/_async_named_graph.py +8 -7
- rdf4j_python/_driver/_async_rdf4j_db.py +6 -5
- rdf4j_python/_driver/_async_repository.py +90 -50
- rdf4j_python/model/__init__.py +0 -2
- rdf4j_python/model/_namespace.py +38 -15
- rdf4j_python/model/_repository_info.py +25 -15
- rdf4j_python/model/repository_config.py +111 -58
- rdf4j_python/model/term.py +15 -9
- rdf4j_python/model/vocabulary.py +1 -0
- rdf4j_python/utils/helpers.py +10 -12
- {rdf4j_python-0.1.2.dist-info → rdf4j_python-0.1.4.dist-info}/METADATA +5 -2
- rdf4j_python-0.1.4.dist-info/RECORD +23 -0
- {rdf4j_python-0.1.2.dist-info → rdf4j_python-0.1.4.dist-info}/WHEEL +1 -1
- rdf4j_python/model/_base_model.py +0 -48
- rdf4j_python/model/_dataset.py +0 -38
- rdf4j_python-0.1.2.dist-info/RECORD +0 -25
- {rdf4j_python-0.1.2.dist-info → rdf4j_python-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {rdf4j_python-0.1.2.dist-info → rdf4j_python-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from typing import Iterable
|
|
2
2
|
|
|
3
|
+
import pyoxigraph as og
|
|
4
|
+
|
|
3
5
|
from rdf4j_python._client import AsyncApiClient
|
|
4
|
-
from rdf4j_python.model import
|
|
5
|
-
from rdf4j_python.model.term import IRI, RDFStatement
|
|
6
|
+
from rdf4j_python.model.term import IRI, Quad, QuadResultSet, Triple
|
|
6
7
|
from rdf4j_python.utils.const import Rdf4jContentType
|
|
7
8
|
from rdf4j_python.utils.helpers import serialize_statements
|
|
8
9
|
|
|
@@ -22,11 +23,11 @@ class AsyncNamedGraph:
|
|
|
22
23
|
self._repository_id = repository_id
|
|
23
24
|
self._graph_uri = graph_uri
|
|
24
25
|
|
|
25
|
-
async def get(self) ->
|
|
26
|
+
async def get(self) -> QuadResultSet:
|
|
26
27
|
"""Fetches all RDF statements from this named graph.
|
|
27
28
|
|
|
28
29
|
Returns:
|
|
29
|
-
|
|
30
|
+
QuadResultSet: RDF data serialized in the requested format.
|
|
30
31
|
|
|
31
32
|
Raises:
|
|
32
33
|
httpx.HTTPStatusError: If the request fails.
|
|
@@ -35,13 +36,13 @@ class AsyncNamedGraph:
|
|
|
35
36
|
headers = {"Accept": Rdf4jContentType.NQUADS}
|
|
36
37
|
response = await self._client.get(path, headers=headers)
|
|
37
38
|
response.raise_for_status()
|
|
38
|
-
return
|
|
39
|
+
return og.parse(response.content, format=og.RdfFormat.N_QUADS)
|
|
39
40
|
|
|
40
|
-
async def add(self, statements: Iterable[
|
|
41
|
+
async def add(self, statements: Iterable[Quad] | Iterable[Triple]):
|
|
41
42
|
"""Adds RDF statements to this named graph.
|
|
42
43
|
|
|
43
44
|
Args:
|
|
44
|
-
statements (Iterable[
|
|
45
|
+
statements (Iterable[Quad] | Iterable[Triple]): RDF statements to add.
|
|
45
46
|
|
|
46
47
|
Raises:
|
|
47
48
|
httpx.HTTPStatusError: If the request fails.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import httpx
|
|
2
|
-
import
|
|
2
|
+
import pyoxigraph as og
|
|
3
3
|
|
|
4
4
|
from rdf4j_python._client import AsyncApiClient
|
|
5
5
|
from rdf4j_python.exception.repo_exception import (
|
|
@@ -66,12 +66,13 @@ class AsyncRdf4j:
|
|
|
66
66
|
"/repositories",
|
|
67
67
|
headers={"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON},
|
|
68
68
|
)
|
|
69
|
-
|
|
70
|
-
response, format=
|
|
69
|
+
query_solutions = og.parse_query_results(
|
|
70
|
+
response.text, format=og.QueryResultsFormat.JSON
|
|
71
71
|
)
|
|
72
|
+
assert isinstance(query_solutions, og.QuerySolutions)
|
|
72
73
|
return [
|
|
73
|
-
RepositoryMetadata.
|
|
74
|
-
for
|
|
74
|
+
RepositoryMetadata.from_sparql_query_solution(query_solution)
|
|
75
|
+
for query_solution in query_solutions
|
|
75
76
|
]
|
|
76
77
|
|
|
77
78
|
async def get_repository(self, repository_id: str) -> AsyncRdf4JRepository:
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
from typing import Iterable, Optional
|
|
2
2
|
|
|
3
3
|
import httpx
|
|
4
|
-
import
|
|
5
|
-
import rdflib.resource
|
|
6
|
-
import rdflib.serializer
|
|
7
|
-
import rdflib.store
|
|
4
|
+
import pyoxigraph as og
|
|
8
5
|
|
|
9
6
|
from rdf4j_python._client import AsyncApiClient
|
|
10
7
|
from rdf4j_python._driver._async_named_graph import AsyncNamedGraph
|
|
@@ -14,21 +11,35 @@ from rdf4j_python.exception.repo_exception import (
|
|
|
14
11
|
RepositoryNotFoundException,
|
|
15
12
|
RepositoryUpdateException,
|
|
16
13
|
)
|
|
17
|
-
from rdf4j_python.model import Namespace
|
|
14
|
+
from rdf4j_python.model import Namespace
|
|
18
15
|
from rdf4j_python.model.term import (
|
|
16
|
+
IRI,
|
|
19
17
|
Context,
|
|
20
18
|
Object,
|
|
21
19
|
Predicate,
|
|
22
|
-
|
|
20
|
+
Quad,
|
|
21
|
+
QuadResultSet,
|
|
23
22
|
Subject,
|
|
23
|
+
Triple,
|
|
24
24
|
)
|
|
25
25
|
from rdf4j_python.utils.const import Rdf4jContentType
|
|
26
26
|
from rdf4j_python.utils.helpers import serialize_statements
|
|
27
27
|
|
|
28
|
+
try:
|
|
29
|
+
from SPARQLWrapper import SPARQLWrapper
|
|
30
|
+
|
|
31
|
+
_has_sparql_wrapper = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
_has_sparql_wrapper = False
|
|
34
|
+
|
|
28
35
|
|
|
29
36
|
class AsyncRdf4JRepository:
|
|
30
37
|
"""Asynchronous interface for interacting with an RDF4J repository."""
|
|
31
38
|
|
|
39
|
+
_client: AsyncApiClient
|
|
40
|
+
_repository_id: str
|
|
41
|
+
_sparql_wrapper: Optional["SPARQLWrapper"] = None
|
|
42
|
+
|
|
32
43
|
def __init__(self, client: AsyncApiClient, repository_id: str):
|
|
33
44
|
"""Initializes the repository interface.
|
|
34
45
|
|
|
@@ -39,32 +50,47 @@ class AsyncRdf4JRepository:
|
|
|
39
50
|
self._client = client
|
|
40
51
|
self._repository_id = repository_id
|
|
41
52
|
|
|
53
|
+
async def get_sparql_wrapper(self) -> "SPARQLWrapper":
|
|
54
|
+
"""Returns the SPARQLWrapper for the repository.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
SPARQLWrapper: The SPARQLWrapper for the repository.
|
|
58
|
+
"""
|
|
59
|
+
if not _has_sparql_wrapper:
|
|
60
|
+
raise ImportError(
|
|
61
|
+
"SPARQLWrapper is not installed. Please install it with `pip install rdf4j-python[sparqlwrapper]`"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if self._sparql_wrapper is None:
|
|
65
|
+
self._sparql_wrapper = SPARQLWrapper(
|
|
66
|
+
f"{self._client.get_base_url()}/repositories/{self._repository_id}"
|
|
67
|
+
)
|
|
68
|
+
return self._sparql_wrapper
|
|
69
|
+
|
|
42
70
|
async def query(
|
|
43
71
|
self,
|
|
44
72
|
sparql_query: str,
|
|
45
73
|
infer: bool = True,
|
|
46
|
-
|
|
47
|
-
):
|
|
74
|
+
) -> og.QuerySolutions | og.QueryBoolean:
|
|
48
75
|
"""Executes a SPARQL SELECT query.
|
|
49
76
|
|
|
50
77
|
Args:
|
|
51
78
|
sparql_query (str): The SPARQL query string.
|
|
52
79
|
infer (bool): Whether to include inferred statements. Defaults to True.
|
|
53
|
-
accept (Rdf4jContentType): The expected response format.
|
|
54
80
|
|
|
55
81
|
Returns:
|
|
56
|
-
|
|
82
|
+
og.QuerySolutions | og.QueryBoolean: Parsed query results.
|
|
57
83
|
"""
|
|
58
84
|
path = f"/repositories/{self._repository_id}"
|
|
59
85
|
params = {"query": sparql_query, "infer": str(infer).lower()}
|
|
60
|
-
headers = {"Accept":
|
|
86
|
+
headers = {"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON}
|
|
61
87
|
response = await self._client.get(path, params=params, headers=headers)
|
|
62
88
|
self._handle_repo_not_found_exception(response)
|
|
63
|
-
|
|
64
|
-
return response.json()
|
|
65
|
-
return response.text
|
|
89
|
+
return og.parse_query_results(response.text, format=og.QueryResultsFormat.JSON)
|
|
66
90
|
|
|
67
|
-
async def update(
|
|
91
|
+
async def update(
|
|
92
|
+
self, sparql_update_query: str, content_type: Rdf4jContentType
|
|
93
|
+
) -> None:
|
|
68
94
|
"""Executes a SPARQL UPDATE command.
|
|
69
95
|
|
|
70
96
|
Args:
|
|
@@ -74,11 +100,15 @@ class AsyncRdf4JRepository:
|
|
|
74
100
|
RepositoryNotFoundException: If the repository doesn't exist.
|
|
75
101
|
httpx.HTTPStatusError: If the update fails.
|
|
76
102
|
"""
|
|
103
|
+
# TODO: handle update results
|
|
77
104
|
path = f"/repositories/{self._repository_id}/statements"
|
|
78
|
-
headers = {"Content-Type":
|
|
79
|
-
response = await self._client.post(
|
|
105
|
+
headers = {"Content-Type": content_type}
|
|
106
|
+
response = await self._client.post(
|
|
107
|
+
path, content=sparql_update_query, headers=headers
|
|
108
|
+
)
|
|
80
109
|
self._handle_repo_not_found_exception(response)
|
|
81
|
-
response.
|
|
110
|
+
if response.status_code != httpx.codes.NO_CONTENT:
|
|
111
|
+
raise RepositoryUpdateException(f"Failed to update: {response.text}")
|
|
82
112
|
|
|
83
113
|
async def get_namespaces(self):
|
|
84
114
|
"""Retrieves all namespaces in the repository.
|
|
@@ -92,26 +122,33 @@ class AsyncRdf4JRepository:
|
|
|
92
122
|
path = f"/repositories/{self._repository_id}/namespaces"
|
|
93
123
|
headers = {"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON}
|
|
94
124
|
response = await self._client.get(path, headers=headers)
|
|
95
|
-
result = rdflib.query.Result.parse(
|
|
96
|
-
response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
|
|
97
|
-
)
|
|
98
125
|
self._handle_repo_not_found_exception(response)
|
|
99
|
-
return [Namespace.from_rdflib_binding(binding) for binding in result.bindings]
|
|
100
126
|
|
|
101
|
-
|
|
127
|
+
query_solutions = og.parse_query_results(
|
|
128
|
+
response.text, format=og.QueryResultsFormat.JSON
|
|
129
|
+
)
|
|
130
|
+
assert isinstance(query_solutions, og.QuerySolutions)
|
|
131
|
+
return [
|
|
132
|
+
Namespace.from_sparql_query_solution(query_solution)
|
|
133
|
+
for query_solution in query_solutions
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
async def set_namespace(self, prefix: str, namespace: IRI):
|
|
102
137
|
"""Sets a namespace prefix.
|
|
103
138
|
|
|
104
139
|
Args:
|
|
105
140
|
prefix (str): The namespace prefix.
|
|
106
|
-
namespace (
|
|
141
|
+
namespace (IRI): The namespace URI.
|
|
107
142
|
|
|
108
143
|
Raises:
|
|
109
144
|
RepositoryNotFoundException: If the repository doesn't exist.
|
|
110
145
|
NamespaceException: If the request fails.
|
|
111
146
|
"""
|
|
112
147
|
path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
|
|
113
|
-
headers = {"Content-Type": Rdf4jContentType.NTRIPLES
|
|
114
|
-
response = await self._client.put(
|
|
148
|
+
headers = {"Content-Type": Rdf4jContentType.NTRIPLES}
|
|
149
|
+
response = await self._client.put(
|
|
150
|
+
path, content=namespace.value, headers=headers
|
|
151
|
+
)
|
|
115
152
|
self._handle_repo_not_found_exception(response)
|
|
116
153
|
if response.status_code != httpx.codes.NO_CONTENT:
|
|
117
154
|
raise NamespaceException(f"Failed to set namespace: {response.text}")
|
|
@@ -130,7 +167,7 @@ class AsyncRdf4JRepository:
|
|
|
130
167
|
NamespaceException: If retrieval fails.
|
|
131
168
|
"""
|
|
132
169
|
path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
|
|
133
|
-
headers = {"Accept": Rdf4jContentType.NTRIPLES
|
|
170
|
+
headers = {"Accept": Rdf4jContentType.NTRIPLES}
|
|
134
171
|
response = await self._client.get(path, headers=headers)
|
|
135
172
|
self._handle_repo_not_found_exception(response)
|
|
136
173
|
|
|
@@ -192,7 +229,7 @@ class AsyncRdf4JRepository:
|
|
|
192
229
|
object_: Optional[Object] = None,
|
|
193
230
|
contexts: Optional[list[Context]] = None,
|
|
194
231
|
infer: bool = True,
|
|
195
|
-
) ->
|
|
232
|
+
) -> QuadResultSet:
|
|
196
233
|
"""Retrieves statements matching the given pattern.
|
|
197
234
|
|
|
198
235
|
Args:
|
|
@@ -202,7 +239,7 @@ class AsyncRdf4JRepository:
|
|
|
202
239
|
contexts (Optional[list[Context]]): Filter by context (named graph).
|
|
203
240
|
|
|
204
241
|
Returns:
|
|
205
|
-
|
|
242
|
+
QuadResultSet: QuadResultSet of matching RDF statements.
|
|
206
243
|
|
|
207
244
|
Raises:
|
|
208
245
|
RepositoryNotFoundException: If the repository doesn't exist.
|
|
@@ -211,20 +248,18 @@ class AsyncRdf4JRepository:
|
|
|
211
248
|
params = {}
|
|
212
249
|
|
|
213
250
|
if subject:
|
|
214
|
-
params["subj"] = subject
|
|
251
|
+
params["subj"] = str(subject)
|
|
215
252
|
if predicate:
|
|
216
|
-
params["pred"] = predicate
|
|
253
|
+
params["pred"] = str(predicate)
|
|
217
254
|
if object_:
|
|
218
|
-
params["obj"] = object_
|
|
255
|
+
params["obj"] = str(object_)
|
|
219
256
|
if contexts:
|
|
220
|
-
params["context"] = [ctx
|
|
257
|
+
params["context"] = [str(ctx) for ctx in contexts]
|
|
221
258
|
params["infer"] = str(infer).lower()
|
|
222
259
|
|
|
223
260
|
headers = {"Accept": Rdf4jContentType.NQUADS}
|
|
224
261
|
response = await self._client.get(path, params=params, headers=headers)
|
|
225
|
-
|
|
226
|
-
dataset.parse(data=response.text, format="nquads")
|
|
227
|
-
return dataset
|
|
262
|
+
return og.parse(response.content, format=og.RdfFormat.N_QUADS)
|
|
228
263
|
|
|
229
264
|
async def delete_statements(
|
|
230
265
|
self,
|
|
@@ -250,13 +285,13 @@ class AsyncRdf4JRepository:
|
|
|
250
285
|
params = {}
|
|
251
286
|
|
|
252
287
|
if subject:
|
|
253
|
-
params["subj"] = subject
|
|
288
|
+
params["subj"] = str(subject)
|
|
254
289
|
if predicate:
|
|
255
|
-
params["pred"] = predicate
|
|
290
|
+
params["pred"] = str(predicate)
|
|
256
291
|
if object_:
|
|
257
|
-
params["obj"] = object_
|
|
292
|
+
params["obj"] = str(object_)
|
|
258
293
|
if contexts:
|
|
259
|
-
params["context"] = [ctx
|
|
294
|
+
params["context"] = [str(ctx) for ctx in contexts]
|
|
260
295
|
|
|
261
296
|
response = await self._client.delete(path, params=params)
|
|
262
297
|
self._handle_repo_not_found_exception(response)
|
|
@@ -286,21 +321,26 @@ class AsyncRdf4JRepository:
|
|
|
286
321
|
httpx.HTTPStatusError: If addition fails.
|
|
287
322
|
"""
|
|
288
323
|
path = f"/repositories/{self._repository_id}/statements"
|
|
324
|
+
statement: Triple | Quad
|
|
325
|
+
if context is None:
|
|
326
|
+
statement = Triple(subject, predicate, object)
|
|
327
|
+
else:
|
|
328
|
+
statement = Quad(subject, predicate, object, context)
|
|
329
|
+
|
|
289
330
|
response = await self._client.post(
|
|
290
331
|
path,
|
|
291
|
-
content=serialize_statements([
|
|
332
|
+
content=serialize_statements([statement]),
|
|
292
333
|
headers={"Content-Type": Rdf4jContentType.NQUADS},
|
|
293
334
|
)
|
|
294
335
|
self._handle_repo_not_found_exception(response)
|
|
295
336
|
if response.status_code != httpx.codes.NO_CONTENT:
|
|
296
337
|
raise RepositoryUpdateException(f"Failed to add statement: {response.text}")
|
|
297
338
|
|
|
298
|
-
async def add_statements(self, statements: Iterable[
|
|
339
|
+
async def add_statements(self, statements: Iterable[Quad] | Iterable[Triple]):
|
|
299
340
|
"""Adds a list of RDF statements to the repository.
|
|
300
341
|
|
|
301
342
|
Args:
|
|
302
|
-
statements (Iterable[
|
|
303
|
-
RDFStatement: A tuple of subject, predicate, object, and context.
|
|
343
|
+
statements (Iterable[Quad] | Iterable[Triple]): A list of RDF statements.
|
|
304
344
|
|
|
305
345
|
Raises:
|
|
306
346
|
RepositoryNotFoundException: If the repository doesn't exist.
|
|
@@ -320,26 +360,26 @@ class AsyncRdf4JRepository:
|
|
|
320
360
|
|
|
321
361
|
async def replace_statements(
|
|
322
362
|
self,
|
|
323
|
-
statements: Iterable[
|
|
324
|
-
contexts: Optional[
|
|
363
|
+
statements: Iterable[Quad] | Iterable[Triple],
|
|
364
|
+
contexts: Optional[Iterable[Context]] = None,
|
|
325
365
|
base_uri: Optional[str] = None,
|
|
326
366
|
):
|
|
327
367
|
"""Replaces all repository statements with the given RDF data.
|
|
328
368
|
|
|
329
369
|
Args:
|
|
330
|
-
statements (Iterable[
|
|
331
|
-
contexts (Optional[
|
|
370
|
+
statements (Iterable[Quad] | Iterable[Triple]): RDF statements to load.
|
|
371
|
+
contexts (Optional[Iterable[Context]]): One or more specific contexts to restrict deletion to.
|
|
332
372
|
|
|
333
373
|
Raises:
|
|
334
374
|
RepositoryNotFoundException: If the repository doesn't exist.
|
|
335
375
|
httpx.HTTPStatusError: If the operation fails.
|
|
336
376
|
"""
|
|
337
377
|
path = f"/repositories/{self._repository_id}/statements"
|
|
338
|
-
headers = {"Content-Type": Rdf4jContentType.NQUADS
|
|
378
|
+
headers = {"Content-Type": Rdf4jContentType.NQUADS}
|
|
339
379
|
|
|
340
380
|
params = {}
|
|
341
381
|
if contexts:
|
|
342
|
-
params["context"] = [ctx
|
|
382
|
+
params["context"] = [str(ctx) for ctx in contexts]
|
|
343
383
|
if base_uri:
|
|
344
384
|
params["baseUri"] = base_uri
|
|
345
385
|
|
rdf4j_python/model/__init__.py
CHANGED
rdf4j_python/model/_namespace.py
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from rdflib.namespace import Namespace as RdflibNamespace
|
|
4
|
-
from rdflib.term import Identifier, Variable
|
|
1
|
+
import pyoxigraph as og
|
|
5
2
|
|
|
6
3
|
from rdf4j_python.model.term import IRI
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
|
|
6
|
+
class _Namespace(str):
|
|
7
|
+
def __new__(cls, value: str | bytes) -> "Namespace":
|
|
8
|
+
try:
|
|
9
|
+
rt = str.__new__(cls, value)
|
|
10
|
+
except UnicodeDecodeError:
|
|
11
|
+
rt = str.__new__(cls, value, "utf-8")
|
|
12
|
+
return rt
|
|
13
|
+
|
|
14
|
+
def term(self, name: str) -> IRI:
|
|
15
|
+
return IRI(self + (name if isinstance(name, str) else ""))
|
|
16
|
+
|
|
17
|
+
def __getitem__(self, key: str) -> IRI:
|
|
18
|
+
return self.term(key)
|
|
19
|
+
|
|
20
|
+
def __getattr__(self, name: str) -> IRI:
|
|
21
|
+
if name.startswith("__"):
|
|
22
|
+
raise AttributeError
|
|
23
|
+
return self.term(name)
|
|
24
|
+
|
|
25
|
+
def __repr__(self) -> str:
|
|
26
|
+
return f"Namespace({super().__repr__()})"
|
|
27
|
+
|
|
28
|
+
def __contains__(self, ref: str) -> bool:
|
|
29
|
+
return ref.startswith(self)
|
|
9
30
|
|
|
10
31
|
|
|
11
32
|
class Namespace:
|
|
@@ -14,7 +35,7 @@ class Namespace:
|
|
|
14
35
|
"""
|
|
15
36
|
|
|
16
37
|
_prefix: str
|
|
17
|
-
_namespace:
|
|
38
|
+
_namespace: _Namespace
|
|
18
39
|
|
|
19
40
|
def __init__(self, prefix: str, namespace: str):
|
|
20
41
|
"""
|
|
@@ -25,24 +46,26 @@ class Namespace:
|
|
|
25
46
|
namespace (str): The namespace URI.
|
|
26
47
|
"""
|
|
27
48
|
self._prefix = prefix
|
|
28
|
-
self._namespace =
|
|
49
|
+
self._namespace = _Namespace(namespace)
|
|
29
50
|
|
|
30
51
|
@classmethod
|
|
31
|
-
def
|
|
52
|
+
def from_sparql_query_solution(
|
|
53
|
+
cls, query_solution: og.QuerySolution
|
|
54
|
+
) -> "Namespace":
|
|
32
55
|
"""
|
|
33
|
-
Creates a Namespace from a
|
|
56
|
+
Creates a Namespace from a binding.
|
|
34
57
|
|
|
35
58
|
Args:
|
|
36
|
-
binding (Mapping[Variable, Identifier]): The
|
|
59
|
+
binding (Mapping[Variable, Identifier]): The binding.
|
|
37
60
|
|
|
38
61
|
Returns:
|
|
39
62
|
Namespace: The created Namespace.
|
|
40
63
|
"""
|
|
41
|
-
prefix =
|
|
42
|
-
namespace =
|
|
64
|
+
prefix: og.Literal = query_solution[og.Variable("prefix")]
|
|
65
|
+
namespace: og.NamedNode = query_solution[og.Variable("namespace")]
|
|
43
66
|
return cls(
|
|
44
|
-
prefix=prefix,
|
|
45
|
-
namespace=namespace,
|
|
67
|
+
prefix=prefix.value,
|
|
68
|
+
namespace=namespace.value,
|
|
46
69
|
)
|
|
47
70
|
|
|
48
71
|
def __str__(self):
|
|
@@ -85,7 +108,7 @@ class Namespace:
|
|
|
85
108
|
Returns:
|
|
86
109
|
IRI: The IRI for the term.
|
|
87
110
|
"""
|
|
88
|
-
return
|
|
111
|
+
return self._namespace.term(name)
|
|
89
112
|
|
|
90
113
|
def __getitem__(self, item: str) -> IRI:
|
|
91
114
|
"""
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Mapping
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from ._base_model import _BaseModel
|
|
3
|
+
import pyoxigraph as og
|
|
7
4
|
|
|
8
5
|
|
|
9
6
|
@dataclass
|
|
10
|
-
class RepositoryMetadata
|
|
7
|
+
class RepositoryMetadata:
|
|
11
8
|
"""
|
|
12
9
|
Represents a repository metadata RDF4J.
|
|
13
10
|
"""
|
|
@@ -28,25 +25,38 @@ class RepositoryMetadata(_BaseModel):
|
|
|
28
25
|
return f"Repository(id={self.id}, title={self.title}, uri={self.uri})"
|
|
29
26
|
|
|
30
27
|
@classmethod
|
|
31
|
-
def
|
|
32
|
-
cls,
|
|
28
|
+
def from_sparql_query_solution(
|
|
29
|
+
cls, query_solution: og.QuerySolution
|
|
33
30
|
) -> "RepositoryMetadata":
|
|
34
31
|
"""
|
|
35
|
-
Create a
|
|
36
|
-
represented as a Mapping from rdflib Variables to Identifiers.
|
|
32
|
+
Create a RepositoryMetadata instance from a SPARQL query result.
|
|
37
33
|
|
|
38
34
|
Args:
|
|
39
|
-
|
|
35
|
+
query_solution (og.QuerySolution): The SPARQL query result.
|
|
40
36
|
|
|
41
37
|
Returns:
|
|
42
38
|
RepositoryMetadata: The RepositoryMetadata instance.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If the query solution is missing required fields.
|
|
43
42
|
"""
|
|
44
43
|
|
|
45
44
|
# Construct and return the Repository object
|
|
45
|
+
if query_solution["id"] is None:
|
|
46
|
+
raise ValueError("id is required")
|
|
47
|
+
if query_solution["uri"] is None:
|
|
48
|
+
raise ValueError("uri is required")
|
|
49
|
+
if query_solution["title"] is None:
|
|
50
|
+
raise ValueError("title is required")
|
|
51
|
+
if query_solution["readable"] is None:
|
|
52
|
+
raise ValueError("readable is required")
|
|
53
|
+
if query_solution["writable"] is None:
|
|
54
|
+
raise ValueError("writable is required")
|
|
55
|
+
|
|
46
56
|
return cls(
|
|
47
|
-
id=
|
|
48
|
-
uri=
|
|
49
|
-
title=
|
|
50
|
-
readable=
|
|
51
|
-
writable=
|
|
57
|
+
id=query_solution["id"].value,
|
|
58
|
+
uri=query_solution["uri"].value,
|
|
59
|
+
title=query_solution["title"].value,
|
|
60
|
+
readable=bool(query_solution["readable"].value),
|
|
61
|
+
writable=bool(query_solution["writable"].value),
|
|
52
62
|
)
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
from typing import Any, Dict, Optional
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
3
|
+
from pyoxigraph import Dataset, Quad, RdfFormat, serialize
|
|
4
|
+
|
|
5
|
+
from rdf4j_python.model import Namespace
|
|
6
|
+
from rdf4j_python.model.term import IRI as URIRef
|
|
7
|
+
from rdf4j_python.model.term import BlankNode as BNode
|
|
8
|
+
from rdf4j_python.model.term import Literal
|
|
9
|
+
from rdf4j_python.model.vocabulary import RDF, RDFS, XSD
|
|
5
10
|
|
|
6
11
|
# Define the RDF4J configuration namespace
|
|
7
|
-
CONFIG = Namespace("tag:rdf4j.org,2023:config/")
|
|
12
|
+
CONFIG = Namespace("config", "tag:rdf4j.org,2023:config/")
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
class RepositoryConfig:
|
|
@@ -54,35 +59,32 @@ class RepositoryConfig:
|
|
|
54
59
|
"""
|
|
55
60
|
return self._title
|
|
56
61
|
|
|
57
|
-
def to_turtle(self) ->
|
|
62
|
+
def to_turtle(self) -> bytes | None:
|
|
58
63
|
"""
|
|
59
|
-
Serializes the Repository configuration to Turtle syntax using
|
|
64
|
+
Serializes the Repository configuration to Turtle syntax using .
|
|
60
65
|
|
|
61
66
|
Returns:
|
|
62
|
-
|
|
67
|
+
bytes | None: A UTF-8 encoded Turtle string representing the RDF4J repository configuration.
|
|
63
68
|
The serialization includes the repository ID, optional human-readable title,
|
|
64
69
|
and nested repository implementation configuration if available.
|
|
65
70
|
|
|
66
71
|
Raises:
|
|
67
72
|
ValueError: If any of the configuration values are of unsupported types during serialization.
|
|
68
73
|
"""
|
|
69
|
-
graph =
|
|
70
|
-
graph.bind("rdfs", RDFS)
|
|
71
|
-
graph.bind("config", CONFIG)
|
|
72
|
-
graph.bind("xsd", XSD)
|
|
74
|
+
graph = Dataset()
|
|
73
75
|
repo_node = BNode()
|
|
74
|
-
graph.add((repo_node, RDF["type"], CONFIG["Repository"]))
|
|
76
|
+
graph.add(Quad(repo_node, RDF["type"], CONFIG["Repository"], None))
|
|
75
77
|
|
|
76
|
-
graph.add((repo_node, CONFIG["rep.id"], Literal(self._repo_id)))
|
|
78
|
+
graph.add(Quad(repo_node, CONFIG["rep.id"], Literal(self._repo_id), None))
|
|
77
79
|
|
|
78
80
|
if self._title:
|
|
79
|
-
graph.add((repo_node, RDFS["label"], Literal(self._title)))
|
|
81
|
+
graph.add(Quad(repo_node, RDFS["label"], Literal(self._title), None))
|
|
80
82
|
|
|
81
83
|
if self._impl:
|
|
82
84
|
impl_node = self._impl.add_to_graph(graph)
|
|
83
|
-
graph.add((repo_node, CONFIG["rep.impl"], impl_node))
|
|
85
|
+
graph.add(Quad(repo_node, CONFIG["rep.impl"], impl_node, None))
|
|
84
86
|
|
|
85
|
-
return
|
|
87
|
+
return serialize(graph, format=RdfFormat.TURTLE)
|
|
86
88
|
|
|
87
89
|
class Builder:
|
|
88
90
|
"""
|
|
@@ -168,14 +170,14 @@ class RepositoryConfig:
|
|
|
168
170
|
|
|
169
171
|
class RepositoryImplConfig:
|
|
170
172
|
"""
|
|
171
|
-
Base class for repository implementation configurations using
|
|
173
|
+
Base class for repository implementation configurations using .
|
|
172
174
|
"""
|
|
173
175
|
|
|
174
176
|
def __init__(self, rep_type: str):
|
|
175
177
|
self.rep_type = rep_type
|
|
176
178
|
self.config_params: Dict[str, Any] = {}
|
|
177
179
|
|
|
178
|
-
def add_to_graph(self, graph:
|
|
180
|
+
def add_to_graph(self, graph: Dataset) -> URIRef:
|
|
179
181
|
"""
|
|
180
182
|
Adds the repository implementation configuration to the RDF graph.
|
|
181
183
|
|
|
@@ -183,28 +185,45 @@ class RepositoryImplConfig:
|
|
|
183
185
|
The RDF node representing this configuration.
|
|
184
186
|
"""
|
|
185
187
|
sail_node = BNode()
|
|
186
|
-
graph.add((sail_node, CONFIG["rep.type"], Literal(self.rep_type)))
|
|
188
|
+
graph.add(Quad(sail_node, CONFIG["rep.type"], Literal(self.rep_type), None))
|
|
187
189
|
for key, value in self.config_params.items():
|
|
188
190
|
if isinstance(value, str):
|
|
189
|
-
graph.add((sail_node, CONFIG[key], Literal(value)))
|
|
190
|
-
elif isinstance(value, int):
|
|
191
|
+
graph.add(Quad(sail_node, CONFIG[key], Literal(value), None))
|
|
192
|
+
elif isinstance(value, int) and not isinstance(value, bool):
|
|
191
193
|
graph.add(
|
|
192
|
-
(
|
|
194
|
+
Quad(
|
|
195
|
+
sail_node,
|
|
196
|
+
CONFIG[key],
|
|
197
|
+
Literal(value, datatype=XSD["integer"]),
|
|
198
|
+
None,
|
|
199
|
+
)
|
|
193
200
|
)
|
|
194
201
|
elif isinstance(value, float):
|
|
195
|
-
graph.add(
|
|
202
|
+
graph.add(
|
|
203
|
+
Quad(
|
|
204
|
+
sail_node,
|
|
205
|
+
CONFIG[key],
|
|
206
|
+
Literal(value, datatype=XSD["double"]),
|
|
207
|
+
None,
|
|
208
|
+
)
|
|
209
|
+
)
|
|
196
210
|
elif isinstance(value, bool):
|
|
197
211
|
graph.add(
|
|
198
|
-
(
|
|
212
|
+
Quad(
|
|
213
|
+
sail_node,
|
|
214
|
+
CONFIG[key],
|
|
215
|
+
Literal(str(value).lower(), datatype=XSD["boolean"]),
|
|
216
|
+
None,
|
|
217
|
+
)
|
|
199
218
|
)
|
|
200
219
|
elif isinstance(value, list):
|
|
201
220
|
for item in value:
|
|
202
|
-
graph.add((sail_node, CONFIG[key], URIRef(item)))
|
|
221
|
+
graph.add(Quad(sail_node, CONFIG[key], URIRef(item), None))
|
|
203
222
|
elif isinstance(value, RepositoryImplConfig) or isinstance(
|
|
204
223
|
value, SailConfig
|
|
205
224
|
):
|
|
206
225
|
nested_node = value.add_to_graph(graph)
|
|
207
|
-
graph.add((sail_node, CONFIG[key], nested_node))
|
|
226
|
+
graph.add(Quad(sail_node, CONFIG[key], nested_node, None))
|
|
208
227
|
else:
|
|
209
228
|
raise ValueError(f"Unsupported configuration value type: {type(value)}")
|
|
210
229
|
return sail_node
|
|
@@ -212,7 +231,7 @@ class RepositoryImplConfig:
|
|
|
212
231
|
|
|
213
232
|
class SPARQLRepositoryConfig(RepositoryImplConfig):
|
|
214
233
|
"""
|
|
215
|
-
Configuration for a SPARQLRepository
|
|
234
|
+
Configuration for a SPARQLRepository.
|
|
216
235
|
"""
|
|
217
236
|
|
|
218
237
|
TYPE = "openrdf:SPARQLRepository"
|
|
@@ -265,7 +284,7 @@ class SPARQLRepositoryConfig(RepositoryImplConfig):
|
|
|
265
284
|
|
|
266
285
|
class HTTPRepositoryConfig(RepositoryImplConfig):
|
|
267
286
|
"""
|
|
268
|
-
Configuration for an HTTPRepository
|
|
287
|
+
Configuration for an HTTPRepository.
|
|
269
288
|
"""
|
|
270
289
|
|
|
271
290
|
TYPE = "openrdf:HTTPRepository"
|
|
@@ -320,7 +339,7 @@ class HTTPRepositoryConfig(RepositoryImplConfig):
|
|
|
320
339
|
|
|
321
340
|
class SailRepositoryConfig(RepositoryImplConfig):
|
|
322
341
|
"""
|
|
323
|
-
Configuration for a SailRepository
|
|
342
|
+
Configuration for a SailRepository.
|
|
324
343
|
"""
|
|
325
344
|
|
|
326
345
|
TYPE = "openrdf:SailRepository"
|
|
@@ -329,7 +348,7 @@ class SailRepositoryConfig(RepositoryImplConfig):
|
|
|
329
348
|
super().__init__(rep_type=SailRepositoryConfig.TYPE)
|
|
330
349
|
self.config_params["sail.impl"] = sail_impl
|
|
331
350
|
|
|
332
|
-
def add_to_graph(self, graph:
|
|
351
|
+
def add_to_graph(self, graph: Dataset) -> URIRef:
|
|
333
352
|
"""
|
|
334
353
|
Adds the SailRepository configuration to the RDF graph.
|
|
335
354
|
"""
|
|
@@ -364,7 +383,7 @@ class SailRepositoryConfig(RepositoryImplConfig):
|
|
|
364
383
|
|
|
365
384
|
class DatasetRepositoryConfig(RepositoryImplConfig):
|
|
366
385
|
"""
|
|
367
|
-
Configuration for a DatasetRepository using
|
|
386
|
+
Configuration for a DatasetRepository using .
|
|
368
387
|
"""
|
|
369
388
|
|
|
370
389
|
TYPE = "openrdf:DatasetRepository"
|
|
@@ -373,7 +392,7 @@ class DatasetRepositoryConfig(RepositoryImplConfig):
|
|
|
373
392
|
super().__init__(rep_type=DatasetRepositoryConfig.TYPE)
|
|
374
393
|
self.config_params["delegate"] = delegate
|
|
375
394
|
|
|
376
|
-
def add_to_graph(self, graph:
|
|
395
|
+
def add_to_graph(self, graph: Dataset) -> URIRef:
|
|
377
396
|
"""
|
|
378
397
|
Adds the DatasetRepository configuration to the RDF Graph
|
|
379
398
|
"""
|
|
@@ -390,7 +409,7 @@ class DatasetRepositoryConfig(RepositoryImplConfig):
|
|
|
390
409
|
|
|
391
410
|
class SailConfig:
|
|
392
411
|
"""
|
|
393
|
-
Base class for SAIL configurations using
|
|
412
|
+
Base class for SAIL configurations using .
|
|
394
413
|
"""
|
|
395
414
|
|
|
396
415
|
def __init__(
|
|
@@ -410,7 +429,7 @@ class SailConfig:
|
|
|
410
429
|
default_query_evaluation_mode
|
|
411
430
|
)
|
|
412
431
|
|
|
413
|
-
def add_to_graph(self, graph:
|
|
432
|
+
def add_to_graph(self, graph: Dataset) -> URIRef:
|
|
414
433
|
"""
|
|
415
434
|
Adds the SAIL configuration to the RDF graph.
|
|
416
435
|
|
|
@@ -418,28 +437,45 @@ class SailConfig:
|
|
|
418
437
|
The RDF node representing this configuration.
|
|
419
438
|
"""
|
|
420
439
|
sail_node = BNode()
|
|
421
|
-
graph.add((sail_node, CONFIG["sail.type"], Literal(self.sail_type)))
|
|
440
|
+
graph.add(Quad(sail_node, CONFIG["sail.type"], Literal(self.sail_type), None))
|
|
422
441
|
for key, value in self.config_params.items():
|
|
423
442
|
if isinstance(value, str):
|
|
424
|
-
graph.add((sail_node, CONFIG[key], Literal(value)))
|
|
425
|
-
elif isinstance(value,
|
|
443
|
+
graph.add(Quad(sail_node, CONFIG[key], Literal(value), None))
|
|
444
|
+
elif isinstance(value, bool):
|
|
426
445
|
graph.add(
|
|
427
|
-
(
|
|
446
|
+
Quad(
|
|
447
|
+
sail_node,
|
|
448
|
+
CONFIG[key],
|
|
449
|
+
Literal(str(value).lower(), datatype=XSD["boolean"]),
|
|
450
|
+
None,
|
|
451
|
+
)
|
|
452
|
+
)
|
|
453
|
+
elif isinstance(value, int) and not isinstance(value, bool):
|
|
454
|
+
graph.add(
|
|
455
|
+
Quad(
|
|
456
|
+
sail_node,
|
|
457
|
+
CONFIG[key],
|
|
458
|
+
Literal(str(value), datatype=XSD["integer"]),
|
|
459
|
+
None,
|
|
460
|
+
)
|
|
428
461
|
)
|
|
429
462
|
elif isinstance(value, float):
|
|
430
|
-
graph.add((sail_node, CONFIG[key], Literal(value, datatype=XSD.double)))
|
|
431
|
-
elif isinstance(value, bool):
|
|
432
463
|
graph.add(
|
|
433
|
-
(
|
|
464
|
+
Quad(
|
|
465
|
+
sail_node,
|
|
466
|
+
CONFIG[key],
|
|
467
|
+
Literal(str(value), datatype=XSD["double"]),
|
|
468
|
+
None,
|
|
469
|
+
)
|
|
434
470
|
)
|
|
435
471
|
elif isinstance(value, list):
|
|
436
472
|
for item in value:
|
|
437
|
-
graph.add((sail_node, CONFIG[key], URIRef(item)))
|
|
473
|
+
graph.add(Quad(sail_node, CONFIG[key], URIRef(item), None))
|
|
438
474
|
elif isinstance(value, SailConfig) or isinstance(
|
|
439
475
|
value, RepositoryImplConfig
|
|
440
476
|
):
|
|
441
477
|
nested_node = value.add_to_graph(graph)
|
|
442
|
-
graph.add((sail_node, CONFIG[key], nested_node))
|
|
478
|
+
graph.add(Quad(sail_node, CONFIG[key], nested_node, None))
|
|
443
479
|
else:
|
|
444
480
|
raise ValueError(f"Unsupported configuration value type: {type(value)}")
|
|
445
481
|
return sail_node
|
|
@@ -447,7 +483,7 @@ class SailConfig:
|
|
|
447
483
|
|
|
448
484
|
class MemoryStoreConfig(SailConfig):
|
|
449
485
|
"""
|
|
450
|
-
Configuration for a MemoryStore using
|
|
486
|
+
Configuration for a MemoryStore using .
|
|
451
487
|
"""
|
|
452
488
|
|
|
453
489
|
TYPE = "openrdf:MemoryStore"
|
|
@@ -543,7 +579,7 @@ class MemoryStoreConfig(SailConfig):
|
|
|
543
579
|
|
|
544
580
|
class NativeStoreConfig(SailConfig):
|
|
545
581
|
"""
|
|
546
|
-
Configuration for a NativeStore using
|
|
582
|
+
Configuration for a NativeStore using .
|
|
547
583
|
"""
|
|
548
584
|
|
|
549
585
|
TYPE = "openrdf:NativeStore"
|
|
@@ -711,7 +747,7 @@ class NativeStoreConfig(SailConfig):
|
|
|
711
747
|
|
|
712
748
|
class ElasticsearchStoreConfig(SailConfig):
|
|
713
749
|
"""
|
|
714
|
-
Configuration for an ElasticsearchStore using
|
|
750
|
+
Configuration for an ElasticsearchStore using .
|
|
715
751
|
"""
|
|
716
752
|
|
|
717
753
|
TYPE = "rdf4j:ElasticsearchStore"
|
|
@@ -835,7 +871,7 @@ class ElasticsearchStoreConfig(SailConfig):
|
|
|
835
871
|
|
|
836
872
|
class SchemaCachingRDFSInferencerConfig(SailConfig):
|
|
837
873
|
"""
|
|
838
|
-
Configuration for the RDF Schema inferencer using
|
|
874
|
+
Configuration for the RDF Schema inferencer using .
|
|
839
875
|
"""
|
|
840
876
|
|
|
841
877
|
TYPE = "rdf4j:SchemaCachingRDFSInferencer"
|
|
@@ -861,7 +897,7 @@ class SchemaCachingRDFSInferencerConfig(SailConfig):
|
|
|
861
897
|
)
|
|
862
898
|
self.config_params["delegate"] = delegate
|
|
863
899
|
|
|
864
|
-
def add_to_graph(self, graph:
|
|
900
|
+
def add_to_graph(self, graph: Dataset) -> URIRef:
|
|
865
901
|
"""
|
|
866
902
|
Adds the SchemaCachingRDFSInferencer configuration to the RDF graph.
|
|
867
903
|
|
|
@@ -873,7 +909,7 @@ class SchemaCachingRDFSInferencerConfig(SailConfig):
|
|
|
873
909
|
"""
|
|
874
910
|
sail_node = super().add_to_graph(graph)
|
|
875
911
|
delegate_node = self.config_params["delegate"].to_rdf(graph)
|
|
876
|
-
graph.add((sail_node, CONFIG.delegate, delegate_node))
|
|
912
|
+
graph.add(Quad(sail_node, CONFIG.delegate, delegate_node, None))
|
|
877
913
|
return sail_node
|
|
878
914
|
|
|
879
915
|
class Builder:
|
|
@@ -928,7 +964,7 @@ class SchemaCachingRDFSInferencerConfig(SailConfig):
|
|
|
928
964
|
|
|
929
965
|
class DirectTypeHierarchyInferencerConfig(SailConfig):
|
|
930
966
|
"""
|
|
931
|
-
Configuration for the Direct Type inferencer using
|
|
967
|
+
Configuration for the Direct Type inferencer using .
|
|
932
968
|
"""
|
|
933
969
|
|
|
934
970
|
TYPE = "openrdf:DirectTypeHierarchyInferencer"
|
|
@@ -954,7 +990,7 @@ class DirectTypeHierarchyInferencerConfig(SailConfig):
|
|
|
954
990
|
)
|
|
955
991
|
self.config_params["delegate"] = delegate
|
|
956
992
|
|
|
957
|
-
def add_to_graph(self, graph:
|
|
993
|
+
def add_to_graph(self, graph: Dataset) -> URIRef:
|
|
958
994
|
"""
|
|
959
995
|
Adds the DirectTypeHierarchyInferencerConfig to the graph
|
|
960
996
|
|
|
@@ -966,7 +1002,7 @@ class DirectTypeHierarchyInferencerConfig(SailConfig):
|
|
|
966
1002
|
"""
|
|
967
1003
|
sail_node = super().add_to_graph(graph)
|
|
968
1004
|
delegate_node = self.config_params["delegate"].to_rdf(graph)
|
|
969
|
-
graph.add((sail_node, CONFIG["delegate"], delegate_node))
|
|
1005
|
+
graph.add(Quad(sail_node, CONFIG["delegate"], delegate_node, None))
|
|
970
1006
|
return sail_node
|
|
971
1007
|
|
|
972
1008
|
class Builder:
|
|
@@ -1027,7 +1063,7 @@ class DirectTypeHierarchyInferencerConfig(SailConfig):
|
|
|
1027
1063
|
|
|
1028
1064
|
class SHACLSailConfig(SailConfig):
|
|
1029
1065
|
"""
|
|
1030
|
-
Configuration for the SHACL Sail using
|
|
1066
|
+
Configuration for the SHACL Sail using .
|
|
1031
1067
|
"""
|
|
1032
1068
|
|
|
1033
1069
|
TYPE = "rdf4j:ShaclSail"
|
|
@@ -1127,7 +1163,7 @@ class SHACLSailConfig(SailConfig):
|
|
|
1127
1163
|
validation_results_limit_per_constraint
|
|
1128
1164
|
)
|
|
1129
1165
|
|
|
1130
|
-
def add_to_graph(self, graph:
|
|
1166
|
+
def add_to_graph(self, graph: Dataset) -> URIRef:
|
|
1131
1167
|
"""
|
|
1132
1168
|
Adds the SHACLSailConfig to the RDF graph.
|
|
1133
1169
|
|
|
@@ -1139,21 +1175,38 @@ class SHACLSailConfig(SailConfig):
|
|
|
1139
1175
|
"""
|
|
1140
1176
|
sail_node = super().add_to_graph(graph) # Get the basic node
|
|
1141
1177
|
delegate_node = self.config_params["delegate"].to_rdf(graph)
|
|
1142
|
-
graph.add((sail_node, CONFIG.delegate, delegate_node))
|
|
1178
|
+
graph.add(Quad(sail_node, CONFIG.delegate, delegate_node, None))
|
|
1143
1179
|
|
|
1144
1180
|
# Add SHACL-specific parameters
|
|
1145
1181
|
for key, value in self.config_params.items():
|
|
1146
1182
|
if key != "delegate": # Delegate is already handled
|
|
1147
1183
|
if isinstance(value, bool):
|
|
1148
1184
|
graph.add(
|
|
1149
|
-
(
|
|
1185
|
+
Quad(
|
|
1186
|
+
sail_node,
|
|
1187
|
+
CONFIG[key],
|
|
1188
|
+
Literal(str(value).lower(), datatype=XSD["boolean"]),
|
|
1189
|
+
None,
|
|
1190
|
+
)
|
|
1150
1191
|
)
|
|
1151
|
-
elif isinstance(value, int):
|
|
1192
|
+
elif isinstance(value, int) and not isinstance(value, bool):
|
|
1152
1193
|
graph.add(
|
|
1153
|
-
(
|
|
1194
|
+
Quad(
|
|
1195
|
+
sail_node,
|
|
1196
|
+
CONFIG[key],
|
|
1197
|
+
Literal(str(value), datatype=XSD["integer"]),
|
|
1198
|
+
None,
|
|
1199
|
+
)
|
|
1154
1200
|
)
|
|
1155
1201
|
else:
|
|
1156
|
-
graph.add(
|
|
1202
|
+
graph.add(
|
|
1203
|
+
Quad(
|
|
1204
|
+
sail_node,
|
|
1205
|
+
CONFIG[key],
|
|
1206
|
+
Literal(value),
|
|
1207
|
+
None,
|
|
1208
|
+
)
|
|
1209
|
+
)
|
|
1157
1210
|
return sail_node
|
|
1158
1211
|
|
|
1159
1212
|
class Builder:
|
rdf4j_python/model/term.py
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import TypeAlias
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from rdflib.term import IdentifiedNode, Node
|
|
3
|
+
import pyoxigraph as og
|
|
5
4
|
|
|
6
|
-
IRI: TypeAlias =
|
|
5
|
+
IRI: TypeAlias = og.NamedNode
|
|
6
|
+
BlankNode: TypeAlias = og.BlankNode
|
|
7
|
+
Literal: TypeAlias = og.Literal
|
|
8
|
+
DefaultGraph: TypeAlias = og.DefaultGraph
|
|
9
|
+
Variable: TypeAlias = og.Variable
|
|
7
10
|
|
|
11
|
+
Quad: TypeAlias = og.Quad
|
|
12
|
+
Triple: TypeAlias = og.Triple
|
|
8
13
|
|
|
9
|
-
Subject: TypeAlias =
|
|
10
|
-
Predicate: TypeAlias =
|
|
11
|
-
Object: TypeAlias =
|
|
12
|
-
Context: TypeAlias =
|
|
14
|
+
Subject: TypeAlias = IRI | BlankNode | Triple
|
|
15
|
+
Predicate: TypeAlias = IRI
|
|
16
|
+
Object: TypeAlias = IRI | BlankNode | Literal
|
|
17
|
+
Context: TypeAlias = IRI | BlankNode | DefaultGraph | None
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
|
|
20
|
+
QuadResultSet: TypeAlias = og.QuadParser
|
rdf4j_python/model/vocabulary.py
CHANGED
|
@@ -4,3 +4,4 @@ from rdf4j_python.model import Namespace
|
|
|
4
4
|
RDF = Namespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
|
|
5
5
|
RDFS = Namespace("rdfs", "http://www.w3.org/2000/01/rdf-schema#")
|
|
6
6
|
EXAMPLE = Namespace("ex", "http://example.org/")
|
|
7
|
+
XSD = Namespace("xsd", "http://www.w3.org/2001/XMLSchema#")
|
rdf4j_python/utils/helpers.py
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
+
from io import BytesIO
|
|
1
2
|
from typing import Iterable
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import pyoxigraph as og
|
|
4
5
|
|
|
6
|
+
from rdf4j_python.model.term import Quad, Triple
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
def serialize_statements(statements: Iterable[Quad] | Iterable[Triple]) -> bytes:
|
|
7
10
|
"""Serializes statements to RDF data.
|
|
8
11
|
|
|
9
12
|
Args:
|
|
10
|
-
statements (Iterable[
|
|
13
|
+
statements (Iterable[Quad] | Iterable[Triple]): RDF statements.
|
|
11
14
|
|
|
12
15
|
Returns:
|
|
13
|
-
|
|
16
|
+
bytes: Serialized RDF data.
|
|
14
17
|
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if ctx:
|
|
19
|
-
parts.append(ctx.n3())
|
|
20
|
-
parts.append(".")
|
|
21
|
-
lines.append(" ".join(parts))
|
|
22
|
-
return "\n".join(lines) + "\n"
|
|
18
|
+
io = BytesIO()
|
|
19
|
+
og.serialize(statements, output=io, format=og.RdfFormat.N_QUADS)
|
|
20
|
+
return io.getvalue()
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rdf4j-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: The Python client for RDF4J
|
|
5
5
|
Author-email: Chengxu Bian <cbian564@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Dist: httpx>=0.28.1
|
|
10
|
-
Requires-Dist:
|
|
10
|
+
Requires-Dist: pyoxigraph>=0.4.10
|
|
11
|
+
Provides-Extra: sparqlwrapper
|
|
12
|
+
Requires-Dist: sparqlwrapper>=2.0.0; extra == "sparqlwrapper"
|
|
11
13
|
Dynamic: license-file
|
|
12
14
|
|
|
13
15
|
# 🐍 rdf4j-python
|
|
@@ -60,6 +62,7 @@ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
|
|
|
60
62
|
Literal("test_object"),
|
|
61
63
|
)
|
|
62
64
|
await repo.get_statements(subject=IRI("http://example.com/subject"))
|
|
65
|
+
results = await repo.query("SELECT * WHERE { ?s ?p ?o }")
|
|
63
66
|
```
|
|
64
67
|
|
|
65
68
|
For more detailed examples, refer to the [examples](https://github.com/odysa/rdf4j-python/tree/main/examples) directory.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
rdf4j_python/__init__.py,sha256=E8gojaWuLHo2AfNn5HBc5cF7K8_dK2jOBcZaH_qoZs4,347
|
|
2
|
+
rdf4j_python/_client/__init__.py,sha256=L5q5RTHXCNyHp2WNP4xTemGXNntUW4RF_QJPoCWciUQ,98
|
|
3
|
+
rdf4j_python/_client/_client.py,sha256=JfHFudRDVkzmb_jWx1u2meqqFONImBQHuaoYkb4mWPc,7390
|
|
4
|
+
rdf4j_python/_driver/__init__.py,sha256=au0r15mQBVaJkqBknw9CoVswe7_rPPFs1wFWI_ttdy4,224
|
|
5
|
+
rdf4j_python/_driver/_async_named_graph.py,sha256=BqJRpN-JzGpvlSnWY2RrM5TrnIHg6W4PortZ0y-4mgw,2685
|
|
6
|
+
rdf4j_python/_driver/_async_rdf4j_db.py,sha256=POJ2-5EiyFOjX6ykjxbm0RH7yjtU6mXCY2PlVYidLDI,4497
|
|
7
|
+
rdf4j_python/_driver/_async_repository.py,sha256=sW52MvZfbPeqJciFk4bgTHcDpxlZpXSikEsYflAmYHQ,15251
|
|
8
|
+
rdf4j_python/exception/__init__.py,sha256=PFdUyIMsHIL5e2P2z33Qr2pwlUAJVI0G5T8114W4-1A,83
|
|
9
|
+
rdf4j_python/exception/repo_exception.py,sha256=WXlfIYzOYfNU8LpwtOct9fAZADR-P3cZx4jAX9v_HaA,704
|
|
10
|
+
rdf4j_python/model/__init__.py,sha256=wHboqZX2bN3AubXvWRwKOWwnrefpeiiB_nnry2Ba_a4,176
|
|
11
|
+
rdf4j_python/model/_namespace.py,sha256=6iOvPc2Z6XFY44wmVucQCrjZbFuxdBtoTfDq_bSHJS0,3854
|
|
12
|
+
rdf4j_python/model/_repository_info.py,sha256=fdKwjoQz6_D95Q8uZEuEYhXEUeqElsdBkHRCRRNxjzM,2010
|
|
13
|
+
rdf4j_python/model/repository_config.py,sha256=6s-b0NPVILOLbmhAdTYR2xia7ErACvWvCUrAYcvZZng,56702
|
|
14
|
+
rdf4j_python/model/term.py,sha256=JBxndQESR2KEY6y9s6TqoELUQaegKcIUO0ctR818QaA,508
|
|
15
|
+
rdf4j_python/model/vocabulary.py,sha256=Yam0F_YNlO-7wDi2fc0DkAqIKB4ZqCpqq56wKRrFAcw,307
|
|
16
|
+
rdf4j_python/utils/__init__.py,sha256=LAyzkmO5QOIjJogQE1m4Eszwb2cCU8ytKRfhjAGoraw,34
|
|
17
|
+
rdf4j_python/utils/const.py,sha256=HKH9ZGe7m_dvMx1RwT6zhu2HUkvwOX2Y4e8vQFs3uE8,849
|
|
18
|
+
rdf4j_python/utils/helpers.py,sha256=4ubvrx3OV_0Y2_YECNIJhkJUB-JthdCJA8d6Hz_G_kE,506
|
|
19
|
+
rdf4j_python-0.1.4.dist-info/licenses/LICENSE,sha256=sGjA7CzoCG1LVlkh0ZB76ZdgWHVpUFegLU41YYDkGIA,1499
|
|
20
|
+
rdf4j_python-0.1.4.dist-info/METADATA,sha256=wdaugJ9Oxn4-fgfwifMaIlK3bplUxHJan54ciVAtZKo,3011
|
|
21
|
+
rdf4j_python-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
22
|
+
rdf4j_python-0.1.4.dist-info/top_level.txt,sha256=Lf2K8d8WcEkmvo5giLGuZ3gl20rNEwMssyAFBeVzbCs,13
|
|
23
|
+
rdf4j_python-0.1.4.dist-info/RECORD,,
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from typing import Mapping, Optional
|
|
3
|
-
|
|
4
|
-
from rdflib.term import Identifier, Literal, URIRef, Variable
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class _BaseModel(ABC):
|
|
8
|
-
"""Abstract base class providing utility methods for parsing RDF query results."""
|
|
9
|
-
|
|
10
|
-
@staticmethod
|
|
11
|
-
def get_literal(
|
|
12
|
-
result: Mapping[Variable, Identifier],
|
|
13
|
-
var_name: str,
|
|
14
|
-
default: Optional[str] = None,
|
|
15
|
-
) -> Optional[str]:
|
|
16
|
-
"""
|
|
17
|
-
Extracts a literal value from a SPARQL query result.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
result (Mapping[Variable, Identifier]): A mapping of variable bindings from a query result.
|
|
21
|
-
var_name (str): The variable name to extract.
|
|
22
|
-
default (Optional[str], optional): The value to return if the variable is not found or is not a Literal. Defaults to None.
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
Optional[str]: The Python representation of the literal, or the default value.
|
|
26
|
-
"""
|
|
27
|
-
val = result.get(Variable(var_name))
|
|
28
|
-
return val.toPython() if isinstance(val, Literal) else default
|
|
29
|
-
|
|
30
|
-
@staticmethod
|
|
31
|
-
def get_uri(
|
|
32
|
-
result: Mapping[Variable, Identifier],
|
|
33
|
-
var_name: str,
|
|
34
|
-
default: Optional[str] = None,
|
|
35
|
-
) -> Optional[str]:
|
|
36
|
-
"""
|
|
37
|
-
Extracts a URI value from a SPARQL query result.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
result (Mapping[Variable, Identifier]): A mapping of variable bindings from a query result.
|
|
41
|
-
var_name (str): The variable name to extract.
|
|
42
|
-
default (Optional[str], optional): The value to return if the variable is not found or is not a URIRef. Defaults to None.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
Optional[str]: The URI string, or the default value.
|
|
46
|
-
"""
|
|
47
|
-
val = result.get(Variable(var_name))
|
|
48
|
-
return str(val) if isinstance(val, URIRef) else default
|
rdf4j_python/model/_dataset.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from rdflib import Dataset as _Dataset
|
|
2
|
-
|
|
3
|
-
from rdf4j_python.model.term import IRI
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class RDF4JDataSet(_Dataset):
|
|
7
|
-
"""
|
|
8
|
-
An RDFLib Dataset subclass with RDF4J-specific utility methods.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
def as_list(self) -> list[tuple]:
|
|
12
|
-
"""
|
|
13
|
-
Converts all quads in the dataset to a list of 4-tuples.
|
|
14
|
-
|
|
15
|
-
Replaces the RDF4J default context IRI ("urn:x-rdflib:default") with None.
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
list[tuple]: A list of (subject, predicate, object, context) quads.
|
|
19
|
-
"""
|
|
20
|
-
return [
|
|
21
|
-
(s, p, o, ctx if ctx != IRI("urn:x-rdflib:default") else None)
|
|
22
|
-
for s, p, o, ctx in self.quads((None, None, None, None))
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
@staticmethod
|
|
26
|
-
def from_raw_text(text: str) -> "RDF4JDataSet":
|
|
27
|
-
"""
|
|
28
|
-
Parses a string of N-Quads RDF data into an RDF4JDataSet.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
text (str): The RDF data in N-Quads format.
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
RDF4JDataSet: A populated dataset.
|
|
35
|
-
"""
|
|
36
|
-
ds = RDF4JDataSet()
|
|
37
|
-
ds.parse(data=text, format="nquads")
|
|
38
|
-
return ds
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
rdf4j_python/__init__.py,sha256=E8gojaWuLHo2AfNn5HBc5cF7K8_dK2jOBcZaH_qoZs4,347
|
|
2
|
-
rdf4j_python/_client/__init__.py,sha256=L5q5RTHXCNyHp2WNP4xTemGXNntUW4RF_QJPoCWciUQ,98
|
|
3
|
-
rdf4j_python/_client/_client.py,sha256=JfHFudRDVkzmb_jWx1u2meqqFONImBQHuaoYkb4mWPc,7390
|
|
4
|
-
rdf4j_python/_driver/__init__.py,sha256=au0r15mQBVaJkqBknw9CoVswe7_rPPFs1wFWI_ttdy4,224
|
|
5
|
-
rdf4j_python/_driver/_async_named_graph.py,sha256=zpYr6x_9aI2wHH0Pj71LlCKkHFFanz7G77Tz5azXlg4,2642
|
|
6
|
-
rdf4j_python/_driver/_async_rdf4j_db.py,sha256=u6r4A4_CTp4JMEswl0I5rMjsuWnMkQV_TJrvF6u9R94,4403
|
|
7
|
-
rdf4j_python/_driver/_async_repository.py,sha256=NKM9IFLTNMepVCx_UeWc7h0BTxHMKfDnUcThz97pVCs,14075
|
|
8
|
-
rdf4j_python/exception/__init__.py,sha256=PFdUyIMsHIL5e2P2z33Qr2pwlUAJVI0G5T8114W4-1A,83
|
|
9
|
-
rdf4j_python/exception/repo_exception.py,sha256=WXlfIYzOYfNU8LpwtOct9fAZADR-P3cZx4jAX9v_HaA,704
|
|
10
|
-
rdf4j_python/model/__init__.py,sha256=eUvfFEPgDsbq37eIpd_dlvjpQPGuRwkKWNfqa0c2QwM,231
|
|
11
|
-
rdf4j_python/model/_base_model.py,sha256=PyhvLhaseKaFuRU70xord8EgIfO88-UVcllkuRobE14,1780
|
|
12
|
-
rdf4j_python/model/_dataset.py,sha256=-nYTm8Fz1CGsLL2ykmtGEJ9Jv4u8FC2Dgl_31LeoGhM,1065
|
|
13
|
-
rdf4j_python/model/_namespace.py,sha256=yaH-MjMFsUfCFadlo25MgHf9zaRbk-wkn_BgtmemVo4,3259
|
|
14
|
-
rdf4j_python/model/_repository_info.py,sha256=zSL3o_QWL06xYbnpFtXIg-PqFiRbwDFPb1aiy2JufYQ,1668
|
|
15
|
-
rdf4j_python/model/repository_config.py,sha256=WPXJAbZ2pM594Uh_r3zI6N1fqObSsk74ArVZTTdqPks,55012
|
|
16
|
-
rdf4j_python/model/term.py,sha256=FkK8-HG6h79dAYO_qiTYTxvu8t-d5zVY5FtgJrW997o,352
|
|
17
|
-
rdf4j_python/model/vocabulary.py,sha256=R9k9dzDPwRBEgwjIF6sNZh3hL41R7GZ1eSJKb8w7IaY,247
|
|
18
|
-
rdf4j_python/utils/__init__.py,sha256=LAyzkmO5QOIjJogQE1m4Eszwb2cCU8ytKRfhjAGoraw,34
|
|
19
|
-
rdf4j_python/utils/const.py,sha256=HKH9ZGe7m_dvMx1RwT6zhu2HUkvwOX2Y4e8vQFs3uE8,849
|
|
20
|
-
rdf4j_python/utils/helpers.py,sha256=xnMB_imsybGQ2GiHvXK4wEpcs4ADiqZ4ez0yop_ko8E,577
|
|
21
|
-
rdf4j_python-0.1.2.dist-info/licenses/LICENSE,sha256=sGjA7CzoCG1LVlkh0ZB76ZdgWHVpUFegLU41YYDkGIA,1499
|
|
22
|
-
rdf4j_python-0.1.2.dist-info/METADATA,sha256=7V13OG7A4fxFPLcwbIUjsqUCVDwC1dlqXE5YSHw7PbU,2852
|
|
23
|
-
rdf4j_python-0.1.2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
24
|
-
rdf4j_python-0.1.2.dist-info/top_level.txt,sha256=Lf2K8d8WcEkmvo5giLGuZ3gl20rNEwMssyAFBeVzbCs,13
|
|
25
|
-
rdf4j_python-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|