rdf4j-python 0.1.0__tar.gz → 0.1.1a0__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 (32) hide show
  1. rdf4j_python-0.1.1a0/LICENSE +28 -0
  2. rdf4j_python-0.1.1a0/PKG-INFO +77 -0
  3. rdf4j_python-0.1.1a0/README.md +66 -0
  4. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/pyproject.toml +1 -1
  5. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python/__init__.py +3 -3
  6. rdf4j_python-0.1.1a0/rdf4j_python/_driver/__init__.py +7 -0
  7. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python/_driver/_async_rdf4j_db.py +10 -8
  8. rdf4j_python-0.1.1a0/rdf4j_python/_driver/_async_repository.py +109 -0
  9. rdf4j_python-0.1.1a0/rdf4j_python/exception/repo_exception.py +13 -0
  10. rdf4j_python-0.1.1a0/rdf4j_python/model/__init__.py +16 -0
  11. rdf4j_python-0.1.1a0/rdf4j_python/model/_namespace.py +56 -0
  12. rdf4j_python-0.1.1a0/rdf4j_python/model/_repository_config.py +899 -0
  13. rdf4j_python-0.1.0/rdf4j_python/model/repository.py → rdf4j_python-0.1.1a0/rdf4j_python/model/_repository_info.py +3 -3
  14. rdf4j_python-0.1.1a0/rdf4j_python.egg-info/PKG-INFO +77 -0
  15. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python.egg-info/SOURCES.txt +7 -2
  16. rdf4j_python-0.1.0/tests/test_repository.py → rdf4j_python-0.1.1a0/tests/test_client.py +17 -4
  17. rdf4j_python-0.1.1a0/tests/test_rdf4j_repo.py +124 -0
  18. rdf4j_python-0.1.0/PKG-INFO +0 -8
  19. rdf4j_python-0.1.0/README.md +0 -0
  20. rdf4j_python-0.1.0/rdf4j_python/_driver/__init__.py +0 -7
  21. rdf4j_python-0.1.0/rdf4j_python/_driver/_async_repository.py +0 -51
  22. rdf4j_python-0.1.0/rdf4j_python/exception/repo_exception.py +0 -4
  23. rdf4j_python-0.1.0/rdf4j_python.egg-info/PKG-INFO +0 -8
  24. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python/_client/__init__.py +0 -0
  25. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python/_client/_client.py +0 -0
  26. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python/model/_base_model.py +0 -0
  27. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python/utils/__init__.py +0 -0
  28. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python/utils/const.py +0 -0
  29. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python.egg-info/dependency_links.txt +0 -0
  30. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python.egg-info/requires.txt +0 -0
  31. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/rdf4j_python.egg-info/top_level.txt +0 -0
  32. {rdf4j_python-0.1.0 → rdf4j_python-0.1.1a0}/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.1a0
