rdf4j-python 0.1.4__tar.gz → 0.1.6__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.
- rdf4j_python-0.1.6/PKG-INFO +259 -0
- rdf4j_python-0.1.6/README.md +245 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/pyproject.toml +1 -1
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/_client/_client.py +30 -2
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/_driver/_async_rdf4j_db.py +7 -2
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/_driver/_async_repository.py +2 -1
- rdf4j_python-0.1.6/rdf4j_python/model/repository_config.py +587 -0
- rdf4j_python-0.1.6/rdf4j_python.egg-info/PKG-INFO +259 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python.egg-info/SOURCES.txt +1 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/tests/test_client.py +31 -9
- rdf4j_python-0.1.6/tests/test_client_initialization.py +89 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/tests/test_rdf4j_repository.py +15 -10
- rdf4j_python-0.1.4/PKG-INFO +0 -80
- rdf4j_python-0.1.4/README.md +0 -66
- rdf4j_python-0.1.4/rdf4j_python/model/repository_config.py +0 -1500
- rdf4j_python-0.1.4/rdf4j_python.egg-info/PKG-INFO +0 -80
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/LICENSE +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/__init__.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/_client/__init__.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/_driver/__init__.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/_driver/_async_named_graph.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/exception/__init__.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/exception/repo_exception.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/model/__init__.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/model/_namespace.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/model/_repository_info.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/model/term.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/model/vocabulary.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/utils/__init__.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/utils/const.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python/utils/helpers.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python.egg-info/dependency_links.txt +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python.egg-info/requires.txt +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/rdf4j_python.egg-info/top_level.txt +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/setup.cfg +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/tests/test_async_named_graph.py +0 -0
- {rdf4j_python-0.1.4 → rdf4j_python-0.1.6}/tests/test_helpers.py +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rdf4j-python
|
|
3
|
+
Version: 0.1.6
|
|
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!
|
|
@@ -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
|
|
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
|
-
|
|
174
|
+
await self.client.__aenter__()
|
|
153
175
|
return self
|
|
154
176
|
|
|
155
177
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
@@ -251,3 +273,9 @@ class AsyncApiClient(BaseClient):
|
|
|
251
273
|
return await self.client.delete(
|
|
252
274
|
self._build_url(path), params=params, headers=headers
|
|
253
275
|
)
|
|
276
|
+
|
|
277
|
+
async def aclose(self):
|
|
278
|
+
"""
|
|
279
|
+
Asynchronously closes the client connection.
|
|
280
|
+
"""
|
|
281
|
+
await self.client.aclose()
|
|
@@ -26,6 +26,7 @@ class AsyncRdf4j:
|
|
|
26
26
|
base_url (str): Base URL of the RDF4J server.
|
|
27
27
|
"""
|
|
28
28
|
self._base_url = base_url.rstrip("/")
|
|
29
|
+
self._client = AsyncApiClient(base_url=self._base_url)
|
|
29
30
|
|
|
30
31
|
async def __aenter__(self):
|
|
31
32
|
"""Enters the async context and initializes the HTTP client.
|
|
@@ -33,7 +34,7 @@ class AsyncRdf4j:
|
|
|
33
34
|
Returns:
|
|
34
35
|
AsyncRdf4j: The initialized RDF4J interface.
|
|
35
36
|
"""
|
|
36
|
-
self._client = await
|
|
37
|
+
self._client = await self._client.__aenter__()
|
|
37
38
|
return self
|
|
38
39
|
|
|
39
40
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
@@ -103,7 +104,7 @@ class AsyncRdf4j:
|
|
|
103
104
|
RepositoryCreationException: If repository creation fails.
|
|
104
105
|
"""
|
|
105
106
|
path = f"/repositories/{config.repo_id}"
|
|
106
|
-
headers = {"Content-Type": Rdf4jContentType.TURTLE}
|
|
107
|
+
headers = {"Content-Type": Rdf4jContentType.TURTLE.value}
|
|
107
108
|
response: httpx.Response = await self._client.put(
|
|
108
109
|
path, content=config.to_turtle(), headers=headers
|
|
109
110
|
)
|
|
@@ -128,3 +129,7 @@ class AsyncRdf4j:
|
|
|
128
129
|
raise RepositoryDeletionException(
|
|
129
130
|
f"Failed to delete repository '{repository_id}': {response.status_code} - {response.text}"
|
|
130
131
|
)
|
|
132
|
+
|
|
133
|
+
async def aclose(self):
|
|
134
|
+
"""Asynchronously closes the client connection."""
|
|
135
|
+
await self._client.aclose()
|
|
@@ -100,7 +100,8 @@ class AsyncRdf4JRepository:
|
|
|
100
100
|
RepositoryNotFoundException: If the repository doesn't exist.
|
|
101
101
|
httpx.HTTPStatusError: If the update fails.
|
|
102
102
|
"""
|
|
103
|
-
#
|
|
103
|
+
# SPARQL UPDATE operations return HTTP 204 No Content on success.
|
|
104
|
+
# No result data is returned as per SPARQL 1.1 UPDATE specification.
|
|
104
105
|
path = f"/repositories/{self._repository_id}/statements"
|
|
105
106
|
headers = {"Content-Type": content_type}
|
|
106
107
|
response = await self._client.post(
|