sqlalchemy-adbc 0.2.0__tar.gz → 0.3.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 (31) hide show
  1. sqlalchemy_adbc-0.3.0/.github/workflows/ci.yml +66 -0
  2. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/.github/workflows/publish.yml +54 -0
  3. sqlalchemy_adbc-0.3.0/CHANGELOG.md +142 -0
  4. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/PKG-INFO +49 -27
  5. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/README.md +41 -24
  6. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/pyproject.toml +12 -4
  7. sqlalchemy_adbc-0.3.0/src/sqlalchemy_adbc/base.py +253 -0
  8. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/src/sqlalchemy_adbc/bigquery.py +2 -2
  9. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/src/sqlalchemy_adbc/flightsql.py +5 -2
  10. sqlalchemy_adbc-0.3.0/src/sqlalchemy_adbc/postgresql.py +186 -0
  11. sqlalchemy_adbc-0.3.0/src/sqlalchemy_adbc/postgresql_types.py +150 -0
  12. sqlalchemy_adbc-0.3.0/src/sqlalchemy_adbc/reflection.py +316 -0
  13. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/src/sqlalchemy_adbc/snowflake.py +2 -2
  14. sqlalchemy_adbc-0.3.0/src/sqlalchemy_adbc/sqlite.py +95 -0
  15. sqlalchemy_adbc-0.3.0/tests/test_alembic_autogenerate.py +223 -0
  16. sqlalchemy_adbc-0.3.0/tests/test_postgresql_types.py +336 -0
  17. sqlalchemy_adbc-0.3.0/tests/test_reflection.py +469 -0
  18. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/tests/test_sqlite.py +5 -2
  19. sqlalchemy_adbc-0.2.0/.github/workflows/ci.yml +0 -44
  20. sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/base.py +0 -110
  21. sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/postgresql.py +0 -46
  22. sqlalchemy_adbc-0.2.0/src/sqlalchemy_adbc/sqlite.py +0 -27
  23. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/.gitignore +0 -0
  24. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/LICENSE +0 -0
  25. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/src/sqlalchemy_adbc/__init__.py +0 -0
  26. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/tests/__init__.py +0 -0
  27. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/tests/test_base.py +0 -0
  28. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/tests/test_bigquery_url.py +0 -0
  29. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/tests/test_flightsql_url.py +0 -0
  30. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/tests/test_postgresql_url.py +0 -0
  31. {sqlalchemy_adbc-0.2.0 → sqlalchemy_adbc-0.3.0}/tests/test_snowflake_url.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,142 @@
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.3.0] — 2026-04-21
10
+
11
+ Largest release so far: table reflection, Alembic autogenerate,
12
+ PostgreSQL type decorators, and breaking fixes to dialect naming
13
+ and Python version support. Not backwards-compatible for callers
14
+ that read `engine.dialect.name` or run on Python 3.9.
15
+
16
+ ### Added
17
+
18
+ - **Table reflection via `adbc_get_objects`** — the full SQLAlchemy
19
+ Inspector contract now works for any ADBC driver that implements
20
+ the GetObjects metadata call. Covers `get_columns`,
21
+ `get_pk_constraint`, `get_foreign_keys`, `get_unique_constraints`,
22
+ `get_table_names`, `get_view_names`, `get_schema_names`, and
23
+ `has_table`. `MetaData.reflect(bind=engine)` works end-to-end.
24
+ ([#5](https://github.com/drls-io/sqlalchemy-adbc/pull/5),
25
+ closes [#1](https://github.com/drls-io/sqlalchemy-adbc/issues/1))
26
+ - **Native `get_indexes` for SQLite and PostgreSQL** — ADBC's
27
+ GetObjects spec doesn't carry per-index column lists, so we
28
+ query the backend's native catalog (PRAGMA `index_list` /
29
+ `index_info` for SQLite; `pg_index` / `pg_class` / `pg_attribute`
30
+ with `LATERAL unnest WITH ORDINALITY` for PG). Identifiers are
31
+ properly escaped — PRAGMA can't be parameterized.
32
+ ([#6](https://github.com/drls-io/sqlalchemy-adbc/pull/6))
33
+ - **Alembic autogenerate support** — fallout from the dialect-name
34
+ fix below. Alembic's DDL impl registry now finds the correct
35
+ per-backend impl (`SQLiteImpl`, `PostgresqlImpl`) for free.
36
+ Covers create/drop table, add/drop column, and roundtrip
37
+ idempotency (apply autogenerate output → re-run → no-op).
38
+ ([#7](https://github.com/drls-io/sqlalchemy-adbc/pull/7),
39
+ closes [#2](https://github.com/drls-io/sqlalchemy-adbc/issues/2))
40
+ - **PostgreSQL type decorators** — `JSONB`, `JSON`, `UUID`, `INET`,
41
+ `CIDR`, `MACADDR`, `MACADDR8`. Round-trip PG-specific types
42
+ through ADBC as the Python objects SQLAlchemy users expect:
43
+ JSONB → dict/list, UUID → `uuid.UUID` (or `str` with
44
+ `as_uuid=False`), INET/CIDR/MAC as strings. PG column reflection
45
+ now queries `information_schema.columns.udt_name` directly
46
+ (ADBC's `xdbc_type_name` is unreliable for PG-extension types),
47
+ so `MetaData.reflect()` surfaces these as TypeDecorators, not
48
+ `NullType`. ([#8](https://github.com/drls-io/sqlalchemy-adbc/pull/8),
49
+ closes [#3](https://github.com/drls-io/sqlalchemy-adbc/issues/3))
50
+ - **CI Postgres service** — integration tests (`test_postgresql_types.py`)
51
+ now run against a real `postgres:16` container, gated by
52
+ `DRLS_PG_URL`. Local `pytest` runs without Postgres continue to
53
+ skip these cleanly.
54
+
55
+ ### Changed
56
+
57
+ - **BREAKING — dialect `name` and `driver` attributes flipped** to
58
+ match SQLAlchemy's convention: `name` is the backend identity
59
+ (`sqlite`, `postgresql`, `flightsql`, `snowflake`, `bigquery`),
60
+ `driver` is the wire path (`adbc`). Previously every dialect
61
+ reported `name="adbc"`, which caused Alembic's DDL-impl lookup
62
+ to `KeyError`. URL forms (`adbc+sqlite://`, etc.) are unchanged.
63
+ Callers inspecting `engine.dialect.name` must update expectations.
64
+ - **BREAKING — dropped Python 3.9.** Python 3.9 reached end-of-life
65
+ in October 2025. `requires-python` is now `>=3.10`; the CI matrix
66
+ covers 3.10–3.13.
67
+ - **Development Status classifier: Alpha → Beta.** Matches the
68
+ README's scope statement — core functionality is stable enough
69
+ for real use, but a few months of external feedback still needed
70
+ before 1.0.
71
+
72
+ ### Fixed
73
+
74
+ - **PG `paramstyle` — `numeric_dollar`.** Forces SQLAlchemy to emit
75
+ `$1, $2, ...` (libpq's native wire form) instead of `:name` binds
76
+ via ADBC's `adbc.statement.bind_by_name` (which libpq rejects
77
+ with `NOT_IMPLEMENTED`) or `:1, :2, ...` (which PG's parser
78
+ rejects with `syntax error at or near ":"`). Set via
79
+ `__init__(kwargs.setdefault(...))` because `DefaultDialect.__init__`
80
+ clobbers the class-level attribute from `dbapi.paramstyle`.
81
+ - `get_columns` now honors SQLAlchemy's `ReflectedColumn` shape
82
+ ([#5](https://github.com/drls-io/sqlalchemy-adbc/pull/5))
83
+ - Mypy is gated in CI with proper `sqlalchemy.engine.interfaces`
84
+ return types
85
+ - `cache_ok = True` declared explicitly on `JSON` and `CIDR`
86
+ subclasses — the attribute does not inherit across `TypeDecorator`
87
+ subclasses, so the absence silenced statement caching and
88
+ emitted SAWarning on every engine.
89
+
90
+ ## [0.2.0] — 2026-04-19
91
+
92
+ Security + correctness release. Not backwards-compatible for
93
+ Flight SQL users relying on the plaintext-by-default behavior.
94
+
95
+ ### Changed
96
+
97
+ - **BREAKING — Flight SQL TLS is now default-on** (`tls=true`).
98
+ Send a bearer token over plaintext gRPC was an unmissable
99
+ credential-leak shape. Local/dev users on plaintext listeners
100
+ must now opt out with `?tls=false`. Default port auto-picks
101
+ 443 for TLS, 50051 for plaintext (Arrow Flight convention).
102
+
103
+ ### Fixed
104
+
105
+ - **URL → DSN special-character escaping** for PostgreSQL and
106
+ Snowflake. A password containing `@`, `:`, `/`, `#`, `%` was
107
+ previously concatenated into the DSN unquoted and would silently
108
+ re-route libpq to the wrong host. Username/password now routed
109
+ through `urllib.parse.quote`; database path stays as-is since
110
+ SQLAlchemy's URL parser leaves it percent-encoded.
111
+ - mypy errors in the Flight SQL dialect around `URL.query`
112
+ tuple vs str handling. A `_scalar()` helper normalizes repeated
113
+ keys to their last value (HTTP/shell convention).
114
+ - Formatting drift on three files.
115
+
116
+ ### Added
117
+
118
+ - CI gates `mypy src tests` alongside the existing ruff check.
119
+ - Extensive URL-parse test coverage: 13 PG, 9 Snowflake, 5 BigQuery
120
+ tests covering special-char escaping, no-credentials paths, and
121
+ edge cases. Total suite: 51 (up from 16).
122
+ - Documentation of known gaps in the README (table reflection,
123
+ Alembic autogenerate, PG-specific types) with links to tracking
124
+ issues.
125
+
126
+ ## [0.1.0] — 2026-04-18
127
+
128
+ Initial public release.
129
+
130
+ ### Added
131
+
132
+ - Generic `ADBCDialect` base class with per-driver subclasses for
133
+ Flight SQL, SQLite, PostgreSQL, Snowflake, and BigQuery
134
+ - URL → `db_kwargs` translation per driver
135
+ - Entry-point registration under `sqlalchemy.dialects` so any
136
+ `adbc+<driver>://` URL resolves automatically
137
+ - PyPI trusted publishing via OIDC
138
+
139
+ [Unreleased]: https://github.com/drls-io/sqlalchemy-adbc/compare/v0.3.0...HEAD
140
+ [0.3.0]: https://github.com/drls-io/sqlalchemy-adbc/compare/v0.2.0...v0.3.0
141
+ [0.2.0]: https://github.com/drls-io/sqlalchemy-adbc/compare/v0.1.0...v0.2.0
142
+ [0.1.0]: https://github.com/drls-io/sqlalchemy-adbc/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlalchemy-adbc
3
- Version: 0.2.0
3
+ Version: 0.3.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'
@@ -117,30 +122,47 @@ df = pd.read_sql("SELECT 1 AS n", engine)
117
122
 
118
123
  ## Status
119
124
 
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.
125
+ **Beta.** Core dialect + reflection + Alembic autogenerate work on
126
+ the ADBC drivers we exercise in CI (SQLite end-to-end, PostgreSQL
127
+ via service container). Not 1.0 yet — we want a few months of
128
+ real-world use and at least one release without breaking changes
129
+ before committing to a stable API.
130
+
131
+ Full history in [CHANGELOG.md](CHANGELOG.md). Download tags on
132
+ [GitHub Releases](https://github.com/drls-io/sqlalchemy-adbc/releases);
133
+ published wheels on [PyPI](https://pypi.org/project/sqlalchemy-adbc/).
134
+
135
+ ### What works
136
+
137
+ - `SELECT` / `INSERT` / `CREATE TABLE` / `DROP TABLE` / `ALTER TABLE`
138
+ for every advertised driver
139
+ - `MetaData.reflect(bind=engine)` — full Inspector contract backed
140
+ by ADBC `GetObjects`, including columns, PK, FK, unique
141
+ constraints, indexes (native PRAGMA / `pg_catalog` queries on
142
+ SQLite and PG)
143
+ - Alembic `autogenerate` — end-to-end, reusing SQLAlchemy's native
144
+ backend DDL impls via standard dialect-name lookup
145
+ - URL forms for Flight SQL, PostgreSQL, SQLite, Snowflake, BigQuery
146
+ with correct special-character escaping in passwords
147
+
148
+ ### Known limitations
149
+
150
+ - **PostgreSQL range types** (tstzrange, tstzmultirange, int4range,
151
+ …) and **hstore** — not yet mapped to typed decorators; returned
152
+ as raw strings. JSONB / JSON / UUID / INET / CIDR / MACADDR /
153
+ MACADDR8 all round-trip through typed decorators as of 0.3.0
154
+ ([#3](https://github.com/drls-io/sqlalchemy-adbc/issues/3) —
155
+ JSONB/UUID/INET portion closed)
156
+ - **Drivers exercised in CI** — SQLite fully, PostgreSQL
157
+ integration-tested via service container. Snowflake / BigQuery /
158
+ Flight SQL inherit the shared reflection path but have no
159
+ per-driver CI
160
+ - **ADBC SQLite NOT NULL reporting** — upstream driver always
161
+ reports nullable=True; reflection faithfully reflects that
162
+ (tracked, auto-reactivating xfail)
163
+
164
+ Tests: 106 passing on Python 3.10–3.13, including 17 PostgreSQL
165
+ integration tests against a service-container Postgres.
144
166
 
145
167
  ## Development
146
168
 
@@ -78,30 +78,47 @@ df = pd.read_sql("SELECT 1 AS n", engine)
78
78
 
79
79
  ## Status
80
80
 
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.
81
+ **Beta.** Core dialect + reflection + Alembic autogenerate work on
82
+ the ADBC drivers we exercise in CI (SQLite end-to-end, PostgreSQL
83
+ via service container). Not 1.0 yet — we want a few months of
84
+ real-world use and at least one release without breaking changes
85
+ before committing to a stable API.
86
+
87
+ Full history in [CHANGELOG.md](CHANGELOG.md). Download tags on
88
+ [GitHub Releases](https://github.com/drls-io/sqlalchemy-adbc/releases);
89
+ published wheels on [PyPI](https://pypi.org/project/sqlalchemy-adbc/).
90
+
91
+ ### What works
92
+
93
+ - `SELECT` / `INSERT` / `CREATE TABLE` / `DROP TABLE` / `ALTER TABLE`
94
+ for every advertised driver
95
+ - `MetaData.reflect(bind=engine)` — full Inspector contract backed
96
+ by ADBC `GetObjects`, including columns, PK, FK, unique
97
+ constraints, indexes (native PRAGMA / `pg_catalog` queries on
98
+ SQLite and PG)
99
+ - Alembic `autogenerate` — end-to-end, reusing SQLAlchemy's native
100
+ backend DDL impls via standard dialect-name lookup
101
+ - URL forms for Flight SQL, PostgreSQL, SQLite, Snowflake, BigQuery
102
+ with correct special-character escaping in passwords
103
+
104
+ ### Known limitations
105
+
106
+ - **PostgreSQL range types** (tstzrange, tstzmultirange, int4range,
107
+ …) and **hstore** — not yet mapped to typed decorators; returned
108
+ as raw strings. JSONB / JSON / UUID / INET / CIDR / MACADDR /
109
+ MACADDR8 all round-trip through typed decorators as of 0.3.0
110
+ ([#3](https://github.com/drls-io/sqlalchemy-adbc/issues/3) —
111
+ JSONB/UUID/INET portion closed)
112
+ - **Drivers exercised in CI** — SQLite fully, PostgreSQL
113
+ integration-tested via service container. Snowflake / BigQuery /
114
+ Flight SQL inherit the shared reflection path but have no
115
+ per-driver CI
116
+ - **ADBC SQLite NOT NULL reporting** — upstream driver always
117
+ reports nullable=True; reflection faithfully reflects that
118
+ (tracked, auto-reactivating xfail)
119
+
120
+ Tests: 106 passing on Python 3.10–3.13, including 17 PostgreSQL
121
+ integration tests against a service-container Postgres.
105
122
 
106
123
  ## Development
107
124
 
@@ -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.3.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]
@@ -64,7 +72,7 @@ packages = ["src/sqlalchemy_adbc"]
64
72
 
65
73
  [tool.ruff]
66
74
  line-length = 100
67
- target-version = "py39"
75
+ target-version = "py310"
68
76
 
69
77
  [tool.ruff.lint]
70
78
  select = ["E", "F", "W", "I", "B", "UP"]