rdf4j-python 0.1.1a0__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rdf4j_python/__init__.py +9 -4
- rdf4j_python/_client/_client.py +156 -5
- rdf4j_python/_driver/__init__.py +2 -0
- rdf4j_python/_driver/_async_named_graph.py +75 -0
- rdf4j_python/_driver/_async_rdf4j_db.py +55 -29
- rdf4j_python/_driver/_async_repository.py +289 -16
- rdf4j_python/exception/__init__.py +5 -0
- rdf4j_python/exception/repo_exception.py +26 -5
- rdf4j_python/model/__init__.py +7 -10
- rdf4j_python/model/_base_model.py +26 -4
- rdf4j_python/model/_dataset.py +38 -0
- rdf4j_python/model/_namespace.py +82 -4
- rdf4j_python/model/_repository_info.py +12 -1
- rdf4j_python/model/{_repository_config.py → repository_config.py} +565 -17
- rdf4j_python/model/term.py +14 -0
- rdf4j_python/model/vocabulary.py +6 -0
- rdf4j_python/utils/__init__.py +3 -0
- rdf4j_python/utils/const.py +4 -4
- rdf4j_python/utils/helpers.py +22 -0
- {rdf4j_python-0.1.1a0.dist-info → rdf4j_python-0.1.2.dist-info}/METADATA +12 -12
- rdf4j_python-0.1.2.dist-info/RECORD +25 -0
- {rdf4j_python-0.1.1a0.dist-info → rdf4j_python-0.1.2.dist-info}/WHEEL +1 -1
- rdf4j_python-0.1.1a0.dist-info/RECORD +0 -19
- {rdf4j_python-0.1.1a0.dist-info → rdf4j_python-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {rdf4j_python-0.1.1a0.dist-info → rdf4j_python-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,41 @@
|
|
|
1
|
+
from typing import Iterable, Optional
|
|
2
|
+
|
|
1
3
|
import httpx
|
|
2
4
|
import rdflib
|
|
5
|
+
import rdflib.resource
|
|
6
|
+
import rdflib.serializer
|
|
7
|
+
import rdflib.store
|
|
3
8
|
|
|
4
|
-
from rdf4j_python import AsyncApiClient
|
|
9
|
+
from rdf4j_python._client import AsyncApiClient
|
|
10
|
+
from rdf4j_python._driver._async_named_graph import AsyncNamedGraph
|
|
5
11
|
from rdf4j_python.exception.repo_exception import (
|
|
6
12
|
NamespaceException,
|
|
7
13
|
RepositoryInternalException,
|
|
8
14
|
RepositoryNotFoundException,
|
|
15
|
+
RepositoryUpdateException,
|
|
16
|
+
)
|
|
17
|
+
from rdf4j_python.model import Namespace, RDF4JDataSet
|
|
18
|
+
from rdf4j_python.model.term import (
|
|
19
|
+
Context,
|
|
20
|
+
Object,
|
|
21
|
+
Predicate,
|
|
22
|
+
RDFStatement,
|
|
23
|
+
Subject,
|
|
9
24
|
)
|
|
10
|
-
from rdf4j_python.model._namespace import Namespace
|
|
11
25
|
from rdf4j_python.utils.const import Rdf4jContentType
|
|
26
|
+
from rdf4j_python.utils.helpers import serialize_statements
|
|
12
27
|
|
|
13
28
|
|
|
14
29
|
class AsyncRdf4JRepository:
|
|
30
|
+
"""Asynchronous interface for interacting with an RDF4J repository."""
|
|
31
|
+
|
|
15
32
|
def __init__(self, client: AsyncApiClient, repository_id: str):
|
|
33
|
+
"""Initializes the repository interface.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
client (AsyncApiClient): The RDF4J API client.
|
|
37
|
+
repository_id (str): The ID of the RDF4J repository.
|
|
38
|
+
"""
|
|
16
39
|
self._client = client
|
|
17
40
|
self._repository_id = repository_id
|
|
18
41
|
|
|
@@ -22,6 +45,16 @@ class AsyncRdf4JRepository:
|
|
|
22
45
|
infer: bool = True,
|
|
23
46
|
accept: Rdf4jContentType = Rdf4jContentType.SPARQL_RESULTS_JSON,
|
|
24
47
|
):
|
|
48
|
+
"""Executes a SPARQL SELECT query.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
sparql_query (str): The SPARQL query string.
|
|
52
|
+
infer (bool): Whether to include inferred statements. Defaults to True.
|
|
53
|
+
accept (Rdf4jContentType): The expected response format.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
dict or str: Parsed JSON results or raw response text.
|
|
57
|
+
"""
|
|
25
58
|
path = f"/repositories/{self._repository_id}"
|
|
26
59
|
params = {"query": sparql_query, "infer": str(infer).lower()}
|
|
27
60
|
headers = {"Accept": accept.value}
|
|
@@ -32,26 +65,33 @@ class AsyncRdf4JRepository:
|
|
|
32
65
|
return response.text
|
|
33
66
|
|
|
34
67
|
async def update(self, sparql_update: str):
|
|
68
|
+
"""Executes a SPARQL UPDATE command.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
sparql_update (str): The SPARQL update string.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
75
|
+
httpx.HTTPStatusError: If the update fails.
|
|
76
|
+
"""
|
|
35
77
|
path = f"/repositories/{self._repository_id}/statements"
|
|
36
78
|
headers = {"Content-Type": Rdf4jContentType.SPARQL_UPDATE.value}
|
|
37
79
|
response = await self._client.post(path, data=sparql_update, headers=headers)
|
|
38
80
|
self._handle_repo_not_found_exception(response)
|
|
39
81
|
response.raise_for_status()
|
|
40
82
|
|
|
41
|
-
async def replace_statements(
|
|
42
|
-
self, rdf_data: str, content_type: Rdf4jContentType = Rdf4jContentType.TURTLE
|
|
43
|
-
):
|
|
44
|
-
path = f"/repositories/{self._repository_id}/statements"
|
|
45
|
-
headers = {"Content-Type": content_type.value}
|
|
46
|
-
response = await self._client.put(path, data=rdf_data, headers=headers)
|
|
47
|
-
self._handle_repo_not_found_exception(response)
|
|
48
|
-
response.raise_for_status()
|
|
49
|
-
|
|
50
83
|
async def get_namespaces(self):
|
|
84
|
+
"""Retrieves all namespaces in the repository.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
list[Namespace]: A list of namespace objects.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
91
|
+
"""
|
|
51
92
|
path = f"/repositories/{self._repository_id}/namespaces"
|
|
52
93
|
headers = {"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON}
|
|
53
94
|
response = await self._client.get(path, headers=headers)
|
|
54
|
-
|
|
55
95
|
result = rdflib.query.Result.parse(
|
|
56
96
|
response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
|
|
57
97
|
)
|
|
@@ -59,6 +99,16 @@ class AsyncRdf4JRepository:
|
|
|
59
99
|
return [Namespace.from_rdflib_binding(binding) for binding in result.bindings]
|
|
60
100
|
|
|
61
101
|
async def set_namespace(self, prefix: str, namespace: str):
|
|
102
|
+
"""Sets a namespace prefix.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
prefix (str): The namespace prefix.
|
|
106
|
+
namespace (str): The namespace URI.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
110
|
+
NamespaceException: If the request fails.
|
|
111
|
+
"""
|
|
62
112
|
path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
|
|
63
113
|
headers = {"Content-Type": Rdf4jContentType.NTRIPLES.value}
|
|
64
114
|
response = await self._client.put(path, content=namespace, headers=headers)
|
|
@@ -67,6 +117,18 @@ class AsyncRdf4JRepository:
|
|
|
67
117
|
raise NamespaceException(f"Failed to set namespace: {response.text}")
|
|
68
118
|
|
|
69
119
|
async def get_namespace(self, prefix: str) -> Namespace:
|
|
120
|
+
"""Gets a namespace by its prefix.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
prefix (str): The namespace prefix.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Namespace: The namespace object.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
130
|
+
NamespaceException: If retrieval fails.
|
|
131
|
+
"""
|
|
70
132
|
path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
|
|
71
133
|
headers = {"Accept": Rdf4jContentType.NTRIPLES.value}
|
|
72
134
|
response = await self._client.get(path, headers=headers)
|
|
@@ -78,12 +140,42 @@ class AsyncRdf4JRepository:
|
|
|
78
140
|
return Namespace(prefix, response.text)
|
|
79
141
|
|
|
80
142
|
async def delete_namespace(self, prefix: str):
|
|
143
|
+
"""Deletes a namespace by prefix.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
prefix (str): The namespace prefix.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
150
|
+
httpx.HTTPStatusError: If deletion fails.
|
|
151
|
+
"""
|
|
81
152
|
path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
|
|
82
153
|
response = await self._client.delete(path)
|
|
83
154
|
self._handle_repo_not_found_exception(response)
|
|
84
155
|
response.raise_for_status()
|
|
85
156
|
|
|
157
|
+
async def clear_all_namespaces(self):
|
|
158
|
+
"""Removes all namespaces from the repository.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
162
|
+
httpx.HTTPStatusError: If clearing fails.
|
|
163
|
+
"""
|
|
164
|
+
path = f"/repositories/{self._repository_id}/namespaces"
|
|
165
|
+
response = await self._client.delete(path)
|
|
166
|
+
self._handle_repo_not_found_exception(response)
|
|
167
|
+
response.raise_for_status()
|
|
168
|
+
|
|
86
169
|
async def size(self) -> int:
|
|
170
|
+
"""Gets the number of statements in the repository.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
int: The total number of RDF statements.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
177
|
+
RepositoryInternalException: If retrieval fails.
|
|
178
|
+
"""
|
|
87
179
|
path = f"/repositories/{self._repository_id}/size"
|
|
88
180
|
response = await self._client.get(path)
|
|
89
181
|
self._handle_repo_not_found_exception(response)
|
|
@@ -93,17 +185,198 @@ class AsyncRdf4JRepository:
|
|
|
93
185
|
|
|
94
186
|
return int(response.text.strip())
|
|
95
187
|
|
|
96
|
-
async def
|
|
188
|
+
async def get_statements(
|
|
189
|
+
self,
|
|
190
|
+
subject: Optional[Subject] = None,
|
|
191
|
+
predicate: Optional[Predicate] = None,
|
|
192
|
+
object_: Optional[Object] = None,
|
|
193
|
+
contexts: Optional[list[Context]] = None,
|
|
194
|
+
infer: bool = True,
|
|
195
|
+
) -> RDF4JDataSet:
|
|
196
|
+
"""Retrieves statements matching the given pattern.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
subject (Optional[Subject]): Filter by subject.
|
|
200
|
+
predicate (Optional[Predicate]): Filter by predicate.
|
|
201
|
+
object_ (Optional[Object]): Filter by object.
|
|
202
|
+
contexts (Optional[list[Context]]): Filter by context (named graph).
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
DataSet: Dataset of matching RDF statements.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
209
|
+
"""
|
|
210
|
+
path = f"/repositories/{self._repository_id}/statements"
|
|
211
|
+
params = {}
|
|
212
|
+
|
|
213
|
+
if subject:
|
|
214
|
+
params["subj"] = subject.n3()
|
|
215
|
+
if predicate:
|
|
216
|
+
params["pred"] = predicate.n3()
|
|
217
|
+
if object_:
|
|
218
|
+
params["obj"] = object_.n3()
|
|
219
|
+
if contexts:
|
|
220
|
+
params["context"] = [ctx.n3() for ctx in contexts]
|
|
221
|
+
params["infer"] = str(infer).lower()
|
|
222
|
+
|
|
223
|
+
headers = {"Accept": Rdf4jContentType.NQUADS}
|
|
224
|
+
response = await self._client.get(path, params=params, headers=headers)
|
|
225
|
+
dataset = RDF4JDataSet()
|
|
226
|
+
dataset.parse(data=response.text, format="nquads")
|
|
227
|
+
return dataset
|
|
228
|
+
|
|
229
|
+
async def delete_statements(
|
|
230
|
+
self,
|
|
231
|
+
subject: Optional[Subject] = None,
|
|
232
|
+
predicate: Optional[Predicate] = None,
|
|
233
|
+
object_: Optional[Object] = None,
|
|
234
|
+
contexts: Optional[list[Context]] = None,
|
|
235
|
+
):
|
|
236
|
+
"""Deletes statements from the repository matching the given pattern.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
subject (Optional[Subject]): Filter by subject (N-Triples encoded).
|
|
240
|
+
predicate (Optional[Predicate]): Filter by predicate (N-Triples encoded).
|
|
241
|
+
object_ (Optional[Object]): Filter by object (N-Triples encoded).
|
|
242
|
+
contexts (Optional[list[Context]]): One or more specific contexts to restrict deletion to.
|
|
243
|
+
Use 'null' as a string to delete context-less statements.
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
RepositoryNotFoundException: If the repository does not exist.
|
|
247
|
+
RepositoryUpdateException: If the deletion fails.
|
|
248
|
+
"""
|
|
249
|
+
path = f"/repositories/{self._repository_id}/statements"
|
|
250
|
+
params = {}
|
|
251
|
+
|
|
252
|
+
if subject:
|
|
253
|
+
params["subj"] = subject.n3()
|
|
254
|
+
if predicate:
|
|
255
|
+
params["pred"] = predicate.n3()
|
|
256
|
+
if object_:
|
|
257
|
+
params["obj"] = object_.n3()
|
|
258
|
+
if contexts:
|
|
259
|
+
params["context"] = [ctx.n3() for ctx in contexts]
|
|
260
|
+
|
|
261
|
+
response = await self._client.delete(path, params=params)
|
|
262
|
+
self._handle_repo_not_found_exception(response)
|
|
263
|
+
|
|
264
|
+
if response.status_code != httpx.codes.NO_CONTENT:
|
|
265
|
+
raise RepositoryUpdateException(
|
|
266
|
+
f"Failed to delete statements: {response.text}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
async def add_statement(
|
|
270
|
+
self,
|
|
271
|
+
subject: Subject,
|
|
272
|
+
predicate: Predicate,
|
|
273
|
+
object: Object,
|
|
274
|
+
context: Optional[Context] = None,
|
|
275
|
+
):
|
|
276
|
+
"""Adds a single RDF statement to the repository.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
subject (Node): The subject of the triple.
|
|
280
|
+
predicate (Node): The predicate of the triple.
|
|
281
|
+
object (Node): The object of the triple.
|
|
282
|
+
context (IdentifiedNode): The context (named graph).
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
286
|
+
httpx.HTTPStatusError: If addition fails.
|
|
287
|
+
"""
|
|
97
288
|
path = f"/repositories/{self._repository_id}/statements"
|
|
98
|
-
headers = {"Content-Type": Rdf4jContentType.NTRIPLES.value}
|
|
99
289
|
response = await self._client.post(
|
|
100
|
-
path,
|
|
290
|
+
path,
|
|
291
|
+
content=serialize_statements([(subject, predicate, object, context)]),
|
|
292
|
+
headers={"Content-Type": Rdf4jContentType.NQUADS},
|
|
101
293
|
)
|
|
102
294
|
self._handle_repo_not_found_exception(response)
|
|
103
|
-
response.
|
|
295
|
+
if response.status_code != httpx.codes.NO_CONTENT:
|
|
296
|
+
raise RepositoryUpdateException(f"Failed to add statement: {response.text}")
|
|
297
|
+
|
|
298
|
+
async def add_statements(self, statements: Iterable[RDFStatement]):
|
|
299
|
+
"""Adds a list of RDF statements to the repository.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
statements (Iterable[RDFStatement]): A list of RDF statements.
|
|
303
|
+
RDFStatement: A tuple of subject, predicate, object, and context.
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
307
|
+
httpx.HTTPStatusError: If addition fails.
|
|
308
|
+
"""
|
|
309
|
+
path = f"/repositories/{self._repository_id}/statements"
|
|
310
|
+
response = await self._client.post(
|
|
311
|
+
path,
|
|
312
|
+
content=serialize_statements(statements),
|
|
313
|
+
headers={"Content-Type": Rdf4jContentType.NQUADS},
|
|
314
|
+
)
|
|
315
|
+
self._handle_repo_not_found_exception(response)
|
|
316
|
+
if response.status_code != httpx.codes.NO_CONTENT:
|
|
317
|
+
raise RepositoryUpdateException(
|
|
318
|
+
f"Failed to add statements: {response.text}"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
async def replace_statements(
|
|
322
|
+
self,
|
|
323
|
+
statements: Iterable[RDFStatement],
|
|
324
|
+
contexts: Optional[list[Context]] = None,
|
|
325
|
+
base_uri: Optional[str] = None,
|
|
326
|
+
):
|
|
327
|
+
"""Replaces all repository statements with the given RDF data.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
statements (Iterable[RDFStatement]): RDF statements to load.
|
|
331
|
+
contexts (Optional[list[Context]]): One or more specific contexts to restrict deletion to.
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
RepositoryNotFoundException: If the repository doesn't exist.
|
|
335
|
+
httpx.HTTPStatusError: If the operation fails.
|
|
336
|
+
"""
|
|
337
|
+
path = f"/repositories/{self._repository_id}/statements"
|
|
338
|
+
headers = {"Content-Type": Rdf4jContentType.NQUADS.value}
|
|
339
|
+
|
|
340
|
+
params = {}
|
|
341
|
+
if contexts:
|
|
342
|
+
params["context"] = [ctx.n3() for ctx in contexts]
|
|
343
|
+
if base_uri:
|
|
344
|
+
params["baseUri"] = base_uri
|
|
345
|
+
|
|
346
|
+
response = await self._client.put(
|
|
347
|
+
path,
|
|
348
|
+
content=serialize_statements(statements),
|
|
349
|
+
headers=headers,
|
|
350
|
+
params=params,
|
|
351
|
+
)
|
|
352
|
+
self._handle_repo_not_found_exception(response)
|
|
353
|
+
if response.status_code != httpx.codes.NO_CONTENT:
|
|
354
|
+
raise RepositoryUpdateException(
|
|
355
|
+
f"Failed to replace statements: {response.text}"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
async def get_named_graph(self, graph: str) -> AsyncNamedGraph:
|
|
359
|
+
"""Retrieves a named graph in the repository.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
AsyncNamedGraph: A named graph object.
|
|
363
|
+
"""
|
|
364
|
+
return AsyncNamedGraph(self._client, self._repository_id, graph)
|
|
104
365
|
|
|
105
366
|
def _handle_repo_not_found_exception(self, response: httpx.Response):
|
|
367
|
+
"""Raises a RepositoryNotFoundException if response is 404.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
response (httpx.Response): HTTP response object.
|
|
371
|
+
|
|
372
|
+
Raises:
|
|
373
|
+
RepositoryNotFoundException: If repository is not found.
|
|
374
|
+
"""
|
|
106
375
|
if response.status_code == httpx.codes.NOT_FOUND:
|
|
107
376
|
raise RepositoryNotFoundException(
|
|
108
377
|
f"Repository {self._repository_id} not found"
|
|
109
378
|
)
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def repository_id(self) -> str:
|
|
382
|
+
return self._repository_id
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
class RepositoryCreationException(Exception):
|
|
1
|
+
class RepositoryCreationException(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Exception raised when a repository creation fails.
|
|
4
|
+
"""
|
|
2
5
|
|
|
3
6
|
|
|
4
|
-
class RepositoryDeletionException(Exception):
|
|
7
|
+
class RepositoryDeletionException(Exception):
|
|
8
|
+
"""
|
|
9
|
+
Exception raised when a repository deletion fails.
|
|
10
|
+
"""
|
|
5
11
|
|
|
6
12
|
|
|
7
|
-
class NamespaceException(Exception):
|
|
13
|
+
class NamespaceException(Exception):
|
|
14
|
+
"""
|
|
15
|
+
Exception raised when a namespace operation fails.
|
|
16
|
+
"""
|
|
8
17
|
|
|
9
18
|
|
|
10
|
-
class RepositoryNotFoundException(Exception):
|
|
19
|
+
class RepositoryNotFoundException(Exception):
|
|
20
|
+
"""
|
|
21
|
+
Exception raised when a repository is not found.
|
|
22
|
+
"""
|
|
11
23
|
|
|
12
24
|
|
|
13
|
-
class RepositoryInternalException(Exception):
|
|
25
|
+
class RepositoryInternalException(Exception):
|
|
26
|
+
"""
|
|
27
|
+
Exception raised when a repository internal error occurs.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RepositoryUpdateException(Exception):
|
|
32
|
+
"""
|
|
33
|
+
Exception raised when a repository update fails.
|
|
34
|
+
"""
|
rdf4j_python/model/__init__.py
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"""
|
|
2
|
+
RDF4J Python Model Module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ._dataset import RDF4JDataSet
|
|
6
|
+
from ._namespace import Namespace
|
|
7
7
|
from ._repository_info import RepositoryMetadata
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
|
-
"IRI",
|
|
11
10
|
"Namespace",
|
|
12
|
-
"RepositoryConfig",
|
|
13
|
-
"MemoryStoreConfig",
|
|
14
|
-
"NativeStoreConfig",
|
|
15
11
|
"RepositoryMetadata",
|
|
12
|
+
"RDF4JDataSet",
|
|
16
13
|
]
|
|
@@ -5,13 +5,25 @@ from rdflib.term import Identifier, Literal, URIRef, Variable
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class _BaseModel(ABC):
|
|
8
|
+
"""Abstract base class providing utility methods for parsing RDF query results."""
|
|
9
|
+
|
|
8
10
|
@staticmethod
|
|
9
11
|
def get_literal(
|
|
10
12
|
result: Mapping[Variable, Identifier],
|
|
11
13
|
var_name: str,
|
|
12
14
|
default: Optional[str] = None,
|
|
13
|
-
):
|
|
14
|
-
"""
|
|
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
|
+
"""
|
|
15
27
|
val = result.get(Variable(var_name))
|
|
16
28
|
return val.toPython() if isinstance(val, Literal) else default
|
|
17
29
|
|
|
@@ -20,7 +32,17 @@ class _BaseModel(ABC):
|
|
|
20
32
|
result: Mapping[Variable, Identifier],
|
|
21
33
|
var_name: str,
|
|
22
34
|
default: Optional[str] = None,
|
|
23
|
-
):
|
|
24
|
-
"""
|
|
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
|
+
"""
|
|
25
47
|
val = result.get(Variable(var_name))
|
|
26
48
|
return str(val) if isinstance(val, URIRef) else default
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
rdf4j_python/model/_namespace.py
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
from typing import Mapping
|
|
2
2
|
|
|
3
|
-
from rdflib import URIRef
|
|
4
3
|
from rdflib.namespace import Namespace as RdflibNamespace
|
|
5
4
|
from rdflib.term import Identifier, Variable
|
|
6
5
|
|
|
7
|
-
from .
|
|
8
|
-
|
|
6
|
+
from rdf4j_python.model.term import IRI
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
from ._base_model import _BaseModel
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
class Namespace:
|
|
12
|
+
"""
|
|
13
|
+
Represents a namespace in RDF4J.
|
|
14
|
+
"""
|
|
15
|
+
|
|
14
16
|
_prefix: str
|
|
15
17
|
_namespace: RdflibNamespace
|
|
16
18
|
|
|
17
19
|
def __init__(self, prefix: str, namespace: str):
|
|
20
|
+
"""
|
|
21
|
+
Initializes a new Namespace.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
prefix (str): The prefix of the namespace.
|
|
25
|
+
namespace (str): The namespace URI.
|
|
26
|
+
"""
|
|
18
27
|
self._prefix = prefix
|
|
19
28
|
self._namespace = RdflibNamespace(namespace)
|
|
20
29
|
|
|
21
30
|
@classmethod
|
|
22
31
|
def from_rdflib_binding(cls, binding: Mapping[Variable, Identifier]) -> "Namespace":
|
|
32
|
+
"""
|
|
33
|
+
Creates a Namespace from a RDFlib binding.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
binding (Mapping[Variable, Identifier]): The RDFlib binding.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Namespace: The created Namespace.
|
|
40
|
+
"""
|
|
23
41
|
prefix = _BaseModel.get_literal(binding, "prefix", "")
|
|
24
42
|
namespace = _BaseModel.get_literal(binding, "namespace", "")
|
|
25
43
|
return cls(
|
|
@@ -28,29 +46,89 @@ class Namespace:
|
|
|
28
46
|
)
|
|
29
47
|
|
|
30
48
|
def __str__(self):
|
|
49
|
+
"""
|
|
50
|
+
Returns a string representation of the Namespace.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: A string representation of the Namespace.
|
|
54
|
+
"""
|
|
31
55
|
return f"{self._prefix}: {self._namespace}"
|
|
32
56
|
|
|
33
57
|
def __repr__(self):
|
|
58
|
+
"""
|
|
59
|
+
Returns a string representation of the Namespace.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
str: A string representation of the Namespace.
|
|
63
|
+
"""
|
|
34
64
|
return f"Namespace(prefix={self._prefix}, namespace={self._namespace})"
|
|
35
65
|
|
|
36
66
|
def __contains__(self, item: str) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
Checks if the Namespace contains a given item.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
item (str): The item to check.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
bool: True if the Namespace contains the item, False otherwise.
|
|
75
|
+
"""
|
|
37
76
|
return item in self._namespace
|
|
38
77
|
|
|
39
78
|
def term(self, name: str) -> IRI:
|
|
79
|
+
"""
|
|
80
|
+
Returns the IRI for a given term.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name (str): The term name.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
IRI: The IRI for the term.
|
|
87
|
+
"""
|
|
40
88
|
return IRI(self._namespace.term(name))
|
|
41
89
|
|
|
42
90
|
def __getitem__(self, item: str) -> IRI:
|
|
91
|
+
"""
|
|
92
|
+
Returns the IRI for a given term.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
item (str): The term name.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
IRI: The IRI for the term.
|
|
99
|
+
"""
|
|
43
100
|
return self.term(item)
|
|
44
101
|
|
|
45
102
|
def __getattr__(self, item: str) -> IRI:
|
|
103
|
+
"""
|
|
104
|
+
Returns the IRI for a given term.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
item (str): The term name.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
IRI: The IRI for the term.
|
|
111
|
+
"""
|
|
46
112
|
if item.startswith("__"):
|
|
47
113
|
raise AttributeError
|
|
48
114
|
return self.term(item)
|
|
49
115
|
|
|
50
116
|
@property
|
|
51
117
|
def namespace(self) -> IRI:
|
|
118
|
+
"""
|
|
119
|
+
Returns the namespace URI.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
IRI: The namespace URI.
|
|
123
|
+
"""
|
|
52
124
|
return IRI(self._namespace)
|
|
53
125
|
|
|
54
126
|
@property
|
|
55
127
|
def prefix(self) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Returns the prefix of the namespace.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
str: The prefix of the namespace.
|
|
133
|
+
"""
|
|
56
134
|
return self._prefix
|