rdf4j-python 0.1.0__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.
Files changed (45) hide show
  1. rdf4j_python-0.1.2/LICENSE +28 -0
  2. rdf4j_python-0.1.2/PKG-INFO +77 -0
  3. rdf4j_python-0.1.2/README.md +65 -0
  4. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/pyproject.toml +7 -1
  5. rdf4j_python-0.1.2/rdf4j_python/__init__.py +14 -0
  6. rdf4j_python-0.1.2/rdf4j_python/_client/_client.py +253 -0
  7. rdf4j_python-0.1.2/rdf4j_python/_driver/__init__.py +9 -0
  8. rdf4j_python-0.1.2/rdf4j_python/_driver/_async_named_graph.py +75 -0
  9. rdf4j_python-0.1.2/rdf4j_python/_driver/_async_rdf4j_db.py +129 -0
  10. rdf4j_python-0.1.2/rdf4j_python/_driver/_async_repository.py +382 -0
  11. rdf4j_python-0.1.2/rdf4j_python/exception/__init__.py +5 -0
  12. rdf4j_python-0.1.2/rdf4j_python/exception/repo_exception.py +34 -0
  13. rdf4j_python-0.1.2/rdf4j_python/model/__init__.py +13 -0
  14. rdf4j_python-0.1.2/rdf4j_python/model/_base_model.py +48 -0
  15. rdf4j_python-0.1.2/rdf4j_python/model/_dataset.py +38 -0
  16. rdf4j_python-0.1.2/rdf4j_python/model/_namespace.py +134 -0
  17. rdf4j_python-0.1.0/rdf4j_python/model/repository.py → rdf4j_python-0.1.2/rdf4j_python/model/_repository_info.py +15 -4
  18. rdf4j_python-0.1.2/rdf4j_python/model/repository_config.py +1447 -0
  19. rdf4j_python-0.1.2/rdf4j_python/model/term.py +14 -0
  20. rdf4j_python-0.1.2/rdf4j_python/model/vocabulary.py +6 -0
  21. rdf4j_python-0.1.2/rdf4j_python/utils/__init__.py +3 -0
  22. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/rdf4j_python/utils/const.py +4 -4
  23. rdf4j_python-0.1.2/rdf4j_python/utils/helpers.py +22 -0
  24. rdf4j_python-0.1.2/rdf4j_python.egg-info/PKG-INFO +77 -0
  25. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/SOURCES.txt +14 -2
  26. rdf4j_python-0.1.2/tests/test_async_named_graph.py +83 -0
  27. rdf4j_python-0.1.2/tests/test_client.py +78 -0
  28. rdf4j_python-0.1.2/tests/test_rdf4j_repository.py +241 -0
  29. rdf4j_python-0.1.0/PKG-INFO +0 -8
  30. rdf4j_python-0.1.0/README.md +0 -0
  31. rdf4j_python-0.1.0/rdf4j_python/__init__.py +0 -9
  32. rdf4j_python-0.1.0/rdf4j_python/_client/_client.py +0 -102
  33. rdf4j_python-0.1.0/rdf4j_python/_driver/__init__.py +0 -7
  34. rdf4j_python-0.1.0/rdf4j_python/_driver/_async_rdf4j_db.py +0 -101
  35. rdf4j_python-0.1.0/rdf4j_python/_driver/_async_repository.py +0 -51
  36. rdf4j_python-0.1.0/rdf4j_python/exception/repo_exception.py +0 -4
  37. rdf4j_python-0.1.0/rdf4j_python/model/_base_model.py +0 -26
  38. rdf4j_python-0.1.0/rdf4j_python/utils/__init__.py +0 -0
  39. rdf4j_python-0.1.0/rdf4j_python.egg-info/PKG-INFO +0 -8
  40. rdf4j_python-0.1.0/tests/test_repository.py +0 -75
  41. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/rdf4j_python/_client/__init__.py +0 -0
  42. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/dependency_links.txt +0 -0
  43. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/requires.txt +0 -0
  44. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/rdf4j_python.egg-info/top_level.txt +0 -0
  45. {rdf4j_python-0.1.0 → rdf4j_python-0.1.2}/setup.cfg +0 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Chengxu Bian
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: rdf4j-python
3
+ Version: 0.1.2
4
+ Summary: The Python client for RDF4J
5
+ Author-email: Chengxu Bian <cbian564@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: httpx>=0.28.1
10
+ Requires-Dist: rdflib>=7.1.4
11
+ Dynamic: license-file
12
+
13
+ # 🐍 rdf4j-python
14
+
15
+ **A Pythonic interface to the powerful Java-based [Eclipse RDF4J](https://rdf4j.org/) framework.**
16
+
17
+ > ⚠️ **Note:** This project is currently under active development and considered **experimental**. Interfaces may change. Use with caution in production environments—and feel free to help shape its future!
18
+
19
+ ✅ **Supports both asynchronous (`async/await`) and synchronous programming styles.**
20
+
21
+ ## 🌐 Overview
22
+
23
+ `rdf4j-python` bridges the gap between Python applications and the [Eclipse RDF4J](https://rdf4j.org/) framework, enabling seamless interaction with RDF4J repositories directly from Python. This integration allows developers to leverage RDF4J's robust capabilities for managing RDF data and executing SPARQL queries without leaving the Python ecosystem.
24
+
25
+ ## 🚀 Features
26
+
27
+ - **Seamless Integration**: Interact with RDF4J repositories using Pythonic constructs.
28
+ - **SPARQL Support**: Execute SPARQL queries and updates effortlessly.
29
+ - **Repository Management**: Create, access, and manage RDF4J repositories programmatically.
30
+ - **Data Handling**: Add and retrieve RDF triples with ease.
31
+ - **Format Flexibility**: Support for various RDF serialization formats.
32
+
33
+ ## 📦 Installation
34
+
35
+ Install via pip:
36
+
37
+ ```bash
38
+ pip install rdf4j-python
39
+ ```
40
+
41
+ ## 🧪 Usage (Async)
42
+
43
+ Here's a basic example of how to use `rdf4j-python` to create an in-memory sail repository
44
+
45
+ ```python
46
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
47
+ repo_config = (
48
+ RepositoryConfig.Builder()
49
+ .repo_id("example-repo")
50
+ .title("Example Repository")
51
+ .sail_repository_impl(
52
+ MemoryStoreConfig.Builder().persist(False).build(),
53
+ )
54
+ .build()
55
+ )
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"),
61
+ )
62
+ await repo.get_statements(subject=IRI("http://example.com/subject"))
63
+ ```
64
+
65
+ For more detailed examples, refer to the [examples](https://github.com/odysa/rdf4j-python/tree/main/examples) directory.
66
+
67
+ ## 🤝 Contributing
68
+
69
+ We welcome contributions and feedback! If you'd like to help improve this project:
70
+
71
+ - Fork the repo and submit a pull request
72
+ - Open an issue for bugs, feature ideas, or discussions
73
+ - ⭐ Star the repo if you find it useful!
74
+
75
+ ## 📄 License
76
+
77
+ This project is licensed under the MIT License. See the [LICENSE](https://github.com/odysa/rdf4j-python/blob/main/LICENSE) file for details.
@@ -0,0 +1,65 @@
1
+ # 🐍 rdf4j-python
2
+
3
+ **A Pythonic interface to the powerful Java-based [Eclipse RDF4J](https://rdf4j.org/) framework.**
4
+
5
+ > ⚠️ **Note:** This project is currently under active development and considered **experimental**. Interfaces may change. Use with caution in production environments—and feel free to help shape its future!
6
+
7
+ ✅ **Supports both asynchronous (`async/await`) and synchronous programming styles.**
8
+
9
+ ## 🌐 Overview
10
+
11
+ `rdf4j-python` bridges the gap between Python applications and the [Eclipse RDF4J](https://rdf4j.org/) framework, enabling seamless interaction with RDF4J repositories directly from Python. This integration allows developers to leverage RDF4J's robust capabilities for managing RDF data and executing SPARQL queries without leaving the Python ecosystem.
12
+
13
+ ## 🚀 Features
14
+
15
+ - **Seamless Integration**: Interact with RDF4J repositories using Pythonic constructs.
16
+ - **SPARQL Support**: Execute SPARQL queries and updates effortlessly.
17
+ - **Repository Management**: Create, access, and manage RDF4J repositories programmatically.
18
+ - **Data Handling**: Add and retrieve RDF triples with ease.
19
+ - **Format Flexibility**: Support for various RDF serialization formats.
20
+
21
+ ## 📦 Installation
22
+
23
+ Install via pip:
24
+
25
+ ```bash
26
+ pip install rdf4j-python
27
+ ```
28
+
29
+ ## 🧪 Usage (Async)
30
+
31
+ Here's a basic example of how to use `rdf4j-python` to create an in-memory sail repository
32
+
33
+ ```python
34
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
35
+ repo_config = (
36
+ RepositoryConfig.Builder()
37
+ .repo_id("example-repo")
38
+ .title("Example Repository")
39
+ .sail_repository_impl(
40
+ MemoryStoreConfig.Builder().persist(False).build(),
41
+ )
42
+ .build()
43
+ )
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"),
49
+ )
50
+ await repo.get_statements(subject=IRI("http://example.com/subject"))
51
+ ```
52
+
53
+ For more detailed examples, refer to the [examples](https://github.com/odysa/rdf4j-python/tree/main/examples) directory.
54
+
55
+ ## 🤝 Contributing
56
+
57
+ We welcome contributions and feedback! If you'd like to help improve this project:
58
+
59
+ - Fork the repo and submit a pull request
60
+ - Open an issue for bugs, feature ideas, or discussions
61
+ - ⭐ Star the repo if you find it useful!
62
+
63
+ ## 📄 License
64
+
65
+ This project is licensed under the MIT License. See the [LICENSE](https://github.com/odysa/rdf4j-python/blob/main/LICENSE) file for details.
@@ -1,6 +1,7 @@
1
1
  [project]
2
2
  name = "rdf4j-python"
3
- version = "0.1.0"
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,9 @@
1
+ from ._async_named_graph import AsyncNamedGraph
2
+ from ._async_rdf4j_db import AsyncRdf4j
3
+ from ._async_repository import AsyncRdf4JRepository
4
+
5
+ __all__ = [
6
+ "AsyncRdf4j",
7
+ "AsyncRdf4JRepository",
8
+ "AsyncNamedGraph",
9
+ ]
@@ -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
+ )
@@ -0,0 +1,129 @@
1
+ import httpx
2
+ import rdflib
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
+ result = rdflib.query.Result.parse(
70
+ response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
71
+ )
72
+ return [
73
+ RepositoryMetadata.from_rdflib_binding(binding)
74
+ for binding in result.bindings
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
+ )