rdf4j-python 0.1.1a0__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rdf4j_python/__init__.py CHANGED
@@ -1,9 +1,14 @@
1
- from ._client import AsyncApiClient, SyncApiClient
2
- from ._driver import AsyncRdf4j, AsyncRdf4JRepository
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
3
9
 
4
10
  __all__ = [
5
- "AsyncApiClient",
6
- "SyncApiClient",
7
11
  "AsyncRdf4j",
8
12
  "AsyncRdf4JRepository",
13
+ "AsyncNamedGraph",
9
14
  ]
@@ -4,20 +4,58 @@ import httpx
4
4
 
5
5
 
6
6
  class BaseClient:
7
+ """Base HTTP client that provides shared URL building functionality."""
8
+
7
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
+ """
8
17
  self.base_url = base_url.rstrip("/")
9
18
  self.timeout = timeout
10
19
 
11
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
+ """
12
30
  return f"{self.base_url}/{path.lstrip('/')}"
13
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
+
14
41
 
15
42
  class SyncApiClient(BaseClient):
43
+ """Synchronous API client using httpx.Client."""
44
+
16
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
+ """
17
52
  self.client = httpx.Client(timeout=self.timeout).__enter__()
18
53
  return self
19
54
 
20
55
  def __exit__(self, exc_type, exc_value, traceback):
56
+ """
57
+ Exits the context and closes the HTTP client.
58
+ """
21
59
  self.client.__exit__(exc_type, exc_value, traceback)
22
60
 
23
61
  def get(
@@ -26,6 +64,17 @@ class SyncApiClient(BaseClient):
26
64
  params: Optional[Dict[str, Any]] = None,
27
65
  headers: Optional[Dict[str, str]] = None,
28
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
+ """
29
78
  return self.client.get(self._build_url(path), params=params, headers=headers)
30
79
 
31
80
  def post(
@@ -35,6 +84,18 @@ class SyncApiClient(BaseClient):
35
84
  json: Optional[Any] = None,
36
85
  headers: Optional[Dict[str, str]] = None,
37
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
+ """
38
99
  return self.client.post(
39
100
  self._build_url(path), data=data, json=json, headers=headers
40
101
  )
@@ -46,6 +107,18 @@ class SyncApiClient(BaseClient):
46
107
  json: Optional[Any] = None,
47
108
  headers: Optional[Dict[str, str]] = None,
48
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
+ """
49
122
  return self.client.put(
50
123
  self._build_url(path), data=data, json=json, headers=headers
51
124
  )
@@ -53,15 +126,36 @@ class SyncApiClient(BaseClient):
53
126
  def delete(
54
127
  self, path: str, headers: Optional[Dict[str, str]] = None
55
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
+ """
56
139
  return self.client.delete(self._build_url(path), headers=headers)
57
140
 
58
141
 
59
142
  class AsyncApiClient(BaseClient):
143
+ """Asynchronous API client using httpx.AsyncClient."""
144
+
60
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
+ """
61
152
  self.client = await httpx.AsyncClient(timeout=self.timeout).__aenter__()
62
153
  return self
63
154
 
64
155
  async def __aexit__(self, exc_type, exc_value, traceback):
156
+ """
157
+ Exits the async context and closes the HTTP client.
158
+ """
65
159
  await self.client.__aexit__(exc_type, exc_value, traceback)
66
160
 
67
161
  async def get(
@@ -70,6 +164,17 @@ class AsyncApiClient(BaseClient):
70
164
  params: Optional[Dict[str, Any]] = None,
71
165
  headers: Optional[Dict[str, str]] = None,
72
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
+ """
73
178
  return await self.client.get(
74
179
  self._build_url(path), params=params, headers=headers
75
180
  )
@@ -77,26 +182,72 @@ class AsyncApiClient(BaseClient):
77
182
  async def post(
78
183
  self,
79
184
  path: str,
80
- data: Optional[Dict[str, Any]] = None,
185
+ content: Optional[str] = None,
81
186
  json: Optional[Any] = None,
82
187
  headers: Optional[Dict[str, str]] = None,
83
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
+ """
84
201
  return await self.client.post(
85
- self._build_url(path), data=data, json=json, headers=headers
202
+ self._build_url(path), content=content, json=json, headers=headers
86
203
  )
87
204
 
88
205
  async def put(
89
206
  self,
90
207
  path: str,
91
208
  content: Optional[bytes] = None,
209
+ params: Optional[Dict[str, Any]] = None,
92
210
  json: Optional[Any] = None,
93
211
  headers: Optional[Dict[str, str]] = None,
94
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
+ """
95
226
  return await self.client.put(
96
- self._build_url(path), content=content, json=json, headers=headers
227
+ self._build_url(path),
228
+ content=content,
229
+ json=json,
230
+ headers=headers,
231
+ params=params,
97
232
  )
98
233
 
99
234
  async def delete(
100
- self, path: str, headers: Optional[Dict[str, str]] = None
235
+ self,
236
+ path: str,
237
+ params: Optional[Dict[str, Any]] = None,
238
+ headers: Optional[Dict[str, str]] = None,
101
239
  ) -> httpx.Response:
102
- return await self.client.delete(self._build_url(path), headers=headers)
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
+ )
@@ -1,99 +1,125 @@
1
- from typing import Union
2
-
3
1
  import httpx
4
- import rdflib
2
+ import pyoxigraph as og
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
- List all RDF4J repositories.
57
+ """Lists all available RDF4J repositories.
58
+
59
+ Returns:
60
+ list[RepositoryMetadata]: A list of repository metadata objects.
39
61
 
40
- :return: List of repository information.
62
+ Raises:
63
+ httpx.HTTPStatusError: If the request fails.
41
64
  """
42
65
  response = await self._client.get(
43
66
  "/repositories",
44
67
  headers={"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON},
45
68
  )
46
- result = rdflib.query.Result.parse(
47
- response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
69
+ query_solutions = og.parse_query_results(
70
+ response.text, format=og.QueryResultsFormat.JSON
48
71
  )
49
-
50
72
  return [
51
- RepositoryMetadata.from_rdflib_binding(binding)
52
- for binding in result.bindings
73
+ RepositoryMetadata.from_sparql_query_solution(query_solution)
74
+ for query_solution in query_solutions
53
75
  ]
54
76
 
55
77
  async def get_repository(self, repository_id: str) -> AsyncRdf4JRepository:
56
- """
57
- Get an AsyncRepository instance for the specified repository ID.
78
+ """Gets an interface to a specific RDF4J repository.
79
+
80
+ Args:
81
+ repository_id (str): The ID of the repository.
58
82
 
59
- :param repository_id: The ID of the repository.
60
- :return: An instance of AsyncRepository.
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
- repository_id: str,
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
- :param repository_id: Repository ID to create.
74
- :param rdf_config_data: RDF config in Turtle, RDF/XML, etc.
75
- :param content_type: MIME type of RDF config.
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
- if isinstance(content_type, Rdf4jContentType):
80
- content_type = content_type.value
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=rdf_config_data, headers=headers
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, repository_id)
113
+ return AsyncRdf4JRepository(self._client, config.repo_id)
91
114
 
92
115
  async def delete_repository(self, repository_id: str):
93
- """
94
- Delete an RDF4J repository and its data/config.
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
- :param repository_id: The repository ID to delete.
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)