rdf4j-python 0.1.1a0__tar.gz → 0.1.3__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.
Files changed (45) hide show
  1. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/PKG-INFO +13 -13
  2. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/README.md +10 -11
  3. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/pyproject.toml +5 -2
  4. rdf4j_python-0.1.3/rdf4j_python/__init__.py +14 -0
  5. rdf4j_python-0.1.3/rdf4j_python/_client/_client.py +253 -0
  6. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/rdf4j_python/_driver/__init__.py +2 -0
  7. rdf4j_python-0.1.3/rdf4j_python/_driver/_async_named_graph.py +76 -0
  8. rdf4j_python-0.1.3/rdf4j_python/_driver/_async_rdf4j_db.py +129 -0
  9. rdf4j_python-0.1.3/rdf4j_python/_driver/_async_repository.py +391 -0
  10. rdf4j_python-0.1.3/rdf4j_python/exception/__init__.py +5 -0
  11. rdf4j_python-0.1.3/rdf4j_python/exception/repo_exception.py +34 -0
  12. rdf4j_python-0.1.3/rdf4j_python/model/__init__.py +11 -0
  13. rdf4j_python-0.1.3/rdf4j_python/model/_namespace.py +157 -0
  14. rdf4j_python-0.1.3/rdf4j_python/model/_repository_info.py +62 -0
  15. rdf4j_python-0.1.1a0/rdf4j_python/model/_repository_config.py → rdf4j_python-0.1.3/rdf4j_python/model/repository_config.py +671 -70
  16. rdf4j_python-0.1.3/rdf4j_python/model/term.py +20 -0
  17. rdf4j_python-0.1.3/rdf4j_python/model/vocabulary.py +7 -0
  18. rdf4j_python-0.1.3/rdf4j_python/utils/__init__.py +3 -0
  19. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/rdf4j_python/utils/const.py +4 -4
  20. rdf4j_python-0.1.3/rdf4j_python/utils/helpers.py +20 -0
  21. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/PKG-INFO +13 -13
  22. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/SOURCES.txt +9 -3
  23. rdf4j_python-0.1.3/rdf4j_python.egg-info/requires.txt +2 -0
  24. rdf4j_python-0.1.3/tests/test_async_named_graph.py +89 -0
  25. rdf4j_python-0.1.3/tests/test_client.py +78 -0
  26. rdf4j_python-0.1.3/tests/test_helpers.py +21 -0
  27. rdf4j_python-0.1.3/tests/test_rdf4j_repository.py +259 -0
  28. rdf4j_python-0.1.1a0/rdf4j_python/__init__.py +0 -9
  29. rdf4j_python-0.1.1a0/rdf4j_python/_client/_client.py +0 -102
  30. rdf4j_python-0.1.1a0/rdf4j_python/_driver/_async_rdf4j_db.py +0 -103
  31. rdf4j_python-0.1.1a0/rdf4j_python/_driver/_async_repository.py +0 -109
  32. rdf4j_python-0.1.1a0/rdf4j_python/exception/repo_exception.py +0 -13
  33. rdf4j_python-0.1.1a0/rdf4j_python/model/__init__.py +0 -16
  34. rdf4j_python-0.1.1a0/rdf4j_python/model/_base_model.py +0 -26
  35. rdf4j_python-0.1.1a0/rdf4j_python/model/_namespace.py +0 -56
  36. rdf4j_python-0.1.1a0/rdf4j_python/model/_repository_info.py +0 -41
  37. rdf4j_python-0.1.1a0/rdf4j_python/utils/__init__.py +0 -0
  38. rdf4j_python-0.1.1a0/rdf4j_python.egg-info/requires.txt +0 -2
  39. rdf4j_python-0.1.1a0/tests/test_client.py +0 -88
  40. rdf4j_python-0.1.1a0/tests/test_rdf4j_repo.py +0 -124
  41. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/LICENSE +0 -0
  42. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/rdf4j_python/_client/__init__.py +0 -0
  43. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/dependency_links.txt +0 -0
  44. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/top_level.txt +0 -0
  45. {rdf4j_python-0.1.1a0 → rdf4j_python-0.1.3}/setup.cfg +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdf4j-python
3
- Version: 0.1.1a0
3
+ Version: 0.1.3
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
8
9
  Requires-Dist: httpx>=0.28.1
9
- Requires-Dist: rdflib>=7.1.4
10
+ Requires-Dist: pyoxigraph>=0.4.10
10
11
  Dynamic: license-file
11
12
 
12
13
  # 🐍 rdf4j-python