4
+ Summary: The Python client for RDF4J
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: httpx>=0.28.1
9
+ Requires-Dist: rdflib>=7.1.4
10
+ Dynamic: license-file
11
+
12
+ # 🐍 rdf4j-python
13
+
14
+ **A Pythonic interface to the powerful Java-based [Eclipse RDF4J](https://rdf4j.org/) framework.**
15
+
16
+ > ⚠️ **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!
17
+
18
+ ✅ **Supports both asynchronous (`async/await`) and synchronous programming styles.**
19
+
20
+ ## 🌐 Overview
21
+
22
+ `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.
23
+
24
+ ## 🚀 Features
25
+
26
+ - **Seamless Integration**: Interact with RDF4J repositories using Pythonic constructs.
27
+ - **SPARQL Support**: Execute SPARQL queries and updates effortlessly.
28
+ - **Repository Management**: Create, access, and manage RDF4J repositories programmatically.
29
+ - **Data Handling**: Add and retrieve RDF triples with ease.
30
+ - **Format Flexibility**: Support for various RDF serialization formats.
31
+
32
+ ## 📦 Installation
33
+
34
+ Install via pip:
35
+
36
+ ```bash
37
+ pip install rdf4j-python
38
+ ```
39
+
40
+ ## 🧪 Usage (Async)
41
+
42
+ Here's a basic example of how to use `rdf4j-python` to create an in-memory sail repository
43
+
44
+ ```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
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
50
+ repo_config = (
51
+ RepositoryConfig.builder_with_sail_repository(
52
+ MemoryStoreConfig.Builder().persist(False).build(),
53
+ )
54
+ .repo_id("example-repo")
55
+ .title("Example Repository")
56
+ .build()
57
+ )
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,
62
+ )
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,66 @@
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
+ from rdf4j_python import AsyncRdf4j
35
+ from rdf4j_python.model import MemoryStoreConfig, RepositoryConfig
36
+ from rdf4j_python.utils.const import Rdf4jContentType
37
+
38
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
39
+ repo_config = (
40
+ RepositoryConfig.builder_with_sail_repository(
41
+ MemoryStoreConfig.Builder().persist(False).build(),
42
+ )
43
+ .repo_id("example-repo")
44
+ .title("Example Repository")
45
+ .build()
46
+ )
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,
51
+ )
52
+ ```
53
+
54
+ For more detailed examples, refer to the [examples](https://github.com/odysa/rdf4j-python/tree/main/examples) directory.
55
+
56
+ ## 🤝 Contributing
57
+
58
+ We welcome contributions and feedback! If you'd like to help improve this project:
59
+
60
+ - Fork the repo and submit a pull request
61
+ - Open an issue for bugs, feature ideas, or discussions
62
+ - ⭐ Star the repo if you find it useful!
63
+
64
+ ## 📄 License
65
+
66
+ 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,6 @@
1
1
  [project]
2
2
  name = "rdf4j-python"
3
- version = "0.1.0"
3
+ version = "0.1.1a"
4
4
  description = "The Python client for RDF4J"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,9 +1,9 @@
1
1
  from ._client import AsyncApiClient, SyncApiClient
2
- from ._driver import AsyncRdf4jDB, AsyncRepository
2
+ from ._driver import AsyncRdf4j, AsyncRdf4JRepository
3
3
 
4
4
  __all__ = [
5
5
  "AsyncApiClient",
6
6
  "SyncApiClient",
7
- "AsyncRdf4jDB",
8
- "AsyncRepository",
7
+ "AsyncRdf4j",
8
+ "AsyncRdf4JRepository",
9
9
  ]
@@ -0,0 +1,7 @@
1
+ from ._async_rdf4j_db import AsyncRdf4j
2
+ from ._async_repository import AsyncRdf4JRepository
3
+
4
+ __all__ = [
5
+ "AsyncRdf4j",
6
+ "AsyncRdf4JRepository",
7
+ ]
@@ -8,13 +8,13 @@ from rdf4j_python.exception.repo_exception import (
8
8
  RepositoryCreationException,
9
9
  RepositoryDeletionException,
10
10
  )
11
- from rdf4j_python.model.repository import RepositoryInfo
11
+ from rdf4j_python.model._repository_info import RepositoryMetadata
12
12
  from rdf4j_python.utils.const import Rdf4jContentType
13
13
 
14
- from ._async_repository import AsyncRepository
14
+ from ._async_repository import AsyncRdf4JRepository
15
15
 
16
16
 
17
- class AsyncRdf4jDB:
17
+ class AsyncRdf4j:
18
18
  _client: AsyncApiClient
19
19
  _base_url: str
20
20
 
@@ -33,7 +33,7 @@ class AsyncRdf4jDB:
33
33
  response.raise_for_status()
34
34
  return response.text
35
35
 
36
- async def list_repositories(self) -> list[RepositoryInfo]:
36
+ async def list_repositories(self) -> list[RepositoryMetadata]:
37
37
  """
38
38
  List all RDF4J repositories.
39
39
 
@@ -48,24 +48,25 @@ class AsyncRdf4jDB:
48
48
  )
49
49
 
50
50
  return [
51
- RepositoryInfo.from_rdflib_binding(binding) for binding in result.bindings
51
+ RepositoryMetadata.from_rdflib_binding(binding)
52
+ for binding in result.bindings
52
53
  ]
53
54
 
54
- async def get_repository(self, repository_id: str) -> AsyncRepository:
55
+ async def get_repository(self, repository_id: str) -> AsyncRdf4JRepository:
55
56
  """
56
57
  Get an AsyncRepository instance for the specified repository ID.
57
58
 
58
59
  :param repository_id: The ID of the repository.
59
60
  :return: An instance of AsyncRepository.
60
61
  """
61
- return AsyncRepository(self._client, repository_id)
62
+ return AsyncRdf4JRepository(self._client, repository_id)
62
63
 
63
64
  async def create_repository(
64
65
  self,
65
66
  repository_id: str,
66
67
  rdf_config_data: str,
67
68
  content_type: Union[Rdf4jContentType, str] = Rdf4jContentType.TURTLE,
68
- ):
69
+ ) -> AsyncRdf4JRepository:
69
70
  """
70
71
  Create a new RDF4J repository.
71
72
 
@@ -86,6 +87,7 @@ class AsyncRdf4jDB:
86
87
  raise RepositoryCreationException(
87
88
  f"Repository creation failed: {response.status_code} - {response.text}"
88
89
  )
90
+ return AsyncRdf4JRepository(self._client, repository_id)
89
91
 
90
92
  async def delete_repository(self, repository_id: str):
91
93
  """
@@ -0,0 +1,109 @@
1
+ import httpx
2
+ import rdflib
3
+
4
+ from rdf4j_python import AsyncApiClient
5
+ from rdf4j_python.exception.repo_exception import (
6
+ NamespaceException,
7
+ RepositoryInternalException,
8
+ RepositoryNotFoundException,
9
+ )
10
+ from rdf4j_python.model._namespace import Namespace
11
+ from rdf4j_python.utils.const import Rdf4jContentType
12
+
13
+
14
+ class AsyncRdf4JRepository:
15
+ def __init__(self, client: AsyncApiClient, repository_id: str):
16
+ self._client = client
17
+ self._repository_id = repository_id
18
+
19
+ async def query(
20
+ self,
21
+ sparql_query: str,
22
+ infer: bool = True,
23
+ accept: Rdf4jContentType = Rdf4jContentType.SPARQL_RESULTS_JSON,
24
+ ):
25
+ path = f"/repositories/{self._repository_id}"
26
+ params = {"query": sparql_query, "infer": str(infer).lower()}
27
+ headers = {"Accept": accept.value}
28
+ response = await self._client.get(path, params=params, headers=headers)
29
+ self._handle_repo_not_found_exception(response)
30
+ if "json" in response.headers.get("Content-Type", ""):
31
+ return response.json()
32
+ return response.text
33
+
34
+ async def update(self, sparql_update: str):
35
+ path = f"/repositories/{self._repository_id}/statements"
36
+ headers = {"Content-Type": Rdf4jContentType.SPARQL_UPDATE.value}
37
+ response = await self._client.post(path, data=sparql_update, headers=headers)
38
+ self._handle_repo_not_found_exception(response)
39
+ response.raise_for_status()
40
+
41
+ async def replace_statements(
42
+ self, rdf_data: str, content_type: Rdf4jContentType = Rdf4jContentType.TURTLE
43
+ ):
44
+ path = f"/repositories/{self._repository_id}/statements"
45
+ headers = {"Content-Type": content_type.value}
46
+ response = await self._client.put(path, data=rdf_data, headers=headers)
47
+ self._handle_repo_not_found_exception(response)
48
+ response.raise_for_status()
49
+
50
+ async def get_namespaces(self):
51
+ path = f"/repositories/{self._repository_id}/namespaces"
52
+ headers = {"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON}
53
+ response = await self._client.get(path, headers=headers)
54
+
55
+ result = rdflib.query.Result.parse(
56
+ response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
57
+ )
58
+ self._handle_repo_not_found_exception(response)
59
+ return [Namespace.from_rdflib_binding(binding) for binding in result.bindings]
60
+
61
+ async def set_namespace(self, prefix: str, namespace: str):
62
+ path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
63
+ headers = {"Content-Type": Rdf4jContentType.NTRIPLES.value}
64
+ response = await self._client.put(path, content=namespace, headers=headers)
65
+ self._handle_repo_not_found_exception(response)
66
+ if response.status_code != httpx.codes.NO_CONTENT:
67
+ raise NamespaceException(f"Failed to set namespace: {response.text}")
68
+
69
+ async def get_namespace(self, prefix: str) -> Namespace:
70
+ path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
71
+ headers = {"Accept": Rdf4jContentType.NTRIPLES.value}
72
+ response = await self._client.get(path, headers=headers)
73
+ self._handle_repo_not_found_exception(response)
74
+
75
+ if response.status_code != httpx.codes.OK:
76
+ raise NamespaceException(f"Failed to get namespace: {response.text}")
77
+
78
+ return Namespace(prefix, response.text)
79
+
80
+ async def delete_namespace(self, prefix: str):
81
+ path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
82
+ response = await self._client.delete(path)
83
+ self._handle_repo_not_found_exception(response)
84
+ response.raise_for_status()
85
+
86
+ async def size(self) -> int:
87
+ path = f"/repositories/{self._repository_id}/size"
88
+ response = await self._client.get(path)
89
+ self._handle_repo_not_found_exception(response)
90
+
91
+ if response.status_code != httpx.codes.OK:
92
+ raise RepositoryInternalException(f"Failed to get size: {response.text}")
93
+
94
+ return int(response.text.strip())
95
+
96
+ async def add_statement(self, subject: str, predicate: str, object: str):
97
+ path = f"/repositories/{self._repository_id}/statements"
98
+ headers = {"Content-Type": Rdf4jContentType.NTRIPLES.value}
99
+ response = await self._client.post(
100
+ path, data=f"{subject} {predicate} {object}.", headers=headers
101
+ )
102
+ self._handle_repo_not_found_exception(response)
103
+ response.raise_for_status()
104
+
105
+ def _handle_repo_not_found_exception(self, response: httpx.Response):
106
+ if response.status_code == httpx.codes.NOT_FOUND:
107
+ raise RepositoryNotFoundException(
108
+ f"Repository {self._repository_id} not found"
109
+ )
@@ -0,0 +1,13 @@
1
+ class RepositoryCreationException(Exception): ...
2
+
3
+
4
+ class RepositoryDeletionException(Exception): ...
5
+
6
+
7
+ class NamespaceException(Exception): ...
8
+
9
+
10
+ class RepositoryNotFoundException(Exception): ...
11
+
12
+
13
+ class RepositoryInternalException(Exception): ...
@@ -0,0 +1,16 @@
1
+ from ._namespace import IRI, Namespace
2
+ from ._repository_config import (
3
+ MemoryStoreConfig,
4
+ NativeStoreConfig,
5
+ RepositoryConfig,
6
+ )
7
+ from ._repository_info import RepositoryMetadata
8
+
9
+ __all__ = [
10
+ "IRI",
11
+ "Namespace",
12
+ "RepositoryConfig",
13
+ "MemoryStoreConfig",
14
+ "NativeStoreConfig",
15
+ "RepositoryMetadata",
16
+ ]
@@ -0,0 +1,56 @@
1
+ from typing import Mapping
2
+
3
+ from rdflib import URIRef
4
+ from rdflib.namespace import Namespace as RdflibNamespace
5
+ from rdflib.term import Identifier, Variable
6
+
7
+ from ._base_model import _BaseModel
8
+
9
+
10
+ class IRI(URIRef): ...
11
+
12
+
13
+ class Namespace:
14
+ _prefix: str
15
+ _namespace: RdflibNamespace
16
+
17
+ def __init__(self, prefix: str, namespace: str):
18
+ self._prefix = prefix
19
+ self._namespace = RdflibNamespace(namespace)
20
+
21
+ @classmethod
22
+ def from_rdflib_binding(cls, binding: Mapping[Variable, Identifier]) -> "Namespace":
23
+ prefix = _BaseModel.get_literal(binding, "prefix", "")
24
+ namespace = _BaseModel.get_literal(binding, "namespace", "")
25
+ return cls(
26
+ prefix=prefix,
27
+ namespace=namespace,
28
+ )
29
+
30
+ def __str__(self):
31
+ return f"{self._prefix}: {self._namespace}"
32
+
33
+ def __repr__(self):
34
+ return f"Namespace(prefix={self._prefix}, namespace={self._namespace})"
35
+
36
+ def __contains__(self, item: str) -> bool:
37
+ return item in self._namespace
38
+
39
+ def term(self, name: str) -> IRI:
40
+ return IRI(self._namespace.term(name))
41
+
42
+ def __getitem__(self, item: str) -> IRI:
43
+ return self.term(item)
44
+
45
+ def __getattr__(self, item: str) -> IRI:
46
+ if item.startswith("__"):
47
+ raise AttributeError
48
+ return self.term(item)
49
+
50
+ @property
51
+ def namespace(self) -> IRI:
52
+ return IRI(self._namespace)
53
+
54
+ @property
55
+ def prefix(self) -> str:
56
+ return self._prefix