dqlite-dbapi 0.1.0__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.
- dqlite_dbapi-0.1.0/.github/workflows/publish-to-pypi.yml +50 -0
- dqlite_dbapi-0.1.0/.gitignore +46 -0
- dqlite_dbapi-0.1.0/DEVELOPMENT.md +76 -0
- dqlite_dbapi-0.1.0/LICENSE.md +21 -0
- dqlite_dbapi-0.1.0/PKG-INFO +82 -0
- dqlite_dbapi-0.1.0/README.md +51 -0
- dqlite_dbapi-0.1.0/pyproject.toml +63 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/__init__.py +98 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/aio/__init__.py +31 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/aio/connection.py +86 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/aio/cursor.py +163 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/connection.py +110 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/cursor.py +181 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/exceptions.py +61 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/py.typed +0 -0
- dqlite_dbapi-0.1.0/src/dqlitedbapi/types.py +64 -0
- dqlite_dbapi-0.1.0/tests/conftest.py +1 -0
- dqlite_dbapi-0.1.0/tests/integration/conftest.py +17 -0
- dqlite_dbapi-0.1.0/tests/test_connection.py +35 -0
- dqlite_dbapi-0.1.0/tests/test_cursor.py +88 -0
- dqlite_dbapi-0.1.0/tests/test_exceptions.py +40 -0
- dqlite_dbapi-0.1.0/tests/test_module_attributes.py +92 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on: push
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
name: Build distribution
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v6
|
|
11
|
+
with:
|
|
12
|
+
persist-credentials: false
|
|
13
|
+
- name: Set up Python
|
|
14
|
+
uses: actions/setup-python@v6
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.x"
|
|
17
|
+
- name: Install pypa/build
|
|
18
|
+
run: >-
|
|
19
|
+
python3 -m
|
|
20
|
+
pip install
|
|
21
|
+
build
|
|
22
|
+
--user
|
|
23
|
+
- name: Build a binary wheel and a source tarball
|
|
24
|
+
run: python3 -m build
|
|
25
|
+
- name: Store the distribution packages
|
|
26
|
+
uses: actions/upload-artifact@v5
|
|
27
|
+
with:
|
|
28
|
+
name: python-package-distributions
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
publish-to-pypi:
|
|
32
|
+
name: >-
|
|
33
|
+
Publish to PyPI
|
|
34
|
+
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
|
|
35
|
+
needs:
|
|
36
|
+
- build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment:
|
|
39
|
+
name: pypi
|
|
40
|
+
url: https://pypi.org/p/dqlite-dbapi
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write
|
|
43
|
+
steps:
|
|
44
|
+
- name: Download all the dists
|
|
45
|
+
uses: actions/download-artifact@v6
|
|
46
|
+
with:
|
|
47
|
+
name: python-package-distributions
|
|
48
|
+
path: dist/
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# Testing
|
|
35
|
+
.pytest_cache/
|
|
36
|
+
.coverage
|
|
37
|
+
htmlcov/
|
|
38
|
+
.tox/
|
|
39
|
+
.nox/
|
|
40
|
+
|
|
41
|
+
# mypy
|
|
42
|
+
.mypy_cache/
|
|
43
|
+
|
|
44
|
+
# Distribution
|
|
45
|
+
dist/
|
|
46
|
+
build/
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Python 3.13+
|
|
6
|
+
- [uv](https://github.com/astral-sh/uv) - Fast Python package manager
|
|
7
|
+
- Docker (for integration tests)
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
Start by also cloning [dqlite-wire](https://github.com/letsdiscodev/python-dqlite-wire) and [dqlite-client](https://github.com/letsdiscodev/python-dqlite-client).
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Install uv (if not already installed)
|
|
15
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
16
|
+
|
|
17
|
+
# Create virtual environment and install dependencies
|
|
18
|
+
uv venv --python 3.13
|
|
19
|
+
uv pip install -e "../python-dqlite-wire" -e "../python-dqlite-client" -e ".[dev]"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Development Tools
|
|
23
|
+
|
|
24
|
+
| Tool | Purpose | Command |
|
|
25
|
+
|------|---------|---------|
|
|
26
|
+
| **pytest** | Testing framework | `pytest` |
|
|
27
|
+
| **ruff** | Linter (replaces flake8, isort, etc.) | `ruff check` |
|
|
28
|
+
| **ruff format** | Code formatter (replaces black) | `ruff format` |
|
|
29
|
+
| **mypy** | Static type checker | `mypy src` |
|
|
30
|
+
|
|
31
|
+
## Running Tests
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Run unit tests only
|
|
35
|
+
.venv/bin/pytest tests/ --ignore=tests/integration
|
|
36
|
+
|
|
37
|
+
# Run all tests (requires Docker cluster)
|
|
38
|
+
cd ../dqlite-test-cluster && docker compose up -d
|
|
39
|
+
.venv/bin/pytest tests/
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Linting & Formatting
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Lint
|
|
46
|
+
.venv/bin/ruff check src tests
|
|
47
|
+
|
|
48
|
+
# Auto-fix lint issues
|
|
49
|
+
.venv/bin/ruff check --fix src tests
|
|
50
|
+
|
|
51
|
+
# Format
|
|
52
|
+
.venv/bin/ruff format src tests
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Type Checking
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
.venv/bin/mypy src
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Pre-commit Workflow
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
.venv/bin/ruff format src tests
|
|
65
|
+
.venv/bin/ruff check --fix src tests
|
|
66
|
+
.venv/bin/mypy src
|
|
67
|
+
.venv/bin/pytest tests/ --ignore=tests/integration
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## PEP 249 Compliance
|
|
71
|
+
|
|
72
|
+
This package implements the DB-API 2.0 specification (PEP 249):
|
|
73
|
+
|
|
74
|
+
- `apilevel = "2.0"`
|
|
75
|
+
- `threadsafety = 1` (threads may share module, not connections)
|
|
76
|
+
- `paramstyle = "qmark"` (question mark placeholders)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antoine Leclair and Greg Sadetsky
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dqlite-dbapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: PEP 249 (DB-API 2.0) compliant interface for dqlite
|
|
5
|
+
Project-URL: Homepage, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
6
|
+
Project-URL: Repository, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
7
|
+
Project-URL: Issues, https://github.com/letsdiscodev/python-dqlite-dbapi/issues
|
|
8
|
+
Author-email: Antoine Leclair <antoineleclair@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Keywords: database,dbapi,distributed,dqlite,pep249,sqlite
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
20
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Requires-Dist: dqlite-client>=0.1.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# dqlite-dbapi
|
|
33
|
+
|
|
34
|
+
PEP 249 compliant interface for [dqlite](https://dqlite.io/).
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install dqlite-dbapi
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Sync Usage
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import dqlitedbapi
|
|
46
|
+
|
|
47
|
+
conn = dqlitedbapi.connect("localhost:9001")
|
|
48
|
+
cursor = conn.cursor()
|
|
49
|
+
cursor.execute("SELECT 1")
|
|
50
|
+
print(cursor.fetchone())
|
|
51
|
+
conn.close()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Async Usage
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import asyncio
|
|
58
|
+
from dqlitedbapi.aio import aconnect
|
|
59
|
+
|
|
60
|
+
async def main():
|
|
61
|
+
conn = await aconnect("localhost:9001")
|
|
62
|
+
cursor = await conn.cursor()
|
|
63
|
+
await cursor.execute("SELECT 1")
|
|
64
|
+
print(await cursor.fetchone())
|
|
65
|
+
await conn.close()
|
|
66
|
+
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## PEP 249 Compliance
|
|
71
|
+
|
|
72
|
+
- `apilevel = "2.0"`
|
|
73
|
+
- `threadsafety = 1`
|
|
74
|
+
- `paramstyle = "qmark"`
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for setup and contribution guidelines.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# dqlite-dbapi
|
|
2
|
+
|
|
3
|
+
PEP 249 compliant interface for [dqlite](https://dqlite.io/).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install dqlite-dbapi
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Sync Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import dqlitedbapi
|
|
15
|
+
|
|
16
|
+
conn = dqlitedbapi.connect("localhost:9001")
|
|
17
|
+
cursor = conn.cursor()
|
|
18
|
+
cursor.execute("SELECT 1")
|
|
19
|
+
print(cursor.fetchone())
|
|
20
|
+
conn.close()
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Async Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import asyncio
|
|
27
|
+
from dqlitedbapi.aio import aconnect
|
|
28
|
+
|
|
29
|
+
async def main():
|
|
30
|
+
conn = await aconnect("localhost:9001")
|
|
31
|
+
cursor = await conn.cursor()
|
|
32
|
+
await cursor.execute("SELECT 1")
|
|
33
|
+
print(await cursor.fetchone())
|
|
34
|
+
await conn.close()
|
|
35
|
+
|
|
36
|
+
asyncio.run(main())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## PEP 249 Compliance
|
|
40
|
+
|
|
41
|
+
- `apilevel = "2.0"`
|
|
42
|
+
- `threadsafety = 1`
|
|
43
|
+
- `paramstyle = "qmark"`
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for setup and contribution guidelines.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dqlite-dbapi"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "PEP 249 (DB-API 2.0) compliant interface for dqlite"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Antoine Leclair", email = "antoineleclair@gmail.com" }]
|
|
13
|
+
keywords = ["dqlite", "sqlite", "distributed", "database", "dbapi", "pep249"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Database",
|
|
22
|
+
"Topic :: Database :: Database Engines/Servers",
|
|
23
|
+
"Topic :: Database :: Front-Ends",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = ["dqlite-client>=0.1.0"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/letsdiscodev/python-dqlite-dbapi"
|
|
30
|
+
Repository = "https://github.com/letsdiscodev/python-dqlite-dbapi"
|
|
31
|
+
Issues = "https://github.com/letsdiscodev/python-dqlite-dbapi/issues"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = ["pytest>=8.0", "pytest-cov>=4.0", "pytest-asyncio>=0.23", "mypy>=1.0", "ruff>=0.4"]
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["src/dqlitedbapi"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
pythonpath = ["src"]
|
|
42
|
+
asyncio_mode = "auto"
|
|
43
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
44
|
+
|
|
45
|
+
[tool.mypy]
|
|
46
|
+
strict = true
|
|
47
|
+
python_version = "3.13"
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
target-version = "py313"
|
|
51
|
+
line-length = 100
|
|
52
|
+
src = ["src", "tests"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint.isort]
|
|
58
|
+
known-first-party = ["dqlitedbapi", "dqliteclient", "dqlitewire"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff.format]
|
|
61
|
+
quote-style = "double"
|
|
62
|
+
indent-style = "space"
|
|
63
|
+
docstring-code-format = true
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""PEP 249 compliant interface for dqlite."""
|
|
2
|
+
|
|
3
|
+
from dqlitedbapi.connection import Connection
|
|
4
|
+
from dqlitedbapi.cursor import Cursor
|
|
5
|
+
from dqlitedbapi.exceptions import (
|
|
6
|
+
DatabaseError,
|
|
7
|
+
DataError,
|
|
8
|
+
Error,
|
|
9
|
+
IntegrityError,
|
|
10
|
+
InterfaceError,
|
|
11
|
+
InternalError,
|
|
12
|
+
NotSupportedError,
|
|
13
|
+
OperationalError,
|
|
14
|
+
ProgrammingError,
|
|
15
|
+
Warning,
|
|
16
|
+
)
|
|
17
|
+
from dqlitedbapi.types import (
|
|
18
|
+
BINARY,
|
|
19
|
+
DATETIME,
|
|
20
|
+
NUMBER,
|
|
21
|
+
ROWID,
|
|
22
|
+
STRING,
|
|
23
|
+
Binary,
|
|
24
|
+
Date,
|
|
25
|
+
DateFromTicks,
|
|
26
|
+
Time,
|
|
27
|
+
TimeFromTicks,
|
|
28
|
+
Timestamp,
|
|
29
|
+
TimestampFromTicks,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# PEP 249 module-level attributes
|
|
33
|
+
apilevel = "2.0"
|
|
34
|
+
threadsafety = 1 # Threads may share the module, but not connections
|
|
35
|
+
paramstyle = "qmark" # Question mark style: WHERE name=?
|
|
36
|
+
|
|
37
|
+
# SQLite compatibility attributes (for SQLAlchemy)
|
|
38
|
+
# dqlite uses SQLite 3.x internally
|
|
39
|
+
sqlite_version_info = (3, 35, 0)
|
|
40
|
+
sqlite_version = "3.35.0"
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Module attributes
|
|
44
|
+
"apilevel",
|
|
45
|
+
"threadsafety",
|
|
46
|
+
"paramstyle",
|
|
47
|
+
# Functions
|
|
48
|
+
"connect",
|
|
49
|
+
# Classes
|
|
50
|
+
"Connection",
|
|
51
|
+
"Cursor",
|
|
52
|
+
# Exceptions
|
|
53
|
+
"Warning",
|
|
54
|
+
"Error",
|
|
55
|
+
"InterfaceError",
|
|
56
|
+
"DatabaseError",
|
|
57
|
+
"DataError",
|
|
58
|
+
"OperationalError",
|
|
59
|
+
"IntegrityError",
|
|
60
|
+
"InternalError",
|
|
61
|
+
"ProgrammingError",
|
|
62
|
+
"NotSupportedError",
|
|
63
|
+
# Type constructors
|
|
64
|
+
"Date",
|
|
65
|
+
"Time",
|
|
66
|
+
"Timestamp",
|
|
67
|
+
"DateFromTicks",
|
|
68
|
+
"TimeFromTicks",
|
|
69
|
+
"TimestampFromTicks",
|
|
70
|
+
"Binary",
|
|
71
|
+
# Type objects
|
|
72
|
+
"STRING",
|
|
73
|
+
"BINARY",
|
|
74
|
+
"NUMBER",
|
|
75
|
+
"DATETIME",
|
|
76
|
+
"ROWID",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
__version__ = "0.1.0"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def connect(
|
|
83
|
+
address: str,
|
|
84
|
+
*,
|
|
85
|
+
database: str = "default",
|
|
86
|
+
timeout: float = 10.0,
|
|
87
|
+
) -> Connection:
|
|
88
|
+
"""Connect to a dqlite database.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
address: Node address in "host:port" format
|
|
92
|
+
database: Database name to open
|
|
93
|
+
timeout: Connection timeout in seconds
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A Connection object
|
|
97
|
+
"""
|
|
98
|
+
return Connection(address, database=database, timeout=timeout)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Async PEP 249-style interface for dqlite."""
|
|
2
|
+
|
|
3
|
+
from dqlitedbapi.aio.connection import AsyncConnection
|
|
4
|
+
from dqlitedbapi.aio.cursor import AsyncCursor
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"aconnect",
|
|
8
|
+
"AsyncConnection",
|
|
9
|
+
"AsyncCursor",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def aconnect(
|
|
14
|
+
address: str,
|
|
15
|
+
*,
|
|
16
|
+
database: str = "default",
|
|
17
|
+
timeout: float = 10.0,
|
|
18
|
+
) -> AsyncConnection:
|
|
19
|
+
"""Connect to a dqlite database asynchronously.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
address: Node address in "host:port" format
|
|
23
|
+
database: Database name to open
|
|
24
|
+
timeout: Connection timeout in seconds
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
An AsyncConnection object
|
|
28
|
+
"""
|
|
29
|
+
conn = AsyncConnection(address, database=database, timeout=timeout)
|
|
30
|
+
await conn.connect()
|
|
31
|
+
return conn
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Async connection implementation for dqlite."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from dqliteclient import DqliteConnection
|
|
6
|
+
from dqlitedbapi.aio.cursor import AsyncCursor
|
|
7
|
+
from dqlitedbapi.exceptions import InterfaceError, OperationalError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncConnection:
|
|
11
|
+
"""Async database connection."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
address: str,
|
|
16
|
+
*,
|
|
17
|
+
database: str = "default",
|
|
18
|
+
timeout: float = 10.0,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Initialize connection (does not connect yet).
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
address: Node address in "host:port" format
|
|
24
|
+
database: Database name to open
|
|
25
|
+
timeout: Connection timeout in seconds
|
|
26
|
+
"""
|
|
27
|
+
self._address = address
|
|
28
|
+
self._database = database
|
|
29
|
+
self._timeout = timeout
|
|
30
|
+
self._async_conn: DqliteConnection | None = None
|
|
31
|
+
self._closed = False
|
|
32
|
+
|
|
33
|
+
async def connect(self) -> None:
|
|
34
|
+
"""Establish the connection."""
|
|
35
|
+
if self._closed:
|
|
36
|
+
raise InterfaceError("Connection is closed")
|
|
37
|
+
|
|
38
|
+
if self._async_conn is not None:
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
self._async_conn = DqliteConnection(
|
|
42
|
+
self._address,
|
|
43
|
+
database=self._database,
|
|
44
|
+
timeout=self._timeout,
|
|
45
|
+
)
|
|
46
|
+
try:
|
|
47
|
+
await self._async_conn.connect()
|
|
48
|
+
except Exception as e:
|
|
49
|
+
self._async_conn = None
|
|
50
|
+
raise OperationalError(f"Failed to connect: {e}") from e
|
|
51
|
+
|
|
52
|
+
async def close(self) -> None:
|
|
53
|
+
"""Close the connection."""
|
|
54
|
+
if self._async_conn is not None:
|
|
55
|
+
await self._async_conn.close()
|
|
56
|
+
self._async_conn = None
|
|
57
|
+
self._closed = True
|
|
58
|
+
|
|
59
|
+
async def commit(self) -> None:
|
|
60
|
+
"""Commit any pending transaction."""
|
|
61
|
+
if self._closed:
|
|
62
|
+
raise InterfaceError("Connection is closed")
|
|
63
|
+
|
|
64
|
+
if self._async_conn is not None:
|
|
65
|
+
await self._async_conn.execute("COMMIT")
|
|
66
|
+
|
|
67
|
+
async def rollback(self) -> None:
|
|
68
|
+
"""Roll back any pending transaction."""
|
|
69
|
+
if self._closed:
|
|
70
|
+
raise InterfaceError("Connection is closed")
|
|
71
|
+
|
|
72
|
+
if self._async_conn is not None:
|
|
73
|
+
await self._async_conn.execute("ROLLBACK")
|
|
74
|
+
|
|
75
|
+
async def cursor(self) -> AsyncCursor:
|
|
76
|
+
"""Return a new AsyncCursor object."""
|
|
77
|
+
if self._closed:
|
|
78
|
+
raise InterfaceError("Connection is closed")
|
|
79
|
+
return AsyncCursor(self)
|
|
80
|
+
|
|
81
|
+
async def __aenter__(self) -> "AsyncConnection":
|
|
82
|
+
await self.connect()
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
86
|
+
await self.close()
|