sqlalchemy-adbc 0.2.0__tar.gz → 0.4.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.
- sqlalchemy_adbc-0.4.0/.github/workflows/ci.yml +66 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/.github/workflows/publish.yml +54 -0
- sqlalchemy_adbc-0.4.0/CHANGELOG.md +176 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/PKG-INFO +65 -38
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/README.md +56 -34
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/pyproject.toml +20 -5
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/__init__.py +4 -4
- sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/base.py +265 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/bigquery.py +3 -3
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/flightsql.py +7 -4
- sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/postgresql.py +191 -0
- sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/postgresql_types.py +150 -0
- sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/reflection.py +316 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/snowflake.py +3 -3
- sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/sqlite.py +95 -0
- sqlalchemy_adbc-0.4.0/tests/test_alembic_autogenerate.py +223 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_bigquery_url.py +6 -6
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_flightsql_url.py +9 -9
- sqlalchemy_adbc-0.4.0/tests/test_postgresql_types.py +336 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_postgresql_url.py +26 -13
- sqlalchemy_adbc-0.4.0/tests/test_reflection.py +471 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_snowflake_url.py +9 -9
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_sqlite.py +10 -7
- sqlalchemy_adbc-0.4.0/tests/test_url_schemes.py +55 -0
- sqlalchemy_adbc-0.2.0/.github/workflows/ci.yml +0 -44
- sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/base.py +0 -110
- sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/postgresql.py +0 -46
- sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/sqlite.py +0 -27
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/.gitignore +0 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/LICENSE +0 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/__init__.py +0 -0
- {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_base.py +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- name: Install
|
|
22
|
+
run: pip install -e ".[dev]"
|
|
23
|
+
- name: Ruff check
|
|
24
|
+
run: ruff check src/ tests/
|
|
25
|
+
- name: Ruff format
|
|
26
|
+
run: ruff format --check src/ tests/
|
|
27
|
+
- name: Mypy
|
|
28
|
+
run: mypy src tests
|
|
29
|
+
|
|
30
|
+
test:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
strategy:
|
|
33
|
+
fail-fast: false
|
|
34
|
+
matrix:
|
|
35
|
+
# Matrix tracks active Python versions only — 3.9 hit EOL in
|
|
36
|
+
# October 2025, so it's dropped. Bump when a new minor ships;
|
|
37
|
+
# drop when Python marks one end-of-life.
|
|
38
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
39
|
+
# Postgres service for integration tests (test_postgresql_types.py,
|
|
40
|
+
# issue #3). Gated by DRLS_PG_URL — tests skip when the env var is
|
|
41
|
+
# absent, so local `pytest` runs without a local PG also pass.
|
|
42
|
+
services:
|
|
43
|
+
postgres:
|
|
44
|
+
image: postgres:16
|
|
45
|
+
env:
|
|
46
|
+
POSTGRES_USER: test
|
|
47
|
+
POSTGRES_PASSWORD: test
|
|
48
|
+
POSTGRES_DB: test
|
|
49
|
+
ports:
|
|
50
|
+
- 5432:5432
|
|
51
|
+
options: >-
|
|
52
|
+
--health-cmd "pg_isready -U test"
|
|
53
|
+
--health-interval 10s
|
|
54
|
+
--health-timeout 5s
|
|
55
|
+
--health-retries 5
|
|
56
|
+
env:
|
|
57
|
+
DRLS_PG_URL: "adbc+postgresql://test:test@localhost:5432/test"
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
- uses: actions/setup-python@v5
|
|
61
|
+
with:
|
|
62
|
+
python-version: ${{ matrix.python-version }}
|
|
63
|
+
- name: Install
|
|
64
|
+
run: pip install -e ".[dev,postgresql]"
|
|
65
|
+
- name: Pytest
|
|
66
|
+
run: pytest tests/ --cov=sqlalchemy_adbc --cov-report=term-missing
|
|
@@ -87,3 +87,57 @@ jobs:
|
|
|
87
87
|
name: dist
|
|
88
88
|
path: dist/
|
|
89
89
|
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
90
|
+
|
|
91
|
+
github-release:
|
|
92
|
+
# Creates a GitHub Release for every tag push, using the matching
|
|
93
|
+
# CHANGELOG.md section as the release body. Runs AFTER the PyPI
|
|
94
|
+
# publish succeeds so we don't advertise a version that failed
|
|
95
|
+
# to upload. No binary assets attached — PyPI is the distribution
|
|
96
|
+
# channel; duplicating wheels here invites drift.
|
|
97
|
+
name: Create GitHub Release
|
|
98
|
+
needs: publish-pypi
|
|
99
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
permissions:
|
|
102
|
+
contents: write
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@v4
|
|
105
|
+
- name: Extract release notes from CHANGELOG
|
|
106
|
+
id: notes
|
|
107
|
+
run: |
|
|
108
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
109
|
+
# Grab the section under `## [X.Y.Z]` up to the next `## `
|
|
110
|
+
# heading (or EOF). `awk` keeps this portable across bash
|
|
111
|
+
# versions without depending on gh's CHANGELOG conventions.
|
|
112
|
+
NOTES=$(awk -v ver="$TAG" '
|
|
113
|
+
/^## \[/ {
|
|
114
|
+
if (found) exit
|
|
115
|
+
if ($0 ~ "\\[" ver "\\]") { found = 1; next }
|
|
116
|
+
}
|
|
117
|
+
found { print }
|
|
118
|
+
' CHANGELOG.md)
|
|
119
|
+
if [ -z "$NOTES" ]; then
|
|
120
|
+
echo "::warning::No CHANGELOG entry found for version $TAG"
|
|
121
|
+
NOTES="Release v$TAG. See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details."
|
|
122
|
+
fi
|
|
123
|
+
# Append PyPI link so the Release page directs installers
|
|
124
|
+
# back to the canonical distribution.
|
|
125
|
+
NOTES="$NOTES
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
📦 **Install:** \`pip install sqlalchemy-adbc==$TAG\` —
|
|
130
|
+
[view on PyPI](https://pypi.org/project/sqlalchemy-adbc/$TAG/)"
|
|
131
|
+
{
|
|
132
|
+
echo "notes<<EOF"
|
|
133
|
+
echo "$NOTES"
|
|
134
|
+
echo "EOF"
|
|
135
|
+
} >> "$GITHUB_OUTPUT"
|
|
136
|
+
- name: Create Release
|
|
137
|
+
env:
|
|
138
|
+
GH_TOKEN: ${{ github.token }}
|
|
139
|
+
run: |
|
|
140
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
141
|
+
gh release create "v$TAG" \
|
|
142
|
+
--title "v$TAG" \
|
|
143
|
+
--notes "${{ steps.notes.outputs.notes }}"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `sqlalchemy-adbc` are documented here. Format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
5
|
+
this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.4.0] — 2026-11-07
|
|
10
|
+
|
|
11
|
+
URL schemes now follow SQLAlchemy's `<backend>+adbc://` convention,
|
|
12
|
+
plus compatibility fixes for SQLAlchemy 2.1 and ADBC 1.13. Existing
|
|
13
|
+
`adbc+<backend>://` URLs keep working and emit a `DeprecationWarning`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **URL schemes are now `<backend>+adbc://`** — `postgresql+adbc://`,
|
|
18
|
+
`sqlite+adbc://`, `flightsql+adbc://`, `snowflake+adbc://`,
|
|
19
|
+
`bigquery+adbc://`. This follows SQLAlchemy's `dialect+driver`
|
|
20
|
+
convention: ADBC is the driver. `URL.get_backend_name()` now agrees
|
|
21
|
+
with `dialect.name`, so tools that pick behavior from the URL (for
|
|
22
|
+
example Superset's engine specs) see the real backend.
|
|
23
|
+
([#15](https://github.com/drls-io/sqlalchemy-adbc/issues/15))
|
|
24
|
+
|
|
25
|
+
### Deprecated
|
|
26
|
+
|
|
27
|
+
- **`adbc+<backend>://` URL spellings** — still registered and
|
|
28
|
+
functional, but `create_engine` emits a `DeprecationWarning`. They
|
|
29
|
+
will be removed in 1.0.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **PostgreSQL database names with SQLAlchemy 2.1** — SQLAlchemy 2.1
|
|
34
|
+
percent-decodes `URL.database`, so the libpq URI lost its escapes
|
|
35
|
+
(`my%20db` became `my db`). The dialect now detects the parser's
|
|
36
|
+
behavior and quotes the database path exactly once on both 2.0 and
|
|
37
|
+
2.1. ([#19](https://github.com/drls-io/sqlalchemy-adbc/issues/19))
|
|
38
|
+
- **CI on ADBC 1.13** — `adbc_driver_sqlite` 1.13.0 reports NOT NULL
|
|
39
|
+
correctly, which turned the strict xfail for column nullability into
|
|
40
|
+
a hard failure. The xfail now applies only to drivers older than
|
|
41
|
+
1.13. ([#13](https://github.com/drls-io/sqlalchemy-adbc/issues/13))
|
|
42
|
+
|
|
43
|
+
## [0.3.0] — 2026-04-21
|
|
44
|
+
|
|
45
|
+
Largest release so far: table reflection, Alembic autogenerate,
|
|
46
|
+
PostgreSQL type decorators, and breaking fixes to dialect naming
|
|
47
|
+
and Python version support. Not backwards-compatible for callers
|
|
48
|
+
that read `engine.dialect.name` or run on Python 3.9.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **Table reflection via `adbc_get_objects`** — the full SQLAlchemy
|
|
53
|
+
Inspector contract now works for any ADBC driver that implements
|
|
54
|
+
the GetObjects metadata call. Covers `get_columns`,
|
|
55
|
+
`get_pk_constraint`, `get_foreign_keys`, `get_unique_constraints`,
|
|
56
|
+
`get_table_names`, `get_view_names`, `get_schema_names`, and
|
|
57
|
+
`has_table`. `MetaData.reflect(bind=engine)` works end-to-end.
|
|
58
|
+
([#5](https://github.com/drls-io/sqlalchemy-adbc/pull/5),
|
|
59
|
+
closes [#1](https://github.com/drls-io/sqlalchemy-adbc/issues/1))
|
|
60
|
+
- **Native `get_indexes` for SQLite and PostgreSQL** — ADBC's
|
|
61
|
+
GetObjects spec doesn't carry per-index column lists, so we
|
|
62
|
+
query the backend's native catalog (PRAGMA `index_list` /
|
|
63
|
+
`index_info` for SQLite; `pg_index` / `pg_class` / `pg_attribute`
|
|
64
|
+
with `LATERAL unnest WITH ORDINALITY` for PG). Identifiers are
|
|
65
|
+
properly escaped — PRAGMA can't be parameterized.
|
|
66
|
+
([#6](https://github.com/drls-io/sqlalchemy-adbc/pull/6))
|
|
67
|
+
- **Alembic autogenerate support** — fallout from the dialect-name
|
|
68
|
+
fix below. Alembic's DDL impl registry now finds the correct
|
|
69
|
+
per-backend impl (`SQLiteImpl`, `PostgresqlImpl`) for free.
|
|
70
|
+
Covers create/drop table, add/drop column, and roundtrip
|
|
71
|
+
idempotency (apply autogenerate output → re-run → no-op).
|
|
72
|
+
([#7](https://github.com/drls-io/sqlalchemy-adbc/pull/7),
|
|
73
|
+
closes [#2](https://github.com/drls-io/sqlalchemy-adbc/issues/2))
|
|
74
|
+
- **PostgreSQL type decorators** — `JSONB`, `JSON`, `UUID`, `INET`,
|
|
75
|
+
`CIDR`, `MACADDR`, `MACADDR8`. Round-trip PG-specific types
|
|
76
|
+
through ADBC as the Python objects SQLAlchemy users expect:
|
|
77
|
+
JSONB → dict/list, UUID → `uuid.UUID` (or `str` with
|
|
78
|
+
`as_uuid=False`), INET/CIDR/MAC as strings. PG column reflection
|
|
79
|
+
now queries `information_schema.columns.udt_name` directly
|
|
80
|
+
(ADBC's `xdbc_type_name` is unreliable for PG-extension types),
|
|
81
|
+
so `MetaData.reflect()` surfaces these as TypeDecorators, not
|
|
82
|
+
`NullType`. ([#8](https://github.com/drls-io/sqlalchemy-adbc/pull/8),
|
|
83
|
+
closes [#3](https://github.com/drls-io/sqlalchemy-adbc/issues/3))
|
|
84
|
+
- **CI Postgres service** — integration tests (`test_postgresql_types.py`)
|
|
85
|
+
now run against a real `postgres:16` container, gated by
|
|
86
|
+
`DRLS_PG_URL`. Local `pytest` runs without Postgres continue to
|
|
87
|
+
skip these cleanly.
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
|
|
91
|
+
- **BREAKING — dialect `name` and `driver` attributes flipped** to
|
|
92
|
+
match SQLAlchemy's convention: `name` is the backend identity
|
|
93
|
+
(`sqlite`, `postgresql`, `flightsql`, `snowflake`, `bigquery`),
|
|
94
|
+
`driver` is the wire path (`adbc`). Previously every dialect
|
|
95
|
+
reported `name="adbc"`, which caused Alembic's DDL-impl lookup
|
|
96
|
+
to `KeyError`. URL forms (`adbc+sqlite://`, etc.) are unchanged.
|
|
97
|
+
Callers inspecting `engine.dialect.name` must update expectations.
|
|
98
|
+
- **BREAKING — dropped Python 3.9.** Python 3.9 reached end-of-life
|
|
99
|
+
in October 2025. `requires-python` is now `>=3.10`; the CI matrix
|
|
100
|
+
covers 3.10–3.13.
|
|
101
|
+
- **Development Status classifier: Alpha → Beta.** Matches the
|
|
102
|
+
README's scope statement — core functionality is stable enough
|
|
103
|
+
for real use, but a few months of external feedback still needed
|
|
104
|
+
before 1.0.
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- **PG `paramstyle` — `numeric_dollar`.** Forces SQLAlchemy to emit
|
|
109
|
+
`$1, $2, ...` (libpq's native wire form) instead of `:name` binds
|
|
110
|
+
via ADBC's `adbc.statement.bind_by_name` (which libpq rejects
|
|
111
|
+
with `NOT_IMPLEMENTED`) or `:1, :2, ...` (which PG's parser
|
|
112
|
+
rejects with `syntax error at or near ":"`). Set via
|
|
113
|
+
`__init__(kwargs.setdefault(...))` because `DefaultDialect.__init__`
|
|
114
|
+
clobbers the class-level attribute from `dbapi.paramstyle`.
|
|
115
|
+
- `get_columns` now honors SQLAlchemy's `ReflectedColumn` shape
|
|
116
|
+
([#5](https://github.com/drls-io/sqlalchemy-adbc/pull/5))
|
|
117
|
+
- Mypy is gated in CI with proper `sqlalchemy.engine.interfaces`
|
|
118
|
+
return types
|
|
119
|
+
- `cache_ok = True` declared explicitly on `JSON` and `CIDR`
|
|
120
|
+
subclasses — the attribute does not inherit across `TypeDecorator`
|
|
121
|
+
subclasses, so the absence silenced statement caching and
|
|
122
|
+
emitted SAWarning on every engine.
|
|
123
|
+
|
|
124
|
+
## [0.2.0] — 2026-04-19
|
|
125
|
+
|
|
126
|
+
Security + correctness release. Not backwards-compatible for
|
|
127
|
+
Flight SQL users relying on the plaintext-by-default behavior.
|
|
128
|
+
|
|
129
|
+
### Changed
|
|
130
|
+
|
|
131
|
+
- **BREAKING — Flight SQL TLS is now default-on** (`tls=true`).
|
|
132
|
+
Send a bearer token over plaintext gRPC was an unmissable
|
|
133
|
+
credential-leak shape. Local/dev users on plaintext listeners
|
|
134
|
+
must now opt out with `?tls=false`. Default port auto-picks
|
|
135
|
+
443 for TLS, 50051 for plaintext (Arrow Flight convention).
|
|
136
|
+
|
|
137
|
+
### Fixed
|
|
138
|
+
|
|
139
|
+
- **URL → DSN special-character escaping** for PostgreSQL and
|
|
140
|
+
Snowflake. A password containing `@`, `:`, `/`, `#`, `%` was
|
|
141
|
+
previously concatenated into the DSN unquoted and would silently
|
|
142
|
+
re-route libpq to the wrong host. Username/password now routed
|
|
143
|
+
through `urllib.parse.quote`; database path stays as-is since
|
|
144
|
+
SQLAlchemy's URL parser leaves it percent-encoded.
|
|
145
|
+
- mypy errors in the Flight SQL dialect around `URL.query`
|
|
146
|
+
tuple vs str handling. A `_scalar()` helper normalizes repeated
|
|
147
|
+
keys to their last value (HTTP/shell convention).
|
|
148
|
+
- Formatting drift on three files.
|
|
149
|
+
|
|
150
|
+
### Added
|
|
151
|
+
|
|
152
|
+
- CI gates `mypy src tests` alongside the existing ruff check.
|
|
153
|
+
- Extensive URL-parse test coverage: 13 PG, 9 Snowflake, 5 BigQuery
|
|
154
|
+
tests covering special-char escaping, no-credentials paths, and
|
|
155
|
+
edge cases. Total suite: 51 (up from 16).
|
|
156
|
+
- Documentation of known gaps in the README (table reflection,
|
|
157
|
+
Alembic autogenerate, PG-specific types) with links to tracking
|
|
158
|
+
issues.
|
|
159
|
+
|
|
160
|
+
## [0.1.0] — 2026-04-18
|
|
161
|
+
|
|
162
|
+
Initial public release.
|
|
163
|
+
|
|
164
|
+
### Added
|
|
165
|
+
|
|
166
|
+
- Generic `ADBCDialect` base class with per-driver subclasses for
|
|
167
|
+
Flight SQL, SQLite, PostgreSQL, Snowflake, and BigQuery
|
|
168
|
+
- URL → `db_kwargs` translation per driver
|
|
169
|
+
- Entry-point registration under `sqlalchemy.dialects` so any
|
|
170
|
+
`adbc+<driver>://` URL resolves automatically
|
|
171
|
+
- PyPI trusted publishing via OIDC
|
|
172
|
+
|
|
173
|
+
[Unreleased]: https://github.com/drls-io/sqlalchemy-adbc/compare/v0.3.0...HEAD
|
|
174
|
+
[0.3.0]: https://github.com/drls-io/sqlalchemy-adbc/compare/v0.2.0...v0.3.0
|
|
175
|
+
[0.2.0]: https://github.com/drls-io/sqlalchemy-adbc/compare/v0.1.0...v0.2.0
|
|
176
|
+
[0.1.0]: https://github.com/drls-io/sqlalchemy-adbc/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sqlalchemy-adbc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Generic SQLAlchemy dialect for any ADBC driver (Flight SQL, PostgreSQL, SQLite, Snowflake, BigQuery).
|
|
5
5
|
Project-URL: Homepage, https://github.com/drls-io/sqlalchemy-adbc
|
|
6
6
|
Project-URL: Repository, https://github.com/drls-io/sqlalchemy-adbc
|
|
@@ -9,13 +9,17 @@ Author: DRLS Contributors
|
|
|
9
9
|
License: Apache-2.0
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: adbc,arrow,dialect,flight-sql,sqlalchemy
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
21
|
Classifier: Topic :: Database
|
|
18
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.10
|
|
19
23
|
Requires-Dist: adbc-driver-manager>=1.0
|
|
20
24
|
Requires-Dist: pyarrow>=14.0
|
|
21
25
|
Requires-Dist: sqlalchemy>=2.0
|
|
@@ -23,6 +27,7 @@ Provides-Extra: bigquery
|
|
|
23
27
|
Requires-Dist: adbc-driver-bigquery>=1.0; extra == 'bigquery'
|
|
24
28
|
Provides-Extra: dev
|
|
25
29
|
Requires-Dist: adbc-driver-sqlite>=1.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: alembic>=1.13; extra == 'dev'
|
|
26
31
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
27
32
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
28
33
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
@@ -76,18 +81,18 @@ import sqlalchemy
|
|
|
76
81
|
|
|
77
82
|
# Flight SQL
|
|
78
83
|
engine = sqlalchemy.create_engine(
|
|
79
|
-
"adbc
|
|
84
|
+
"flightsql+adbc://flight-sql.example.com:443?tls=true",
|
|
80
85
|
connect_args={"db_kwargs": {
|
|
81
86
|
"adbc.flight.sql.authorization_header": f"Bearer {token}",
|
|
82
87
|
}},
|
|
83
88
|
)
|
|
84
89
|
|
|
85
90
|
# SQLite
|
|
86
|
-
engine = sqlalchemy.create_engine("adbc
|
|
91
|
+
engine = sqlalchemy.create_engine("sqlite+adbc:///path/to/db.sqlite")
|
|
87
92
|
|
|
88
93
|
# PostgreSQL
|
|
89
94
|
engine = sqlalchemy.create_engine(
|
|
90
|
-
"adbc
|
|
95
|
+
"postgresql+adbc://user:pass@host:5432/mydb"
|
|
91
96
|
)
|
|
92
97
|
|
|
93
98
|
# Works with pandas, Polars, marimo SQL cells, etc.
|
|
@@ -97,14 +102,18 @@ df = pd.read_sql("SELECT 1 AS n", engine)
|
|
|
97
102
|
|
|
98
103
|
## URL forms
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
URLs follow SQLAlchemy's `<backend>+<driver>://` convention, with `adbc`
|
|
106
|
+
as the driver. The pre-0.4 `adbc+<backend>://` spelling still works but
|
|
107
|
+
emits a `DeprecationWarning` and will be removed in 1.0.
|
|
108
|
+
|
|
109
|
+
| Backend | URL |
|
|
101
110
|
|--------------|------------------------------------------------------------|
|
|
102
|
-
| Flight SQL | `adbc
|
|
103
|
-
| Flight SQL | `adbc
|
|
104
|
-
| PostgreSQL | `adbc
|
|
105
|
-
| SQLite | `adbc
|
|
106
|
-
| Snowflake | `adbc
|
|
107
|
-
| BigQuery | `adbc
|
|
111
|
+
| Flight SQL | `flightsql+adbc://host:port?tls=true&authorization=...` |
|
|
112
|
+
| Flight SQL | `flightsql+adbc://user:TOKEN@host:port?tls=true` (bearer) |
|
|
113
|
+
| PostgreSQL | `postgresql+adbc://user:pass@host:port/database` |
|
|
114
|
+
| SQLite | `sqlite+adbc:///path/to/db` or `sqlite+adbc:///:memory:` |
|
|
115
|
+
| Snowflake | `snowflake+adbc://user:pass@account/db/schema?warehouse=W` |
|
|
116
|
+
| BigQuery | `bigquery+adbc:///my-project?dataset=my_dataset` |
|
|
108
117
|
|
|
109
118
|
### Flight SQL query-string options
|
|
110
119
|
|
|
@@ -117,30 +126,48 @@ df = pd.read_sql("SELECT 1 AS n", engine)
|
|
|
117
126
|
|
|
118
127
|
## Status
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
129
|
+
**Beta.** Core dialect + reflection + Alembic autogenerate work on
|
|
130
|
+
the ADBC drivers we exercise in CI (SQLite end-to-end, PostgreSQL
|
|
131
|
+
via service container). Not 1.0 yet — we want a few months of
|
|
132
|
+
real-world use and at least one release without breaking changes
|
|
133
|
+
before committing to a stable API.
|
|
134
|
+
|
|
135
|
+
Full history in [CHANGELOG.md](CHANGELOG.md). Download tags on
|
|
136
|
+
[GitHub Releases](https://github.com/drls-io/sqlalchemy-adbc/releases);
|
|
137
|
+
published wheels on [PyPI](https://pypi.org/project/sqlalchemy-adbc/).
|
|
138
|
+
|
|
139
|
+
### What works
|
|
140
|
+
|
|
141
|
+
- `SELECT` / `INSERT` / `CREATE TABLE` / `DROP TABLE` / `ALTER TABLE`
|
|
142
|
+
for every advertised driver
|
|
143
|
+
- `MetaData.reflect(bind=engine)` — full Inspector contract backed
|
|
144
|
+
by ADBC `GetObjects`, including columns, PK, FK, unique
|
|
145
|
+
constraints, indexes (native PRAGMA / `pg_catalog` queries on
|
|
146
|
+
SQLite and PG)
|
|
147
|
+
- Alembic `autogenerate` — end-to-end, reusing SQLAlchemy's native
|
|
148
|
+
backend DDL impls via standard dialect-name lookup
|
|
149
|
+
- URL forms for Flight SQL, PostgreSQL, SQLite, Snowflake, BigQuery
|
|
150
|
+
with correct special-character escaping in passwords
|
|
151
|
+
|
|
152
|
+
### Known limitations
|
|
153
|
+
|
|
154
|
+
- **PostgreSQL range types** (tstzrange, tstzmultirange, int4range,
|
|
155
|
+
…) and **hstore** — not yet mapped to typed decorators; returned
|
|
156
|
+
as raw strings. JSONB / JSON / UUID / INET / CIDR / MACADDR /
|
|
157
|
+
MACADDR8 all round-trip through typed decorators as of 0.3.0
|
|
158
|
+
([#11](https://github.com/drls-io/sqlalchemy-adbc/issues/11))
|
|
159
|
+
- **Drivers exercised in CI** — SQLite fully, PostgreSQL
|
|
160
|
+
integration-tested via service container. Snowflake / BigQuery /
|
|
161
|
+
Flight SQL inherit the shared reflection path but have no
|
|
162
|
+
per-driver CI
|
|
163
|
+
([#12](https://github.com/drls-io/sqlalchemy-adbc/issues/12))
|
|
164
|
+
- **ADBC SQLite NOT NULL reporting** — fixed upstream in
|
|
165
|
+
`adbc_driver_sqlite` 1.13 (Python ≥ 3.11). On older drivers every
|
|
166
|
+
column reflects as nullable=True
|
|
167
|
+
([#13](https://github.com/drls-io/sqlalchemy-adbc/issues/13))
|
|
168
|
+
|
|
169
|
+
Tests: 121 passing on Python 3.10–3.13, including 17 PostgreSQL
|
|
170
|
+
integration tests against a service-container Postgres.
|
|
144
171
|
|
|
145
172
|
## Development
|
|
146
173
|
|
|
@@ -37,18 +37,18 @@ import sqlalchemy
|
|
|
37
37
|
|
|
38
38
|
# Flight SQL
|
|
39
39
|
engine = sqlalchemy.create_engine(
|
|
40
|
-
"adbc
|
|
40
|
+
"flightsql+adbc://flight-sql.example.com:443?tls=true",
|
|
41
41
|
connect_args={"db_kwargs": {
|
|
42
42
|
"adbc.flight.sql.authorization_header": f"Bearer {token}",
|
|
43
43
|
}},
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
# SQLite
|
|
47
|
-
engine = sqlalchemy.create_engine("adbc
|
|
47
|
+
engine = sqlalchemy.create_engine("sqlite+adbc:///path/to/db.sqlite")
|
|
48
48
|
|
|
49
49
|
# PostgreSQL
|
|
50
50
|
engine = sqlalchemy.create_engine(
|
|
51
|
-
"adbc
|
|
51
|
+
"postgresql+adbc://user:pass@host:5432/mydb"
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
# Works with pandas, Polars, marimo SQL cells, etc.
|
|
@@ -58,14 +58,18 @@ df = pd.read_sql("SELECT 1 AS n", engine)
|
|
|
58
58
|
|
|
59
59
|
## URL forms
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
URLs follow SQLAlchemy's `<backend>+<driver>://` convention, with `adbc`
|
|
62
|
+
as the driver. The pre-0.4 `adbc+<backend>://` spelling still works but
|
|
63
|
+
emits a `DeprecationWarning` and will be removed in 1.0.
|
|
64
|
+
|
|
65
|
+
| Backend | URL |
|
|
62
66
|
|--------------|------------------------------------------------------------|
|
|
63
|
-
| Flight SQL | `adbc
|
|
64
|
-
| Flight SQL | `adbc
|
|
65
|
-
| PostgreSQL | `adbc
|
|
66
|
-
| SQLite | `adbc
|
|
67
|
-
| Snowflake | `adbc
|
|
68
|
-
| BigQuery | `adbc
|
|
67
|
+
| Flight SQL | `flightsql+adbc://host:port?tls=true&authorization=...` |
|
|
68
|
+
| Flight SQL | `flightsql+adbc://user:TOKEN@host:port?tls=true` (bearer) |
|
|
69
|
+
| PostgreSQL | `postgresql+adbc://user:pass@host:port/database` |
|
|
70
|
+
| SQLite | `sqlite+adbc:///path/to/db` or `sqlite+adbc:///:memory:` |
|
|
71
|
+
| Snowflake | `snowflake+adbc://user:pass@account/db/schema?warehouse=W` |
|
|
72
|
+
| BigQuery | `bigquery+adbc:///my-project?dataset=my_dataset` |
|
|
69
73
|
|
|
70
74
|
### Flight SQL query-string options
|
|
71
75
|
|
|
@@ -78,30 +82,48 @@ df = pd.read_sql("SELECT 1 AS n", engine)
|
|
|
78
82
|
|
|
79
83
|
## Status
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
85
|
+
**Beta.** Core dialect + reflection + Alembic autogenerate work on
|
|
86
|
+
the ADBC drivers we exercise in CI (SQLite end-to-end, PostgreSQL
|
|
87
|
+
via service container). Not 1.0 yet — we want a few months of
|
|
88
|
+
real-world use and at least one release without breaking changes
|
|
89
|
+
before committing to a stable API.
|
|
90
|
+
|
|
91
|
+
Full history in [CHANGELOG.md](CHANGELOG.md). Download tags on
|
|
92
|
+
[GitHub Releases](https://github.com/drls-io/sqlalchemy-adbc/releases);
|
|
93
|
+
published wheels on [PyPI](https://pypi.org/project/sqlalchemy-adbc/).
|
|
94
|
+
|
|
95
|
+
### What works
|
|
96
|
+
|
|
97
|
+
- `SELECT` / `INSERT` / `CREATE TABLE` / `DROP TABLE` / `ALTER TABLE`
|
|
98
|
+
for every advertised driver
|
|
99
|
+
- `MetaData.reflect(bind=engine)` — full Inspector contract backed
|
|
100
|
+
by ADBC `GetObjects`, including columns, PK, FK, unique
|
|
101
|
+
constraints, indexes (native PRAGMA / `pg_catalog` queries on
|
|
102
|
+
SQLite and PG)
|
|
103
|
+
- Alembic `autogenerate` — end-to-end, reusing SQLAlchemy's native
|
|
104
|
+
backend DDL impls via standard dialect-name lookup
|
|
105
|
+
- URL forms for Flight SQL, PostgreSQL, SQLite, Snowflake, BigQuery
|
|
106
|
+
with correct special-character escaping in passwords
|
|
107
|
+
|
|
108
|
+
### Known limitations
|
|
109
|
+
|
|
110
|
+
- **PostgreSQL range types** (tstzrange, tstzmultirange, int4range,
|
|
111
|
+
…) and **hstore** — not yet mapped to typed decorators; returned
|
|
112
|
+
as raw strings. JSONB / JSON / UUID / INET / CIDR / MACADDR /
|
|
113
|
+
MACADDR8 all round-trip through typed decorators as of 0.3.0
|
|
114
|
+
([#11](https://github.com/drls-io/sqlalchemy-adbc/issues/11))
|
|
115
|
+
- **Drivers exercised in CI** — SQLite fully, PostgreSQL
|
|
116
|
+
integration-tested via service container. Snowflake / BigQuery /
|
|
117
|
+
Flight SQL inherit the shared reflection path but have no
|
|
118
|
+
per-driver CI
|
|
119
|
+
([#12](https://github.com/drls-io/sqlalchemy-adbc/issues/12))
|
|
120
|
+
- **ADBC SQLite NOT NULL reporting** — fixed upstream in
|
|
121
|
+
`adbc_driver_sqlite` 1.13 (Python ≥ 3.11). On older drivers every
|
|
122
|
+
column reflects as nullable=True
|
|
123
|
+
([#13](https://github.com/drls-io/sqlalchemy-adbc/issues/13))
|
|
124
|
+
|
|
125
|
+
Tests: 121 passing on Python 3.10–3.13, including 17 PostgreSQL
|
|
126
|
+
integration tests against a service-container Postgres.
|
|
105
127
|
|
|
106
128
|
## Development
|
|
107
129
|
|
|
@@ -4,19 +4,23 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlalchemy-adbc"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Generic SQLAlchemy dialect for any ADBC driver (Flight SQL, PostgreSQL, SQLite, Snowflake, BigQuery)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
12
|
authors = [{ name = "DRLS Contributors" }]
|
|
13
13
|
keywords = ["sqlalchemy", "adbc", "arrow", "flight-sql", "dialect"]
|
|
14
14
|
classifiers = [
|
|
15
|
-
"Development Status ::
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
17
|
"License :: OSI Approved :: Apache Software License",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
19
19
|
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
20
24
|
"Topic :: Database",
|
|
21
25
|
]
|
|
22
26
|
dependencies = [
|
|
@@ -41,6 +45,10 @@ dev = [
|
|
|
41
45
|
"ruff>=0.6",
|
|
42
46
|
"mypy>=1.10",
|
|
43
47
|
"adbc-driver-sqlite>=1.0",
|
|
48
|
+
# Alembic autogenerate tests (test_alembic_autogenerate.py) rely on
|
|
49
|
+
# the Inspector contract we implemented in #1 — pinning the dev
|
|
50
|
+
# pin ensures CI exercises that integration path.
|
|
51
|
+
"alembic>=1.13",
|
|
44
52
|
]
|
|
45
53
|
|
|
46
54
|
[project.urls]
|
|
@@ -50,8 +58,15 @@ Issues = "https://github.com/drls-io/sqlalchemy-adbc/issues"
|
|
|
50
58
|
|
|
51
59
|
# SQLAlchemy discovers dialects via the `sqlalchemy.dialects` entry point
|
|
52
60
|
# group. The name form is `<name>` or `<name>.<driver>` — the second is what
|
|
53
|
-
# lets us write `adbc
|
|
61
|
+
# lets us write `flightsql+adbc://` etc.
|
|
54
62
|
[project.entry-points."sqlalchemy.dialects"]
|
|
63
|
+
# SQLAlchemy URLs are <backend>+<driver>; ADBC is the driver (#15).
|
|
64
|
+
"flightsql.adbc" = "sqlalchemy_adbc.flightsql:ADBCFlightSQLDialect"
|
|
65
|
+
"sqlite.adbc" = "sqlalchemy_adbc.sqlite:ADBCSQLiteDialect"
|
|
66
|
+
"postgresql.adbc" = "sqlalchemy_adbc.postgresql:ADBCPostgreSQLDialect"
|
|
67
|
+
"snowflake.adbc" = "sqlalchemy_adbc.snowflake:ADBCSnowflakeDialect"
|
|
68
|
+
"bigquery.adbc" = "sqlalchemy_adbc.bigquery:ADBCBigQueryDialect"
|
|
69
|
+
# Deprecated adbc+<backend> spellings; removed in 1.0.
|
|
55
70
|
"adbc" = "sqlalchemy_adbc.base:ADBCDialect"
|
|
56
71
|
"adbc.flightsql" = "sqlalchemy_adbc.flightsql:ADBCFlightSQLDialect"
|
|
57
72
|
"adbc.sqlite" = "sqlalchemy_adbc.sqlite:ADBCSQLiteDialect"
|
|
@@ -64,7 +79,7 @@ packages = ["src/sqlalchemy_adbc"]
|
|
|
64
79
|
|
|
65
80
|
[tool.ruff]
|
|
66
81
|
line-length = 100
|
|
67
|
-
target-version = "
|
|
82
|
+
target-version = "py310"
|
|
68
83
|
|
|
69
84
|
[tool.ruff.lint]
|
|
70
85
|
select = ["E", "F", "W", "I", "B", "UP"]
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
Register dialects via the ``sqlalchemy.dialects`` entry-point group (see
|
|
4
4
|
pyproject.toml). URL forms::
|
|
5
5
|
|
|
6
|
-
adbc
|
|
7
|
-
adbc
|
|
8
|
-
adbc
|
|
9
|
-
adbc
|
|
6
|
+
flightsql+adbc://host:port/...?tls=true
|
|
7
|
+
sqlite+adbc:///path/to/db.sqlite
|
|
8
|
+
postgresql+adbc://user:pass@host:port/db
|
|
9
|
+
snowflake+adbc://user:pass@account/db/schema?warehouse=WH
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from sqlalchemy_adbc.base import ADBCDialect
|