rdf4j-python 0.1.3__tar.gz → 0.1.5__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 (38) hide show
  1. rdf4j_python-0.1.5/PKG-INFO +259 -0
  2. rdf4j_python-0.1.5/README.md +245 -0
  3. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/pyproject.toml +5 -1
  4. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/_client/_client.py +24 -2
  5. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/_driver/_async_rdf4j_db.py +1 -0
  6. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/_driver/_async_repository.py +44 -12
  7. rdf4j_python-0.1.5/rdf4j_python/model/repository_config.py +587 -0
  8. rdf4j_python-0.1.5/rdf4j_python.egg-info/PKG-INFO +259 -0
  9. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python.egg-info/SOURCES.txt +1 -0
  10. rdf4j_python-0.1.5/rdf4j_python.egg-info/requires.txt +5 -0
  11. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/tests/test_client.py +9 -9
  12. rdf4j_python-0.1.5/tests/test_client_initialization.py +89 -0
  13. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/tests/test_rdf4j_repository.py +144 -10
  14. rdf4j_python-0.1.3/PKG-INFO +0 -77
  15. rdf4j_python-0.1.3/README.md +0 -65
  16. rdf4j_python-0.1.3/rdf4j_python/model/repository_config.py +0 -1500
  17. rdf4j_python-0.1.3/rdf4j_python.egg-info/PKG-INFO +0 -77
  18. rdf4j_python-0.1.3/rdf4j_python.egg-info/requires.txt +0 -2
  19. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/LICENSE +0 -0
  20. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/__init__.py +0 -0
  21. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/_client/__init__.py +0 -0
  22. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/_driver/__init__.py +0 -0
  23. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/_driver/_async_named_graph.py +0 -0
  24. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/exception/__init__.py +0 -0
  25. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/exception/repo_exception.py +0 -0
  26. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/model/__init__.py +0 -0
  27. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/model/_namespace.py +0 -0
  28. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/model/_repository_info.py +0 -0
  29. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/model/term.py +0 -0
  30. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/model/vocabulary.py +0 -0
  31. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/utils/__init__.py +0 -0
  32. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/utils/const.py +0 -0
  33. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python/utils/helpers.py +0 -0
  34. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python.egg-info/dependency_links.txt +0 -0
  35. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/rdf4j_python.egg-info/top_level.txt +0 -0
  36. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/setup.cfg +0 -0
  37. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/tests/test_async_named_graph.py +0 -0
  38. {rdf4j_python-0.1.3 → rdf4j_python-0.1.5}/tests/test_helpers.py +0 -0
