rdf4j-python 0.1.1a0__tar.gz → 0.1.2__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.1a0 → rdf4j_python-0.1.2}/PKG-INFO +12 -12
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/README.md +10 -11
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/pyproject.toml +7 -1
- rdf4j_python-0.1.2/rdf4j_python/__init__.py +14 -0
- rdf4j_python-0.1.2/rdf4j_python/_client/_client.py +253 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python/_driver/__init__.py +2 -0
- rdf4j_python-0.1.2/rdf4j_python/_driver/_async_named_graph.py +75 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python/_driver/_async_rdf4j_db.py +55 -29
- rdf4j_python-0.1.2/rdf4j_python/_driver/_async_repository.py +382 -0
- rdf4j_python-0.1.2/rdf4j_python/exception/__init__.py +5 -0
- rdf4j_python-0.1.2/rdf4j_python/exception/repo_exception.py +34 -0
- rdf4j_python-0.1.2/rdf4j_python/model/__init__.py +13 -0
- rdf4j_python-0.1.2/rdf4j_python/model/_base_model.py +48 -0
- rdf4j_python-0.1.2/rdf4j_python/model/_dataset.py +38 -0
- rdf4j_python-0.1.2/rdf4j_python/model/_namespace.py +134 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python/model/_repository_info.py +12 -1
- rdf4j_python-0.1.1a0/rdf4j_python/model/_repository_config.py → rdf4j_python-0.1.2/rdf4j_python/model/repository_config.py +565 -17
- rdf4j_python-0.1.2/rdf4j_python/model/term.py +14 -0
- rdf4j_python-0.1.2/rdf4j_python/model/vocabulary.py +6 -0
- rdf4j_python-0.1.2/rdf4j_python/utils/__init__.py +3 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python/utils/const.py +4 -4
- rdf4j_python-0.1.2/rdf4j_python/utils/helpers.py +22 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/PKG-INFO +12 -12
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/SOURCES.txt +9 -2
- rdf4j_python-0.1.2/tests/test_async_named_graph.py +83 -0
- rdf4j_python-0.1.2/tests/test_client.py +78 -0
- rdf4j_python-0.1.2/tests/test_rdf4j_repository.py +241 -0
- rdf4j_python-0.1.1a0/rdf4j_python/__init__.py +0 -9
- rdf4j_python-0.1.1a0/rdf4j_python/_client/_client.py +0 -102
- rdf4j_python-0.1.1a0/rdf4j_python/_driver/_async_repository.py +0 -109
- rdf4j_python-0.1.1a0/rdf4j_python/exception/repo_exception.py +0 -13
- rdf4j_python-0.1.1a0/rdf4j_python/model/__init__.py +0 -16
- rdf4j_python-0.1.1a0/rdf4j_python/model/_base_model.py +0 -26
- rdf4j_python-0.1.1a0/rdf4j_python/model/_namespace.py +0 -56
- rdf4j_python-0.1.1a0/rdf4j_python/utils/__init__.py +0 -0
- rdf4j_python-0.1.1a0/tests/test_client.py +0 -88
- rdf4j_python-0.1.1a0/tests/test_rdf4j_repo.py +0 -124
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/LICENSE +0 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python/_client/__init__.py +0 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/dependency_links.txt +0 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/requires.txt +0 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/top_level.txt +0 -0
- {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.2}/setup.cfg +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rdf4j-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: The Python client for RDF4J
|
|
5
|
+
Author-email: Chengxu Bian <cbian564@gmail.com>
|
|
5
6
|
Requires-Python: >=3.10
|
|
6
7
|
Description-Content-Type: text/markdown
|
|
7
8
|
License-File: LICENSE
|
|
@@ -42,24 +43,23 @@ pip install rdf4j-python
|
|
|
42
43
|
Here's a basic example of how to use `rdf4j-python` to create an in-memory sail repository
|
|
43
44
|
|
|
44
45
|
```python
|
|
45
|
-
from rdf4j_python import AsyncRdf4j
|
|
46
|
-
from rdf4j_python.model import MemoryStoreConfig, RepositoryConfig
|
|
47
|
-
from rdf4j_python.utils.const import Rdf4jContentType
|
|
48
|
-
|
|
49
46
|
async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
|
|
50
47
|
repo_config = (
|
|
51
|
-
RepositoryConfig.
|
|
52
|
-
MemoryStoreConfig.Builder().persist(False).build(),
|
|
53
|
-
)
|
|
48
|
+
RepositoryConfig.Builder()
|
|
54
49
|
.repo_id("example-repo")
|
|
55
50
|
.title("Example Repository")
|
|
51
|
+
.sail_repository_impl(
|
|
52
|
+
MemoryStoreConfig.Builder().persist(False).build(),
|
|
53
|
+
)
|
|
56
54
|
.build()
|
|
57
55
|
)
|
|
58
|
-
await db.create_repository(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
repo = await db.create_repository(config=repo_config)
|
|
57
|
+
await repo.add_statement(
|
|
58
|
+
IRI("http://example.com/subject"),
|
|
59
|
+
IRI("http://example.com/predicate"),
|
|
60
|
+
Literal("test_object"),
|
|
62
61
|
)
|
|
62
|
+
await repo.get_statements(subject=IRI("http://example.com/subject"))
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
For more detailed examples, refer to the [examples](https://github.com/odysa/rdf4j-python/tree/main/examples) directory.
|
|
@@ -31,24 +31,23 @@ pip install rdf4j-python
|
|
|
31
31
|
Here's a basic example of how to use `rdf4j-python` to create an in-memory sail repository
|
|
32
32
|
|
|
33
33
|
```python
|
|
34
|
-
from rdf4j_python import AsyncRdf4j
|
|
35
|
-
from rdf4j_python.model import MemoryStoreConfig, RepositoryConfig
|
|
36
|
-
from rdf4j_python.utils.const import Rdf4jContentType
|
|
37
|
-
|
|
38
34
|
async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
|
|
39
35
|
repo_config = (
|
|
40
|
-
RepositoryConfig.
|
|
41
|
-
MemoryStoreConfig.Builder().persist(False).build(),
|
|
42
|
-
)
|
|
36
|
+
RepositoryConfig.Builder()
|
|
43
37
|
.repo_id("example-repo")
|
|
44
38
|
.title("Example Repository")
|
|
39
|
+
.sail_repository_impl(
|
|
40
|
+
MemoryStoreConfig.Builder().persist(False).build(),
|
|
41
|
+
)
|
|
45
42
|
.build()
|
|
46
43
|
)
|
|
47
|
-
await db.create_repository(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
repo = await db.create_repository(config=repo_config)
|
|
45
|
+
await repo.add_statement(
|
|
46
|
+
IRI("http://example.com/subject"),
|
|
47
|
+
IRI("http://example.com/predicate"),
|
|
48
|
+
Literal("test_object"),
|
|
51
49
|
)
|
|
50
|
+
await repo.get_statements(subject=IRI("http://example.com/subject"))
|
|
52
51
|
```
|
|
53
52
|
|
|
54
53
|
For more detailed examples, refer to the [examples](https://github.com/odysa/rdf4j-python/tree/main/examples) directory.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rdf4j-python"
|
|
3
|
-
|
|
3
|
+
authors = [{ name = "Chengxu Bian", email = "cbian564@gmail.com" }]
|
|
4
|
+
version = "0.1.2"
|
|
4
5
|
description = "The Python client for RDF4J"
|
|
5
6
|
readme = "README.md"
|
|
6
7
|
requires-python = ">=3.10"
|
|
@@ -13,6 +14,11 @@ dev = [
|
|
|
13
14
|
"pytest-docker>=3.2.1",
|
|
14
15
|
"ruff>=0.11.8",
|
|
15
16
|
]
|
|
17
|
+
docs = [
|
|
18
|
+
"furo>=2024.8.6",
|
|
19
|
+
"sphinx>=8",
|
|
20
|
+
]
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
[tool.pytest.ini_options]
|
|
18
24
|
log_cli = true
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RDF4J Python is a Python library for interacting with RDF4J repositories.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ._driver import AsyncNamedGraph, AsyncRdf4j, AsyncRdf4JRepository
|
|
6
|
+
from .exception import * # noqa: F403
|
|
7
|
+
from .model import * # noqa: F403
|
|
8
|
+
from .utils import * # noqa: F403
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AsyncRdf4j",
|
|
12
|
+
"AsyncRdf4JRepository",
|
|
13
|
+
"AsyncNamedGraph",
|
|
14
|
+
]
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseClient:
|
|
7
|
+
"""Base HTTP client that provides shared URL building functionality."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, base_url: str, timeout: int = 10):
|
|
10
|
+
"""
|
|
11
|
+
Initializes a BaseClient.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
base_url (str): The base URL for the API endpoints.
|
|
15
|
+
timeout (int, optional): Request timeout in seconds. Defaults to 10.
|
|
16
|
+
"""
|
|
17
|
+
self.base_url = base_url.rstrip("/")
|
|
18
|
+
self.timeout = timeout
|
|
19
|
+
|
|
20
|
+
def _build_url(self, path: str) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Builds a full URL by combining the base URL and the given path.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
path (str): The path to append to the base URL.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: The full URL.
|
|
29
|
+
"""
|
|
30
|
+
return f"{self.base_url}/{path.lstrip('/')}"
|
|
31
|
+
|
|
32
|
+
def get_base_url(self) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Returns the base URL.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: The base URL.
|
|
38
|
+
"""
|
|
39
|
+
return self.base_url
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SyncApiClient(BaseClient):
|
|
43
|
+
"""Synchronous API client using httpx.Client."""
|
|
44
|
+
|
|
45
|
+
def __enter__(self):
|
|
46
|
+
"""
|
|
47
|
+
Enters the context and initializes the HTTP client.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
SyncApiClient: The instance of the client.
|
|
51
|
+
"""
|
|
52
|
+
self.client = httpx.Client(timeout=self.timeout).__enter__()
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
56
|
+
"""
|
|
57
|
+
Exits the context and closes the HTTP client.
|
|
58
|
+
"""
|
|
59
|
+
self.client.__exit__(exc_type, exc_value, traceback)
|
|
60
|
+
|
|
61
|
+
def get(
|
|
62
|
+
self,
|
|
63
|
+
path: str,
|
|
64
|
+
params: Optional[Dict[str, Any]] = None,
|
|
65
|
+
headers: Optional[Dict[str, str]] = None,
|
|
66
|
+
) -> httpx.Response:
|
|
67
|
+
"""
|
|
68
|
+
Sends a GET request.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
path (str): API endpoint path.
|
|
72
|
+
params (Optional[Dict[str, Any]]): Query parameters.
|
|
73
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
httpx.Response: The HTTP response.
|
|
77
|
+
"""
|
|
78
|
+
return self.client.get(self._build_url(path), params=params, headers=headers)
|
|
79
|
+
|
|
80
|
+
def post(
|
|
81
|
+
self,
|
|
82
|
+
path: str,
|
|
83
|
+
data: Optional[Dict[str, Any]] = None,
|
|
84
|
+
json: Optional[Any] = None,
|
|
85
|
+
headers: Optional[Dict[str, str]] = None,
|
|
86
|
+
) -> httpx.Response:
|
|
87
|
+
"""
|
|
88
|
+
Sends a POST request.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
path (str): API endpoint path.
|
|
92
|
+
data (Optional[Dict[str, Any]]): Form-encoded body data.
|
|
93
|
+
json (Optional[Any]): JSON-encoded body data.
|
|
94
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
httpx.Response: The HTTP response.
|
|
98
|
+
"""
|
|
99
|
+
return self.client.post(
|
|
100
|
+
self._build_url(path), data=data, json=json, headers=headers
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def put(
|
|
104
|
+
self,
|
|
105
|
+
path: str,
|
|
106
|
+
data: Optional[Dict[str, Any]] = None,
|
|
107
|
+
json: Optional[Any] = None,
|
|
108
|
+
headers: Optional[Dict[str, str]] = None,
|
|
109
|
+
) -> httpx.Response:
|
|
110
|
+
"""
|
|
111
|
+
Sends a PUT request.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
path (str): API endpoint path.
|
|
115
|
+
data (Optional[Dict[str, Any]]): Form-encoded body data.
|
|
116
|
+
json (Optional[Any]): JSON-encoded body data.
|
|
117
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
httpx.Response: The HTTP response.
|
|
121
|
+
"""
|
|
122
|
+
return self.client.put(
|
|
123
|
+
self._build_url(path), data=data, json=json, headers=headers
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def delete(
|
|
127
|
+
self, path: str, headers: Optional[Dict[str, str]] = None
|
|
128
|
+
) -> httpx.Response:
|
|
129
|
+
"""
|
|
130
|
+
Sends a DELETE request.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
path (str): API endpoint path.
|
|
134
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
httpx.Response: The HTTP response.
|
|
138
|
+
"""
|
|
139
|
+
return self.client.delete(self._build_url(path), headers=headers)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class AsyncApiClient(BaseClient):
|
|
143
|
+
"""Asynchronous API client using httpx.AsyncClient."""
|
|
144
|
+
|
|
145
|
+
async def __aenter__(self):
|
|
146
|
+
"""
|
|
147
|
+
Enters the async context and initializes the HTTP client.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
AsyncApiClient: The instance of the client.
|
|
151
|
+
"""
|
|
152
|
+
self.client = await httpx.AsyncClient(timeout=self.timeout).__aenter__()
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
156
|
+
"""
|
|
157
|
+
Exits the async context and closes the HTTP client.
|
|
158
|
+
"""
|
|
159
|
+
await self.client.__aexit__(exc_type, exc_value, traceback)
|
|
160
|
+
|
|
161
|
+
async def get(
|
|
162
|
+
self,
|
|
163
|
+
path: str,
|
|
164
|
+
params: Optional[Dict[str, Any]] = None,
|
|
165
|
+
headers: Optional[Dict[str, str]] = None,
|
|
166
|
+
) -> httpx.Response:
|
|
167
|
+
"""
|
|
168
|
+
Sends an asynchronous GET request.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
path (str): API endpoint path.
|
|
172
|
+
params (Optional[Dict[str, Any]]): Query parameters.
|
|
173
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
httpx.Response: The HTTP response.
|
|
177
|
+
"""
|
|
178
|
+
return await self.client.get(
|
|
179
|
+
self._build_url(path), params=params, headers=headers
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
async def post(
|
|
183
|
+
self,
|
|
184
|
+
path: str,
|
|
185
|
+
content: Optional[str] = None,
|
|
186
|
+
json: Optional[Any] = None,
|
|
187
|
+
headers: Optional[Dict[str, str]] = None,
|
|
188
|
+
) -> httpx.Response:
|
|
189
|
+
"""
|
|
190
|
+
Sends an asynchronous POST request.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
path (str): API endpoint path.
|
|
194
|
+
content (Optional[str]): Raw string content to include in the request body.
|
|
195
|
+
json (Optional[Any]): JSON-encoded body data.
|
|
196
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
httpx.Response: The HTTP response.
|
|
200
|
+
"""
|
|
201
|
+
return await self.client.post(
|
|
202
|
+
self._build_url(path), content=content, json=json, headers=headers
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
async def put(
|
|
206
|
+
self,
|
|
207
|
+
path: str,
|
|
208
|
+
content: Optional[bytes] = None,
|
|
209
|
+
params: Optional[Dict[str, Any]] = None,
|
|
210
|
+
json: Optional[Any] = None,
|
|
211
|
+
headers: Optional[Dict[str, str]] = None,
|
|
212
|
+
) -> httpx.Response:
|
|
213
|
+
"""
|
|
214
|
+
Sends an asynchronous PUT request.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
path (str): API endpoint path.
|
|
218
|
+
content (Optional[bytes]): Raw bytes to include in the request body.
|
|
219
|
+
params (Optional[Dict[str, Any]]): Query parameters.
|
|
220
|
+
json (Optional[Any]): JSON-encoded body data.
|
|
221
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
httpx.Response: The HTTP response.
|
|
225
|
+
"""
|
|
226
|
+
return await self.client.put(
|
|
227
|
+
self._build_url(path),
|
|
228
|
+
content=content,
|
|
229
|
+
json=json,
|
|
230
|
+
headers=headers,
|
|
231
|
+
params=params,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
async def delete(
|
|
235
|
+
self,
|
|
236
|
+
path: str,
|
|
237
|
+
params: Optional[Dict[str, Any]] = None,
|
|
238
|
+
headers: Optional[Dict[str, str]] = None,
|
|
239
|
+
) -> httpx.Response:
|
|
240
|
+
"""
|
|
241
|
+
Sends an asynchronous DELETE request.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
path (str): API endpoint path.
|
|
245
|
+
params (Optional[Dict[str, Any]]): Query parameters.
|
|
246
|
+
headers (Optional[Dict[str, str]]): Request headers.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
httpx.Response: The HTTP response.
|
|
250
|
+
"""
|
|
251
|
+
return await self.client.delete(
|
|
252
|
+
self._build_url(path), params=params, headers=headers
|
|
253
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
from rdf4j_python._client import AsyncApiClient
|
|
4
|
+
from rdf4j_python.model import RDF4JDataSet
|
|
5
|
+
from rdf4j_python.model.term import IRI, RDFStatement
|
|
6
|
+
from rdf4j_python.utils.const import Rdf4jContentType
|
|
7
|
+
from rdf4j_python.utils.helpers import serialize_statements
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncNamedGraph:
|
|
11
|
+
"""Asynchronous interface for operations on a specific RDF4J named graph."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, client: AsyncApiClient, repository_id: str, graph_uri: str):
|
|
14
|
+
"""Initializes the AsyncNamedGraph.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
client (AsyncApiClient): The RDF4J HTTP client.
|
|
18
|
+
repository_id (str): The ID of the RDF4J repository.
|
|
19
|
+
graph_uri (str): The URI identifying the named graph.
|
|
20
|
+
"""
|
|
21
|
+
self._client = client
|
|
22
|
+
self._repository_id = repository_id
|
|
23
|
+
self._graph_uri = graph_uri
|
|
24
|
+
|
|
25
|
+
async def get(self) -> RDF4JDataSet:
|
|
26
|
+
"""Fetches all RDF statements from this named graph.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: RDF data serialized in the requested format.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
httpx.HTTPStatusError: If the request fails.
|
|
33
|
+
"""
|
|
34
|
+
path = f"/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
|
|
35
|
+
headers = {"Accept": Rdf4jContentType.NQUADS}
|
|
36
|
+
response = await self._client.get(path, headers=headers)
|
|
37
|
+
response.raise_for_status()
|
|
38
|
+
return RDF4JDataSet.from_raw_text(response.text)
|
|
39
|
+
|
|
40
|
+
async def add(self, statements: Iterable[RDFStatement]):
|
|
41
|
+
"""Adds RDF statements to this named graph.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
statements (Iterable[RDFStatement]): RDF statements to add.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
httpx.HTTPStatusError: If the request fails.
|
|
48
|
+
"""
|
|
49
|
+
path = f"/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
|
|
50
|
+
headers = {"Content-Type": Rdf4jContentType.NQUADS}
|
|
51
|
+
response = await self._client.post(
|
|
52
|
+
path, content=serialize_statements(statements), headers=headers
|
|
53
|
+
)
|
|
54
|
+
response.raise_for_status()
|
|
55
|
+
|
|
56
|
+
async def clear(self):
|
|
57
|
+
"""Deletes all statements from this named graph.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
httpx.HTTPStatusError: If the request fails.
|
|
61
|
+
"""
|
|
62
|
+
path = f"/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
|
|
63
|
+
response = await self._client.delete(path)
|
|
64
|
+
response.raise_for_status()
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def iri(self) -> IRI:
|
|
68
|
+
"""Returns the IRI of the named graph.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: The graph IRI.
|
|
72
|
+
"""
|
|
73
|
+
return IRI(
|
|
74
|
+
f"{self._client.get_base_url()}/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
|
|
75
|
+
)
|
|
@@ -1,43 +1,66 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
1
|
import httpx
|
|
4
2
|
import rdflib
|
|
5
3
|
|
|
6
|
-
from rdf4j_python import AsyncApiClient
|
|
4
|
+
from rdf4j_python._client import AsyncApiClient
|
|
7
5
|
from rdf4j_python.exception.repo_exception import (
|
|
8
6
|
RepositoryCreationException,
|
|
9
7
|
RepositoryDeletionException,
|
|
10
8
|
)
|
|
11
9
|
from rdf4j_python.model._repository_info import RepositoryMetadata
|
|
10
|
+
from rdf4j_python.model.repository_config import RepositoryConfig
|
|
12
11
|
from rdf4j_python.utils.const import Rdf4jContentType
|
|
13
12
|
|
|
14
13
|
from ._async_repository import AsyncRdf4JRepository
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class AsyncRdf4j:
|
|
17
|
+
"""Asynchronous entry point for interacting with an RDF4J server."""
|
|
18
|
+
|
|
18
19
|
_client: AsyncApiClient
|
|
19
20
|
_base_url: str
|
|
20
21
|
|
|
21
22
|
def __init__(self, base_url: str):
|
|
23
|
+
"""Initializes the RDF4J API client.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
base_url (str): Base URL of the RDF4J server.
|
|
27
|
+
"""
|
|
22
28
|
self._base_url = base_url.rstrip("/")
|
|
23
29
|
|
|
24
30
|
async def __aenter__(self):
|
|
31
|
+
"""Enters the async context and initializes the HTTP client.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
AsyncRdf4j: The initialized RDF4J interface.
|
|
35
|
+
"""
|
|
25
36
|
self._client = await AsyncApiClient(base_url=self._base_url).__aenter__()
|
|
26
37
|
return self
|
|
27
38
|
|
|
28
39
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
40
|
+
"""Closes the HTTP client when exiting the async context."""
|
|
29
41
|
await self._client.__aexit__(exc_type, exc_value, traceback)
|
|
30
42
|
|
|
31
43
|
async def get_protocol_version(self) -> str:
|
|
44
|
+
"""Fetches the RDF4J protocol version.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
str: The protocol version string.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
httpx.HTTPStatusError: If the request fails.
|
|
51
|
+
"""
|
|
32
52
|
response = await self._client.get("/protocol")
|
|
33
53
|
response.raise_for_status()
|
|
34
54
|
return response.text
|
|
35
55
|
|
|
36
56
|
async def list_repositories(self) -> list[RepositoryMetadata]:
|
|
37
|
-
"""
|
|
38
|
-
|
|
57
|
+
"""Lists all available RDF4J repositories.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
list[RepositoryMetadata]: A list of repository metadata objects.
|
|
39
61
|
|
|
40
|
-
:
|
|
62
|
+
Raises:
|
|
63
|
+
httpx.HTTPStatusError: If the request fails.
|
|
41
64
|
"""
|
|
42
65
|
response = await self._client.get(
|
|
43
66
|
"/repositories",
|
|
@@ -46,54 +69,57 @@ class AsyncRdf4j:
|
|
|
46
69
|
result = rdflib.query.Result.parse(
|
|
47
70
|
response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
|
|
48
71
|
)
|
|
49
|
-
|
|
50
72
|
return [
|
|
51
73
|
RepositoryMetadata.from_rdflib_binding(binding)
|
|
52
74
|
for binding in result.bindings
|
|
53
75
|
]
|
|
54
76
|
|
|
55
77
|
async def get_repository(self, repository_id: str) -> AsyncRdf4JRepository:
|
|
56
|
-
"""
|
|
57
|
-
|
|
78
|
+
"""Gets an interface to a specific RDF4J repository.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
repository_id (str): The ID of the repository.
|
|
58
82
|
|
|
59
|
-
:
|
|
60
|
-
|
|
83
|
+
Returns:
|
|
84
|
+
AsyncRdf4JRepository: An async interface for the repository.
|
|
61
85
|
"""
|
|
62
86
|
return AsyncRdf4JRepository(self._client, repository_id)
|
|
63
87
|
|
|
64
88
|
async def create_repository(
|
|
65
89
|
self,
|
|
66
|
-
|
|
67
|
-
rdf_config_data: str,
|
|
68
|
-
content_type: Union[Rdf4jContentType, str] = Rdf4jContentType.TURTLE,
|
|
90
|
+
config: RepositoryConfig,
|
|
69
91
|
) -> AsyncRdf4JRepository:
|
|
70
|
-
"""
|
|
71
|
-
Create a new RDF4J repository.
|
|
92
|
+
"""Creates a new RDF4J repository using RDF configuration.
|
|
72
93
|
|
|
73
|
-
:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"""
|
|
77
|
-
path = f"/repositories/{repository_id}"
|
|
94
|
+
Args:
|
|
95
|
+
repository_id (str): The repository ID to create.
|
|
96
|
+
config (RepositoryConfig): RDF configuration.
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
headers = {"Content-Type": content_type}
|
|
98
|
+
Returns:
|
|
99
|
+
AsyncRdf4JRepository: An async interface to the newly created repository.
|
|
82
100
|
|
|
101
|
+
Raises:
|
|
102
|
+
RepositoryCreationException: If repository creation fails.
|
|
103
|
+
"""
|
|
104
|
+
path = f"/repositories/{config.repo_id}"
|
|
105
|
+
headers = {"Content-Type": Rdf4jContentType.TURTLE}
|
|
83
106
|
response: httpx.Response = await self._client.put(
|
|
84
|
-
path, content=
|
|
107
|
+
path, content=config.to_turtle(), headers=headers
|
|
85
108
|
)
|
|
86
109
|
if response.status_code != httpx.codes.NO_CONTENT:
|
|
87
110
|
raise RepositoryCreationException(
|
|
88
111
|
f"Repository creation failed: {response.status_code} - {response.text}"
|
|
89
112
|
)
|
|
90
|
-
return AsyncRdf4JRepository(self._client,
|
|
113
|
+
return AsyncRdf4JRepository(self._client, config.repo_id)
|
|
91
114
|
|
|
92
115
|
async def delete_repository(self, repository_id: str):
|
|
93
|
-
"""
|
|
94
|
-
|
|
116
|
+
"""Deletes a repository and all its data and configuration.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
repository_id (str): The ID of the repository to delete.
|
|
95
120
|
|
|
96
|
-
:
|
|
121
|
+
Raises:
|
|
122
|
+
RepositoryDeletionException: If the deletion fails.
|
|
97
123
|
"""
|
|
98
124
|
path = f"/repositories/{repository_id}"
|
|
99
125
|
response = await self._client.delete(path)
|