@@ -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.builder_with_sail_repository(
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
- repository_id=repo_config.repo_id,
60
- rdf_config_data=repo_config.to_turtle(),
61
- content_type=Rdf4jContentType.TURTLE,
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.builder_with_sail_repository(
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
- repository_id=repo_config.repo_id,
49
- rdf_config_data=repo_config.to_turtle(),
50
- content_type=Rdf4jContentType.TURTLE,
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,10 +1,11 @@
1
1
  [project]
2
2
  name = "rdf4j-python"
3
- version = "0.1.1a"
3
+ authors = [{ name = "Chengxu Bian", email = "cbian564@gmail.com" }]
4
+ version = "0.1.3"
4
5
  description = "The Python client for RDF4J"
5
6
  readme = "README.md"
6
7
  requires-python = ">=3.10"
7
- dependencies = ["httpx>=0.28.1", "rdflib>=7.1.4"]
8
+ dependencies = ["httpx>=0.28.1", "pyoxigraph>=0.4.10"]
8
9
 
9
10
  [dependency-groups]
10
11
  dev = [
@@ -13,6 +14,8 @@ dev = [
13
14
  "pytest-docker>=3.2.1",
14
15
  "ruff>=0.11.8",
15
16
  ]
17
+ docs = ["furo>=2024.8.6", "sphinx>=8"]
18
+
16
19
 
17
20
  [tool.pytest.ini_options]
18
21
  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
+ )
@@ -1,7 +1,9 @@
1
+ from ._async_named_graph import AsyncNamedGraph
1
2
  from ._async_rdf4j_db import AsyncRdf4j
2
3
  from ._async_repository import AsyncRdf4JRepository
3
4
 
4
5
  __all__ = [
5
6
  "AsyncRdf4j",
6
7
  "AsyncRdf4JRepository",
8
+ "AsyncNamedGraph",
7
9
  ]
@@ -0,0 +1,76 @@
1
+ from typing import Iterable
2
+
3
+ import pyoxigraph as og
4
+
5
+ from rdf4j_python._client import AsyncApiClient
6
+ from rdf4j_python.model.term import IRI, Quad, QuadResultSet, Triple
7
+ from rdf4j_python.utils.const import Rdf4jContentType
8
+ from rdf4j_python.utils.helpers import serialize_statements
9
+
10
+
11
+ class AsyncNamedGraph:
12
+ """Asynchronous interface for operations on a specific RDF4J named graph."""
13
+
14
+ def __init__(self, client: AsyncApiClient, repository_id: str, graph_uri: str):
15
+ """Initializes the AsyncNamedGraph.
16
+
17
+ Args:
18
+ client (AsyncApiClient): The RDF4J HTTP client.
19
+ repository_id (str): The ID of the RDF4J repository.
20
+ graph_uri (str): The URI identifying the named graph.
21
+ """
22
+ self._client = client
23
+ self._repository_id = repository_id
24
+ self._graph_uri = graph_uri
25
+
26
+ async def get(self) -> QuadResultSet:
27
+ """Fetches all RDF statements from this named graph.
28
+
29
+ Returns:
30
+ QuadResultSet: RDF data serialized in the requested format.
31
+
32
+ Raises:
33
+ httpx.HTTPStatusError: If the request fails.
34
+ """
35
+ path = f"/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
36
+ headers = {"Accept": Rdf4jContentType.NQUADS}
37
+ response = await self._client.get(path, headers=headers)
38
+ response.raise_for_status()
39
+ return og.parse(response.content, format=og.RdfFormat.N_QUADS)
40
+
41
+ async def add(self, statements: Iterable[Quad] | Iterable[Triple]):
42
+ """Adds RDF statements to this named graph.
43
+
44
+ Args:
45
+ statements (Iterable[Quad] | Iterable[Triple]): RDF statements to add.
46
+
47
+ Raises:
48
+ httpx.HTTPStatusError: If the request fails.
49
+ """
50
+ path = f"/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
51
+ headers = {"Content-Type": Rdf4jContentType.NQUADS}
52
+ response = await self._client.post(
53
+ path, content=serialize_statements(statements), headers=headers
54
+ )
55
+ response.raise_for_status()
56
+
57
+ async def clear(self):
58
+ """Deletes all statements from this named graph.
59
+
60
+ Raises:
61
+ httpx.HTTPStatusError: If the request fails.
62
+ """
63
+ path = f"/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
64
+ response = await self._client.delete(path)
65
+ response.raise_for_status()
66
+
67
+ @property
68
+ def iri(self) -> IRI:
69
+ """Returns the IRI of the named graph.
70
+
71
+ Returns:
72
+ str: The graph IRI.
73
+ """
74
+ return IRI(
75
+ f"{self._client.get_base_url()}/repositories/{self._repository_id}/rdf-graphs/{self._graph_uri}"
76
+ )
@@ -0,0 +1,129 @@
1
+ import httpx
2
+ import pyoxigraph as og
3
+
4
+ from rdf4j_python._client import AsyncApiClient
5
+ from rdf4j_python.exception.repo_exception import (
6
+ RepositoryCreationException,
7
+ RepositoryDeletionException,
8
+ )
9
+ from rdf4j_python.model._repository_info import RepositoryMetadata
10
+ from rdf4j_python.model.repository_config import RepositoryConfig
11
+ from rdf4j_python.utils.const import Rdf4jContentType
12
+
13
+ from ._async_repository import AsyncRdf4JRepository
14
+
15
+
16
+ class AsyncRdf4j:
17
+ """Asynchronous entry point for interacting with an RDF4J server."""
18
+
19
+ _client: AsyncApiClient
20
+ _base_url: str
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
+ """
28
+ self._base_url = base_url.rstrip("/")
29
+
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
+ """
36
+ self._client = await AsyncApiClient(base_url=self._base_url).__aenter__()
37
+ return self
38
+
39
+ async def __aexit__(self, exc_type, exc_value, traceback):
40
+ """Closes the HTTP client when exiting the async context."""
41
+ await self._client.__aexit__(exc_type, exc_value, traceback)
42
+
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
+ """
52
+ response = await self._client.get("/protocol")
53
+ response.raise_for_status()
54
+ return response.text
55
+
56
+ async def list_repositories(self) -> list[RepositoryMetadata]:
57
+ """Lists all available RDF4J repositories.
58
+
59
+ Returns:
60
+ list[RepositoryMetadata]: A list of repository metadata objects.
61
+
62
+ Raises:
63
+ httpx.HTTPStatusError: If the request fails.
64
+ """
65
+ response = await self._client.get(
66
+ "/repositories",
67
+ headers={"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON},
68
+ )
69
+ query_solutions = og.parse_query_results(
70
+ response.text, format=og.QueryResultsFormat.JSON
71
+ )
72
+ return [
73
+ RepositoryMetadata.from_sparql_query_solution(query_solution)
74
+ for query_solution in query_solutions
75
+ ]
76
+
77
+ async def get_repository(self, repository_id: str) -> AsyncRdf4JRepository:
78
+ """Gets an interface to a specific RDF4J repository.
79
+
80
+ Args:
81
+ repository_id (str): The ID of the repository.
82
+
83
+ Returns:
84
+ AsyncRdf4JRepository: An async interface for the repository.
85
+ """
86
+ return AsyncRdf4JRepository(self._client, repository_id)
87
+
88
+ async def create_repository(
89
+ self,
90
+ config: RepositoryConfig,
91
+ ) -> AsyncRdf4JRepository:
92
+ """Creates a new RDF4J repository using RDF configuration.
93
+
94
+ Args:
95
+ repository_id (str): The repository ID to create.
96
+ config (RepositoryConfig): RDF configuration.
97
+
98
+ Returns:
99
+ AsyncRdf4JRepository: An async interface to the newly created repository.
100
+
101
+ Raises:
102
+ RepositoryCreationException: If repository creation fails.
103
+ """
104
+ path = f"/repositories/{config.repo_id}"
105
+ headers = {"Content-Type": Rdf4jContentType.TURTLE}
106
+ response: httpx.Response = await self._client.put(
107
+ path, content=config.to_turtle(), headers=headers
108
+ )
109
+ if response.status_code != httpx.codes.NO_CONTENT:
110
+ raise RepositoryCreationException(
111
+ f"Repository creation failed: {response.status_code} - {response.text}"
112
+ )
113
+ return AsyncRdf4JRepository(self._client, config.repo_id)
114
+
115
+ async def delete_repository(self, repository_id: str):
116
+ """Deletes a repository and all its data and configuration.
117
+
118
+ Args:
119
+ repository_id (str): The ID of the repository to delete.
120
+
121
+ Raises:
122
+ RepositoryDeletionException: If the deletion fails.
123
+ """
124
+ path = f"/repositories/{repository_id}"
125
+ response = await self._client.delete(path)
126
+ if response.status_code != httpx.codes.NO_CONTENT:
127
+ raise RepositoryDeletionException(
128
+ f"Failed to delete repository '{repository_id}': {response.status_code} - {response.text}"
129
+ )