@@ -0,0 +1,259 @@
1
+ Metadata-Version: 2.4
2
+ Name: rdf4j-python
3
+ Version: 0.1.5
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: pyoxigraph>=0.4.10
11
+ Provides-Extra: sparqlwrapper
12
+ Requires-Dist: sparqlwrapper>=2.0.0; extra == "sparqlwrapper"
13
+ Dynamic: license-file
14
+
15
+ # rdf4j-python
16
+
17
+ **A modern Python client for the Eclipse RDF4J framework, enabling seamless RDF data management and SPARQL operations from Python applications.**
18
+
19
+ rdf4j-python bridges the gap between Python and the robust [Eclipse RDF4J](https://rdf4j.org/) ecosystem, providing a clean, async-first API for managing RDF repositories, executing SPARQL queries, and handling semantic data with ease.
20
+
21
+ > ⚠️ **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!
22
+
23
+ ## Features
24
+
25
+ - **🚀 Async-First Design**: Native support for async/await with synchronous fallback
26
+ - **🔄 Repository Management**: Create, access, and manage RDF4J repositories programmatically
27
+ - **⚡ SPARQL Support**: Execute SELECT, ASK, CONSTRUCT, and UPDATE queries effortlessly
28
+ - **📊 Flexible Data Handling**: Add, retrieve, and manipulate RDF triples and quads
29
+ - **🎯 Multiple Formats**: Support for various RDF serialization formats (Turtle, N-Triples, JSON-LD, etc.)
30
+ - **🛠️ Repository Types**: Memory stores, native stores, HTTP repositories, and more
31
+ - **🔗 Named Graph Support**: Work with multiple graphs within repositories
32
+ - **⚙️ Inferencing**: Built-in support for RDFS and custom inferencing rules
33
+
34
+ ## Installation
35
+
36
+ ### Prerequisites
37
+
38
+ - Python 3.10 or higher
39
+ - RDF4J Server (for remote repositories) or embedded usage
40
+
41
+ ### Install from PyPI
42
+
43
+ ```bash
44
+ pip install rdf4j-python
45
+ ```
46
+
47
+ ### Install with Optional Dependencies
48
+
49
+ ```bash
50
+ # Include SPARQLWrapper integration
51
+ pip install rdf4j-python[sparqlwrapper]
52
+ ```
53
+
54
+ ### Development Installation
55
+
56
+ ```bash
57
+ git clone https://github.com/odysa/rdf4j-python.git
58
+ cd rdf4j-python
59
+ uv sync --group dev
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ ### Quick Start
65
+
66
+ ```python
67
+ import asyncio
68
+ from rdf4j_python import AsyncRdf4j
69
+ from rdf4j_python.model.repository_config import RepositoryConfig, MemoryStoreConfig, SailRepositoryConfig
70
+ from rdf4j_python.model.term import IRI, Literal
71
+
72
+ async def main():
73
+ # Connect to RDF4J server
74
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
75
+ # Create an in-memory repository
76
+ config = RepositoryConfig(
77
+ repo_id="my-repo",
78
+ title="My Repository",
79
+ impl=SailRepositoryConfig(sail_impl=MemoryStoreConfig(persist=False))
80
+ )
81
+ repo = await db.create_repository(config=config)
82
+
83
+ # Add some data
84
+ await repo.add_statement(
85
+ IRI("http://example.com/person/alice"),
86
+ IRI("http://xmlns.com/foaf/0.1/name"),
87
+ Literal("Alice")
88
+ )
89
+
90
+ # Query the data
91
+ results = await repo.query("SELECT * WHERE { ?s ?p ?o }")
92
+ for result in results:
93
+ print(f"Subject: {result['s']}, Predicate: {result['p']}, Object: {result['o']}")
94
+
95
+ if __name__ == "__main__":
96
+ asyncio.run(main())
97
+ ```
98
+
99
+ ### Working with Multiple Graphs
100
+
101
+ ```python
102
+ from rdf4j_python.model.term import Quad
103
+
104
+ async def multi_graph_example():
105
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
106
+ repo = await db.get_repository("my-repo")
107
+
108
+ # Add data to specific graphs
109
+ statements = [
110
+ Quad(
111
+ IRI("http://example.com/person/bob"),
112
+ IRI("http://xmlns.com/foaf/0.1/name"),
113
+ Literal("Bob"),
114
+ IRI("http://example.com/graph/people")
115
+ ),
116
+ Quad(
117
+ IRI("http://example.com/person/bob"),
118
+ IRI("http://xmlns.com/foaf/0.1/age"),
119
+ Literal("30", datatype=IRI("http://www.w3.org/2001/XMLSchema#integer")),
120
+ IRI("http://example.com/graph/demographics")
121
+ )
122
+ ]
123
+ await repo.add_statements(statements)
124
+
125
+ # Query specific graph
126
+ graph_query = """
127
+ SELECT * WHERE {
128
+ GRAPH <http://example.com/graph/people> {
129
+ ?person ?property ?value
130
+ }
131
+ }
132
+ """
133
+ results = await repo.query(graph_query)
134
+ ```
135
+
136
+ ### Advanced Repository Configuration
137
+
138
+ Here's a more comprehensive example showing repository creation with different configurations:
139
+
140
+ ```python
141
+ async def advanced_example():
142
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
143
+ # Memory store with persistence
144
+ persistent_config = RepositoryConfig(
145
+ repo_id="persistent-repo",
146
+ title="Persistent Memory Store",
147
+ impl=SailRepositoryConfig(sail_impl=MemoryStoreConfig(persist=True))
148
+ )
149
+
150
+ # Create and populate repository
151
+ repo = await db.create_repository(config=persistent_config)
152
+
153
+ # Bulk data operations
154
+ data = [
155
+ (IRI("http://example.com/alice"), IRI("http://xmlns.com/foaf/0.1/name"), Literal("Alice")),
156
+ (IRI("http://example.com/alice"), IRI("http://xmlns.com/foaf/0.1/email"), Literal("alice@example.com")),
157
+ (IRI("http://example.com/bob"), IRI("http://xmlns.com/foaf/0.1/name"), Literal("Bob")),
158
+ ]
159
+
160
+ statements = [
161
+ Quad(subj, pred, obj, IRI("http://example.com/default"))
162
+ for subj, pred, obj in data
163
+ ]
164
+ await repo.add_statements(statements)
165
+
166
+ # Complex SPARQL query
167
+ query = """
168
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
169
+ SELECT ?name ?email WHERE {
170
+ ?person foaf:name ?name .
171
+ OPTIONAL { ?person foaf:email ?email }
172
+ }
173
+ ORDER BY ?name
174
+ """
175
+ results = await repo.query(query)
176
+ ```
177
+
178
+ For more detailed examples, see the [examples](examples/) directory.
179
+
180
+ ## Development
181
+
182
+ ### Setting up Development Environment
183
+
184
+ 1. **Clone the repository**:
185
+ ```bash
186
+ git clone https://github.com/odysa/rdf4j-python.git
187
+ cd rdf4j-python
188
+ ```
189
+
190
+ 2. **Install development dependencies**:
191
+ ```bash
192
+ uv sync --group dev
193
+ ```
194
+
195
+ 3. **Start RDF4J Server** (for integration tests):
196
+ ```bash
197
+ # Using Docker
198
+ docker run -p 19780:8080 eclipse/rdf4j:latest
199
+ ```
200
+
201
+ 4. **Run tests**:
202
+ ```bash
203
+ pytest tests/
204
+ ```
205
+
206
+ 5. **Run linting**:
207
+ ```bash
208
+ ruff check .
209
+ ruff format .
210
+ ```
211
+
212
+ ### Project Structure
213
+
214
+ ```
215
+ rdf4j_python/
216
+ ├── _driver/ # Core async driver implementation
217
+ ├── model/ # Data models and configurations
218
+ ├── exception/ # Custom exceptions
219
+ └── utils/ # Utility functions
220
+
221
+ examples/ # Usage examples
222
+ tests/ # Test suite
223
+ docs/ # Documentation
224
+ ```
225
+
226
+ ### Contributing
227
+
228
+ We welcome contributions! Here's how to get involved:
229
+
230
+ 1. **Fork** the repository on GitHub
231
+ 2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
232
+ 3. **Make** your changes and add tests
233
+ 4. **Run** the test suite to ensure everything works
234
+ 5. **Commit** your changes (`git commit -m 'Add amazing feature'`)
235
+ 6. **Push** to your branch (`git push origin feature/amazing-feature`)
236
+ 7. **Open** a Pull Request
237
+
238
+ ### Running Examples
239
+
240
+ ```bash
241
+ # Make sure RDF4J server is running on localhost:19780
242
+ python examples/complete_workflow.py
243
+ python examples/query.py
244
+ ```
245
+
246
+ ## License
247
+
248
+ This project is licensed under the **BSD 3-Clause License**. See the [LICENSE](LICENSE) file for the full license text.
249
+
250
+ ```
251
+ Copyright (c) 2025, Chengxu Bian
252
+ All rights reserved.
253
+ ```
254
+
255
+ ---
256
+
257
+ **Questions or Issues?** Please feel free to [open an issue](https://github.com/odysa/rdf4j-python/issues) on GitHub.
258
+
259
+ **⭐ Star this repo** if you find it useful!
@@ -0,0 +1,245 @@
1
+ # rdf4j-python
2
+
3
+ **A modern Python client for the Eclipse RDF4J framework, enabling seamless RDF data management and SPARQL operations from Python applications.**
4
+
5
+ rdf4j-python bridges the gap between Python and the robust [Eclipse RDF4J](https://rdf4j.org/) ecosystem, providing a clean, async-first API for managing RDF repositories, executing SPARQL queries, and handling semantic data with ease.
6
+
7
+ > ⚠️ **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!
8
+
9
+ ## Features
10
+
11
+ - **🚀 Async-First Design**: Native support for async/await with synchronous fallback
12
+ - **🔄 Repository Management**: Create, access, and manage RDF4J repositories programmatically
13
+ - **⚡ SPARQL Support**: Execute SELECT, ASK, CONSTRUCT, and UPDATE queries effortlessly
14
+ - **📊 Flexible Data Handling**: Add, retrieve, and manipulate RDF triples and quads
15
+ - **🎯 Multiple Formats**: Support for various RDF serialization formats (Turtle, N-Triples, JSON-LD, etc.)
16
+ - **🛠️ Repository Types**: Memory stores, native stores, HTTP repositories, and more
17
+ - **🔗 Named Graph Support**: Work with multiple graphs within repositories
18
+ - **⚙️ Inferencing**: Built-in support for RDFS and custom inferencing rules
19
+
20
+ ## Installation
21
+
22
+ ### Prerequisites
23
+
24
+ - Python 3.10 or higher
25
+ - RDF4J Server (for remote repositories) or embedded usage
26
+
27
+ ### Install from PyPI
28
+
29
+ ```bash
30
+ pip install rdf4j-python
31
+ ```
32
+
33
+ ### Install with Optional Dependencies
34
+
35
+ ```bash
36
+ # Include SPARQLWrapper integration
37
+ pip install rdf4j-python[sparqlwrapper]
38
+ ```
39
+
40
+ ### Development Installation
41
+
42
+ ```bash
43
+ git clone https://github.com/odysa/rdf4j-python.git
44
+ cd rdf4j-python
45
+ uv sync --group dev
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Quick Start
51
+
52
+ ```python
53
+ import asyncio
54
+ from rdf4j_python import AsyncRdf4j
55
+ from rdf4j_python.model.repository_config import RepositoryConfig, MemoryStoreConfig, SailRepositoryConfig
56
+ from rdf4j_python.model.term import IRI, Literal
57
+
58
+ async def main():
59
+ # Connect to RDF4J server
60
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
61
+ # Create an in-memory repository
62
+ config = RepositoryConfig(
63
+ repo_id="my-repo",
64
+ title="My Repository",
65
+ impl=SailRepositoryConfig(sail_impl=MemoryStoreConfig(persist=False))
66
+ )
67
+ repo = await db.create_repository(config=config)
68
+
69
+ # Add some data
70
+ await repo.add_statement(
71
+ IRI("http://example.com/person/alice"),
72
+ IRI("http://xmlns.com/foaf/0.1/name"),
73
+ Literal("Alice")
74
+ )
75
+
76
+ # Query the data
77
+ results = await repo.query("SELECT * WHERE { ?s ?p ?o }")
78
+ for result in results:
79
+ print(f"Subject: {result['s']}, Predicate: {result['p']}, Object: {result['o']}")
80
+
81
+ if __name__ == "__main__":
82
+ asyncio.run(main())
83
+ ```
84
+
85
+ ### Working with Multiple Graphs
86
+
87
+ ```python
88
+ from rdf4j_python.model.term import Quad
89
+
90
+ async def multi_graph_example():
91
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
92
+ repo = await db.get_repository("my-repo")
93
+
94
+ # Add data to specific graphs
95
+ statements = [
96
+ Quad(
97
+ IRI("http://example.com/person/bob"),
98
+ IRI("http://xmlns.com/foaf/0.1/name"),
99
+ Literal("Bob"),
100
+ IRI("http://example.com/graph/people")
101
+ ),
102
+ Quad(
103
+ IRI("http://example.com/person/bob"),
104
+ IRI("http://xmlns.com/foaf/0.1/age"),
105
+ Literal("30", datatype=IRI("http://www.w3.org/2001/XMLSchema#integer")),
106
+ IRI("http://example.com/graph/demographics")
107
+ )
108
+ ]
109
+ await repo.add_statements(statements)
110
+
111
+ # Query specific graph
112
+ graph_query = """
113
+ SELECT * WHERE {
114
+ GRAPH <http://example.com/graph/people> {
115
+ ?person ?property ?value
116
+ }
117
+ }
118
+ """
119
+ results = await repo.query(graph_query)
120
+ ```
121
+
122
+ ### Advanced Repository Configuration
123
+
124
+ Here's a more comprehensive example showing repository creation with different configurations:
125
+
126
+ ```python
127
+ async def advanced_example():
128
+ async with AsyncRdf4j("http://localhost:19780/rdf4j-server") as db:
129
+ # Memory store with persistence
130
+ persistent_config = RepositoryConfig(
131
+ repo_id="persistent-repo",
132
+ title="Persistent Memory Store",
133
+ impl=SailRepositoryConfig(sail_impl=MemoryStoreConfig(persist=True))
134
+ )
135
+
136
+ # Create and populate repository
137
+ repo = await db.create_repository(config=persistent_config)
138
+
139
+ # Bulk data operations
140
+ data = [
141
+ (IRI("http://example.com/alice"), IRI("http://xmlns.com/foaf/0.1/name"), Literal("Alice")),
142
+ (IRI("http://example.com/alice"), IRI("http://xmlns.com/foaf/0.1/email"), Literal("alice@example.com")),
143
+ (IRI("http://example.com/bob"), IRI("http://xmlns.com/foaf/0.1/name"), Literal("Bob")),
144
+ ]
145
+
146
+ statements = [
147
+ Quad(subj, pred, obj, IRI("http://example.com/default"))
148
+ for subj, pred, obj in data
149
+ ]
150
+ await repo.add_statements(statements)
151
+
152
+ # Complex SPARQL query
153
+ query = """
154
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
155
+ SELECT ?name ?email WHERE {
156
+ ?person foaf:name ?name .
157
+ OPTIONAL { ?person foaf:email ?email }
158
+ }
159
+ ORDER BY ?name
160
+ """
161
+ results = await repo.query(query)
162
+ ```
163
+
164
+ For more detailed examples, see the [examples](examples/) directory.
165
+
166
+ ## Development
167
+
168
+ ### Setting up Development Environment
169
+
170
+ 1. **Clone the repository**:
171
+ ```bash
172
+ git clone https://github.com/odysa/rdf4j-python.git
173
+ cd rdf4j-python
174
+ ```
175
+
176
+ 2. **Install development dependencies**:
177
+ ```bash
178
+ uv sync --group dev
179
+ ```
180
+
181
+ 3. **Start RDF4J Server** (for integration tests):
182
+ ```bash
183
+ # Using Docker
184
+ docker run -p 19780:8080 eclipse/rdf4j:latest
185
+ ```
186
+
187
+ 4. **Run tests**:
188
+ ```bash
189
+ pytest tests/
190
+ ```
191
+
192
+ 5. **Run linting**:
193
+ ```bash
194
+ ruff check .
195
+ ruff format .
196
+ ```
197
+
198
+ ### Project Structure
199
+
200
+ ```
201
+ rdf4j_python/
202
+ ├── _driver/ # Core async driver implementation
203
+ ├── model/ # Data models and configurations
204
+ ├── exception/ # Custom exceptions
205
+ └── utils/ # Utility functions
206
+
207
+ examples/ # Usage examples
208
+ tests/ # Test suite
209
+ docs/ # Documentation
210
+ ```
211
+
212
+ ### Contributing
213
+
214
+ We welcome contributions! Here's how to get involved:
215
+
216
+ 1. **Fork** the repository on GitHub
217
+ 2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
218
+ 3. **Make** your changes and add tests
219
+ 4. **Run** the test suite to ensure everything works
220
+ 5. **Commit** your changes (`git commit -m 'Add amazing feature'`)
221
+ 6. **Push** to your branch (`git push origin feature/amazing-feature`)
222
+ 7. **Open** a Pull Request
223
+
224
+ ### Running Examples
225
+
226
+ ```bash
227
+ # Make sure RDF4J server is running on localhost:19780
228
+ python examples/complete_workflow.py
229
+ python examples/query.py
230
+ ```
231
+
232
+ ## License
233
+
234
+ This project is licensed under the **BSD 3-Clause License**. See the [LICENSE](LICENSE) file for the full license text.
235
+
236
+ ```
237
+ Copyright (c) 2025, Chengxu Bian
238
+ All rights reserved.
239
+ ```
240
+
241
+ ---
242
+
243
+ **Questions or Issues?** Please feel free to [open an issue](https://github.com/odysa/rdf4j-python/issues) on GitHub.
244
+
245
+ **⭐ Star this repo** if you find it useful!
@@ -1,18 +1,22 @@
1
1
  [project]
2
2
  name = "rdf4j-python"
3
3
  authors = [{ name = "Chengxu Bian", email = "cbian564@gmail.com" }]
4
- version = "0.1.3"
4
+ version = "0.1.5"
5
5
  description = "The Python client for RDF4J"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.10"
8
8
  dependencies = ["httpx>=0.28.1", "pyoxigraph>=0.4.10"]
9
9
 
10
+ [project.optional-dependencies]
11
+ sparqlwrapper = ["sparqlwrapper>=2.0.0"]
12
+
10
13
  [dependency-groups]
11
14
  dev = [
12
15
  "pytest>=8.3.5",
13
16
  "pytest-asyncio>=0.26.0",
14
17
  "pytest-docker>=3.2.1",
15
18
  "ruff>=0.11.8",
19
+ "ty>=0.0.1a7",
16
20
  ]
17
21
  docs = ["furo>=2024.8.6", "sphinx>=8"]
18
22
 
@@ -42,6 +42,17 @@ class BaseClient:
42
42
  class SyncApiClient(BaseClient):
43
43
  """Synchronous API client using httpx.Client."""
44
44
 
45
+ def __init__(self, base_url: str, timeout: int = 10):
46
+ """
47
+ Initializes the SyncApiClient.
48
+
49
+ Args:
50
+ base_url (str): The base URL for the API endpoints.
51
+ timeout (int, optional): Request timeout in seconds. Defaults to 10.
52
+ """
53
+ super().__init__(base_url, timeout)
54
+ self.client = httpx.Client(timeout=self.timeout)
55
+
45
56
  def __enter__(self):
46
57
  """
47
58
  Enters the context and initializes the HTTP client.
@@ -49,7 +60,7 @@ class SyncApiClient(BaseClient):
49
60
  Returns:
50
61
  SyncApiClient: The instance of the client.
51
62
  """
52
- self.client = httpx.Client(timeout=self.timeout).__enter__()
63
+ self.client.__enter__()
53
64
  return self
54
65
 
55
66
  def __exit__(self, exc_type, exc_value, traceback):
@@ -142,6 +153,17 @@ class SyncApiClient(BaseClient):
142
153
  class AsyncApiClient(BaseClient):
143
154
  """Asynchronous API client using httpx.AsyncClient."""
144
155
 
156
+ def __init__(self, base_url: str, timeout: int = 10):
157
+ """
158
+ Initializes the AsyncApiClient.
159
+
160
+ Args:
161
+ base_url (str): The base URL for the API endpoints.
162
+ timeout (int, optional): Request timeout in seconds. Defaults to 10.
163
+ """
164
+ super().__init__(base_url, timeout)
165
+ self.client = httpx.AsyncClient(timeout=self.timeout)
166
+
145
167
  async def __aenter__(self):
146
168
  """
147
169
  Enters the async context and initializes the HTTP client.
@@ -149,7 +171,7 @@ class AsyncApiClient(BaseClient):
149
171
  Returns:
150
172
  AsyncApiClient: The instance of the client.
151
173
  """
152
- self.client = await httpx.AsyncClient(timeout=self.timeout).__aenter__()
174
+ await self.client.__aenter__()
153
175
  return self
154
176
 
155
177
  async def __aexit__(self, exc_type, exc_value, traceback):
@@ -69,6 +69,7 @@ class AsyncRdf4j:
69
69
  query_solutions = og.parse_query_results(
70
70
  response.text, format=og.QueryResultsFormat.JSON
71
71
  )
72
+ assert isinstance(query_solutions, og.QuerySolutions)
72
73
  return [
73
74
  RepositoryMetadata.from_sparql_query_solution(query_solution)
74
75
  for query_solution in query_solutions
@@ -25,10 +25,21 @@ from rdf4j_python.model.term import (
25
25
  from rdf4j_python.utils.const import Rdf4jContentType
26
26
  from rdf4j_python.utils.helpers import serialize_statements
27
27
 
28
+ try:
29
+ from SPARQLWrapper import SPARQLWrapper
30
+
31
+ _has_sparql_wrapper = True
32
+ except ImportError:
33
+ _has_sparql_wrapper = False
34
+
28
35
 
29
36
  class AsyncRdf4JRepository:
30
37
  """Asynchronous interface for interacting with an RDF4J repository."""
31
38
 
39
+ _client: AsyncApiClient
40
+ _repository_id: str
41
+ _sparql_wrapper: Optional["SPARQLWrapper"] = None
42
+
32
43
  def __init__(self, client: AsyncApiClient, repository_id: str):
33
44
  """Initializes the repository interface.
34
45
 
@@ -39,32 +50,47 @@ class AsyncRdf4JRepository:
39
50
  self._client = client
40
51
  self._repository_id = repository_id
41
52
 
53
+ async def get_sparql_wrapper(self) -> "SPARQLWrapper":
54
+ """Returns the SPARQLWrapper for the repository.
55
+
56
+ Returns:
57
+ SPARQLWrapper: The SPARQLWrapper for the repository.
58
+ """
59
+ if not _has_sparql_wrapper:
60
+ raise ImportError(
61
+ "SPARQLWrapper is not installed. Please install it with `pip install rdf4j-python[sparqlwrapper]`"
62
+ )
63
+
64
+ if self._sparql_wrapper is None:
65
+ self._sparql_wrapper = SPARQLWrapper(
66
+ f"{self._client.get_base_url()}/repositories/{self._repository_id}"
67
+ )
68
+ return self._sparql_wrapper
69
+
42
70
  async def query(
43
71
  self,
44
72
  sparql_query: str,
45
73
  infer: bool = True,
46
- accept: Rdf4jContentType = Rdf4jContentType.SPARQL_RESULTS_JSON,
47
- ):
74
+ ) -> og.QuerySolutions | og.QueryBoolean:
48
75
  """Executes a SPARQL SELECT query.
49
76
 
50
77
  Args:
51
78
  sparql_query (str): The SPARQL query string.
52
79
  infer (bool): Whether to include inferred statements. Defaults to True.
53
- accept (Rdf4jContentType): The expected response format.
54
80
 
55
81
  Returns:
56
- dict or str: Parsed JSON results or raw response text.
82
+ og.QuerySolutions | og.QueryBoolean: Parsed query results.
57
83
  """
58
84
  path = f"/repositories/{self._repository_id}"
59
85
  params = {"query": sparql_query, "infer": str(infer).lower()}
60
- headers = {"Accept": accept}
86
+ headers = {"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON}
61
87
  response = await self._client.get(path, params=params, headers=headers)
62
88
  self._handle_repo_not_found_exception(response)
63
- if "json" in response.headers.get("Content-Type", ""):
64
- return response.json()
65
- return response.text
89
+ return og.parse_query_results(response.text, format=og.QueryResultsFormat.JSON)
66
90
 
67
- async def update(self, sparql_update: str):
91
+ async def update(
92
+ self, sparql_update_query: str, content_type: Rdf4jContentType
93
+ ) -> None:
68
94
  """Executes a SPARQL UPDATE command.
69
95
 
70
96
  Args:
@@ -74,11 +100,16 @@ class AsyncRdf4JRepository:
74
100
  RepositoryNotFoundException: If the repository doesn't exist.
75
101
  httpx.HTTPStatusError: If the update fails.
76
102
  """
103
+ # SPARQL UPDATE operations return HTTP 204 No Content on success.
104
+ # No result data is returned as per SPARQL 1.1 UPDATE specification.
77
105
  path = f"/repositories/{self._repository_id}/statements"
78
- headers = {"Content-Type": Rdf4jContentType.SPARQL_UPDATE.value}
79
- response = await self._client.post(path, data=sparql_update, headers=headers)
106
+ headers = {"Content-Type": content_type}
107
+ response = await self._client.post(
108
+ path, content=sparql_update_query, headers=headers
109
+ )
80
110
  self._handle_repo_not_found_exception(response)
81
- response.raise_for_status()
111
+ if response.status_code != httpx.codes.NO_CONTENT:
112
+ raise RepositoryUpdateException(f"Failed to update: {response.text}")
82
113
 
83
114
  async def get_namespaces(self):
84
115
  """Retrieves all namespaces in the repository.
@@ -97,6 +128,7 @@ class AsyncRdf4JRepository:
97
128
  query_solutions = og.parse_query_results(
98
129
  response.text, format=og.QueryResultsFormat.JSON
99
130
  )
131
+ assert isinstance(query_solutions, og.QuerySolutions)
100
132
  return [
101
133
  Namespace.from_sparql_query_solution(query_solution)
102
134
  for query_solution in query_solutions