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.
Files changed (32) hide show
  1. sqlalchemy_adbc-0.4.0/.github/workflows/ci.yml +66 -0
  2. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/.github/workflows/publish.yml +54 -0
  3. sqlalchemy_adbc-0.4.0/CHANGELOG.md +176 -0
  4. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/PKG-INFO +65 -38
  5. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/README.md +56 -34
  6. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/pyproject.toml +20 -5
  7. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/__init__.py +4 -4
  8. sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/base.py +265 -0
  9. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/bigquery.py +3 -3
  10. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/flightsql.py +7 -4
  11. sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/postgresql.py +191 -0
  12. sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/postgresql_types.py +150 -0
  13. sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/reflection.py +316 -0
  14. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/src/sqlalchemy_adbc/snowflake.py +3 -3
  15. sqlalchemy_adbc-0.4.0/src/sqlalchemy_adbc/sqlite.py +95 -0
  16. sqlalchemy_adbc-0.4.0/tests/test_alembic_autogenerate.py +223 -0
  17. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_bigquery_url.py +6 -6
  18. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_flightsql_url.py +9 -9
  19. sqlalchemy_adbc-0.4.0/tests/test_postgresql_types.py +336 -0
  20. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_postgresql_url.py +26 -13
  21. sqlalchemy_adbc-0.4.0/tests/test_reflection.py +471 -0
  22. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_snowflake_url.py +9 -9
  23. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/test_sqlite.py +10 -7
  24. sqlalchemy_adbc-0.4.0/tests/test_url_schemes.py +55 -0
  25. sqlalchemy_adbc-0.2.0/.github/workflows/ci.yml +0 -44
  26. sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/base.py +0 -110
  27. sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/postgresql.py +0 -46
  28. sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/sqlite.py +0 -27
  29. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/.gitignore +0 -0
  30. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/LICENSE +0 -0
  31. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.4.0}/tests/__init__.py +0 -0
  32. {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.4
1
+ Metadata-Version: 2.5
2
2
  Name: sqlalchemy-adbc
3
- Version: 0.2.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 :: 3 - Alpha
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.9
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+flightsql://flight-sql.example.com:443?tls=true",
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+sqlite:///path/to/db.sqlite")
91
+ engine = sqlalchemy.create_engine("sqlite+adbc:///path/to/db.sqlite")
87
92
 
88
93
  # PostgreSQL
89
94
  engine = sqlalchemy.create_engine(
90
- "adbc+postgresql://user:pass@host:5432/mydb"
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
- | Driver | URL |
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+flightsql://host:port?tls=true&authorization=...` |
103
- | Flight SQL | `adbc+flightsql://user:TOKEN@host:port?tls=true` (bearer) |
104
- | PostgreSQL | `adbc+postgresql://user:pass@host:port/database` |
105
- | SQLite | `adbc+sqlite:///path/to/db` or `adbc+sqlite:///:memory:` |
106
- | Snowflake | `adbc+snowflake://user:pass@account/db/schema?warehouse=W` |
107
- | BigQuery | `adbc+bigquery:///my-project?dataset=my_dataset` |
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
- Alpha — the core dialect machinery works for simple `SELECT` / `INSERT` /
121
- `CREATE TABLE` use. More advanced SQLAlchemy features are still to come;
122
- the known gaps are called out below so you don't waste time debugging a
123
- missing feature:
124
-
125
- - **Table reflection** (`MetaData.reflect()`): not implemented. The ADBC
126
- driver exposes table/column metadata via the Arrow-based `GetObjects`
127
- call, but this library doesn't yet wire that into SQLAlchemy's
128
- `Inspector` contract. PRs welcome.
129
- - **Alembic autogenerate**: follows from reflection — won't work until
130
- reflection lands. `alembic upgrade` against already-written migrations
131
- is fine (it's pure SQL).
132
- - **PostgreSQL-specific types** (JSONB, ARRAY, UUID, TSTZMULTIRANGE,
133
- …): ADBC's Arrow-over-the-wire codec returns these as strings / lists
134
- of primitives, not as SQLAlchemy's typed objects. If you need ORM-level
135
- round-tripping of PG-specific types, use `psycopg2` until we add a
136
- type compiler.
137
- - **Query parameterization**: uses ADBC's native `$1`-style placeholders
138
- for PostgreSQL and `?` for SQLite. Flight SQL and Snowflake delegate
139
- to the driver's default.
140
-
141
- PRs welcome.
142
-
143
- Tests: 52+ passing against ADBC SQLite + URL/DSN translators on Python 3.9–3.13.
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+flightsql://flight-sql.example.com:443?tls=true",
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+sqlite:///path/to/db.sqlite")
47
+ engine = sqlalchemy.create_engine("sqlite+adbc:///path/to/db.sqlite")
48
48
 
49
49
  # PostgreSQL
50
50
  engine = sqlalchemy.create_engine(
51
- "adbc+postgresql://user:pass@host:5432/mydb"
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
- | Driver | URL |
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+flightsql://host:port?tls=true&authorization=...` |
64
- | Flight SQL | `adbc+flightsql://user:TOKEN@host:port?tls=true` (bearer) |
65
- | PostgreSQL | `adbc+postgresql://user:pass@host:port/database` |
66
- | SQLite | `adbc+sqlite:///path/to/db` or `adbc+sqlite:///:memory:` |
67
- | Snowflake | `adbc+snowflake://user:pass@account/db/schema?warehouse=W` |
68
- | BigQuery | `adbc+bigquery:///my-project?dataset=my_dataset` |
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
- Alpha — the core dialect machinery works for simple `SELECT` / `INSERT` /
82
- `CREATE TABLE` use. More advanced SQLAlchemy features are still to come;
83
- the known gaps are called out below so you don't waste time debugging a
84
- missing feature:
85
-
86
- - **Table reflection** (`MetaData.reflect()`): not implemented. The ADBC
87
- driver exposes table/column metadata via the Arrow-based `GetObjects`
88
- call, but this library doesn't yet wire that into SQLAlchemy's
89
- `Inspector` contract. PRs welcome.
90
- - **Alembic autogenerate**: follows from reflection — won't work until
91
- reflection lands. `alembic upgrade` against already-written migrations
92
- is fine (it's pure SQL).
93
- - **PostgreSQL-specific types** (JSONB, ARRAY, UUID, TSTZMULTIRANGE,
94
- …): ADBC's Arrow-over-the-wire codec returns these as strings / lists
95
- of primitives, not as SQLAlchemy's typed objects. If you need ORM-level
96
- round-tripping of PG-specific types, use `psycopg2` until we add a
97
- type compiler.
98
- - **Query parameterization**: uses ADBC's native `$1`-style placeholders
99
- for PostgreSQL and `?` for SQLite. Flight SQL and Snowflake delegate
100
- to the driver's default.
101
-
102
- PRs welcome.
103
-
104
- Tests: 52+ passing against ADBC SQLite + URL/DSN translators on Python 3.9–3.13.
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.2.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.9"
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 :: 3 - Alpha",
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+flightsql://` etc.
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 = "py39"
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+flightsql://host:port/...?tls=true
7
- adbc+sqlite:///path/to/db.sqlite
8
- adbc+postgresql://user:pass@host:port/db
9
- adbc+snowflake://user:pass@account/db/schema?warehouse=WH
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