Skip to content

Commit a87cb47

Browse files
Initial commit
0 parents  commit a87cb47

30 files changed

+1324
-0
lines changed

.gitignore

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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/

DEVELOPMENT.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

pyproject.toml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

src/dqlitedbapi/__init__.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
1.73 KB
Binary file not shown.
5.58 KB
Binary file not shown.
8.99 KB
Binary file not shown.
2.39 KB
Binary file not shown.

0 commit comments

Comments
 (0)