rdf4j-python 0.1.2__tar.gz → 0.1.4__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.
- {rdf4j_python-0.1.2/rdf4j_python.egg-info → rdf4j_python-0.1.4}/PKG-INFO +5 -2
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/README.md +1 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/pyproject.toml +7 -6
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/_driver/_async_named_graph.py +8 -7
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/_driver/_async_rdf4j_db.py +6 -5
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/_driver/_async_repository.py +90 -50
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/model/__init__.py +0 -2
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/model/_namespace.py +38 -15
- rdf4j_python-0.1.4/rdf4j_python/model/_repository_info.py +62 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/model/repository_config.py +111 -58
- rdf4j_python-0.1.4/rdf4j_python/model/term.py +20 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/model/vocabulary.py +1 -0
- rdf4j_python-0.1.4/rdf4j_python/utils/helpers.py +20 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4/rdf4j_python.egg-info}/PKG-INFO +5 -2
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python.egg-info/SOURCES.txt +1 -2
- rdf4j_python-0.1.4/rdf4j_python.egg-info/requires.txt +5 -0
- rdf4j_python-0.1.4/tests/test_async_named_graph.py +89 -0
- rdf4j_python-0.1.4/tests/test_helpers.py +21 -0
- rdf4j_python-0.1.4/tests/test_rdf4j_repository.py +388 -0
- rdf4j_python-0.1.2/rdf4j_python/model/_base_model.py +0 -48
- rdf4j_python-0.1.2/rdf4j_python/model/_dataset.py +0 -38
- rdf4j_python-0.1.2/rdf4j_python/model/_repository_info.py +0 -52
- rdf4j_python-0.1.2/rdf4j_python/model/term.py +0 -14
- rdf4j_python-0.1.2/rdf4j_python/utils/helpers.py +0 -22
- rdf4j_python-0.1.2/rdf4j_python.egg-info/requires.txt +0 -2
- rdf4j_python-0.1.2/tests/test_async_named_graph.py +0 -83
- rdf4j_python-0.1.2/tests/test_rdf4j_repository.py +0 -241
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/LICENSE +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/__init__.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/_client/__init__.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/_client/_client.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/_driver/__init__.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/exception/__init__.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/exception/repo_exception.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/utils/__init__.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python/utils/const.py +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python.egg-info/dependency_links.txt +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/rdf4j_python.egg-info/top_level.txt +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/setup.cfg +0 -0
- {rdf4j_python-0.1.2 → rdf4j_python-0.1.4}/tests/test_client.py +0 -0
|
@@ -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.
|
|
@@ -48,6 +48,7 @@ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
|
|
|
48
48
|
Literal("test_object"),
|
|
49
49
|
)
|
|
50
50
|
await repo.get_statements(subject=IRI("http://example.com/subject"))
|
|
51
|
+
results = await repo.query("SELECT * WHERE { ?s ?p ?o }")
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
For more detailed examples, refer to the [examples](https://github.com/odysa/rdf4j-python/tree/main/examples) directory.
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rdf4j-python"
|
|
3
3
|
authors = [{ name = "Chengxu Bian", email = "cbian564@gmail.com" }]
|
|
4
|
-
version = "0.1.
|
|
4
|
+
version = "0.1.4"
|
|
5
5
|
description = "The Python client for RDF4J"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.10"
|
|
8
|
-
dependencies = ["httpx>=0.28.1", "
|
|
8
|
+
dependencies = ["httpx>=0.28.1", "pyoxigraph>=0.4.10"]
|
|
9
|
+
|
|
10
|
+
[project.optional-dependencies]
|
|
11
|
+
sparqlwrapper = ["sparqlwrapper>=2.0.0"]
|
|
9
12
|
|
|
10
13
|
[dependency-groups]
|
|
11
14
|
dev = [
|
|
@@ -13,11 +16,9 @@ dev = [
|
|
|
13
16
|
"pytest-asyncio>=0.26.0",
|
|
14
17
|
"pytest-docker>=3.2.1",
|
|
15
18
|
"ruff>=0.11.8",
|
|
19
|
+
"ty>=0.0.1a7",
|
|
16
20
|
]
|
|
17
|
-
docs = [
|
|
18
|
-
"furo>=2024.8.6",
|
|
19
|
-
"sphinx>=8",
|
|
20
|
-
]
|
|
21
|
+
docs = ["furo>=2024.8.6", "sphinx>=8"]
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
[tool.pytest.ini_options]
|
|
@@ -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
|
|
|
@@ -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
|
"""
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
import pyoxigraph as og
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class RepositoryMetadata:
|
|
8
|
+
"""
|
|
9
|
+
Represents a repository metadata RDF4J.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
id: str # The repository identifier
|
|
13
|
+
uri: str # The full URI to the repository
|
|
14
|
+
title: str # A human-readable title (currently reusing id)
|
|
15
|
+
readable: bool # Whether the repository is readable
|
|
16
|
+
writable: bool # Whether the repository is writable
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
"""
|
|
20
|
+
Returns a string representation of the RepositoryMetadata.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: A string representation of the RepositoryMetadata.
|
|
24
|
+
"""
|
|
25
|
+
return f"Repository(id={self.id}, title={self.title}, uri={self.uri})"
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_sparql_query_solution(
|
|
29
|
+
cls, query_solution: og.QuerySolution
|
|
30
|
+
) -> "RepositoryMetadata":
|
|
31
|
+
"""
|
|
32
|
+
Create a RepositoryMetadata instance from a SPARQL query result.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
query_solution (og.QuerySolution): The SPARQL query result.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
RepositoryMetadata: The RepositoryMetadata instance.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If the query solution is missing required fields.
|
|
42
|
+
"""
|
|
43
|
+
|
|
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
|
+
|
|
56
|
+
return cls(
|
|
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),
|
|
62
|
+
)
|