sqlalchemy-cubrid 1.4.2__tar.gz → 1.4.3__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 (45) hide show
  1. {sqlalchemy_cubrid-1.4.2/sqlalchemy_cubrid.egg-info → sqlalchemy_cubrid-1.4.3}/PKG-INFO +33 -22
  2. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/README.md +24 -13
  3. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/pyproject.toml +9 -13
  4. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/__init__.py +1 -1
  5. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/_compat.py +24 -10
  6. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/aio_pycubrid_dialect.py +11 -5
  7. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/alembic_impl.py +5 -3
  8. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/base.py +4 -4
  9. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/compiler.py +93 -19
  10. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/dialect.py +103 -21
  11. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/pycubrid_dialect.py +6 -8
  12. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/requirements.py +3 -3
  13. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/trace.py +11 -4
  14. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/types.py +41 -4
  15. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3/sqlalchemy_cubrid.egg-info}/PKG-INFO +33 -22
  16. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/SOURCES.txt +2 -0
  17. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/requires.txt +2 -2
  18. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_alembic.py +19 -3
  19. sqlalchemy_cubrid-1.4.3/test/test_alembic_roundtrip.py +296 -0
  20. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_compiler.py +199 -32
  21. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_dialect_offline.py +112 -2
  22. sqlalchemy_cubrid-1.4.3/test/test_reflection_golden.py +291 -0
  23. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_requirements.py +2 -2
  24. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_show_create_table.py +1 -1
  25. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_trace.py +10 -5
  26. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_types.py +5 -0
  27. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/AUTHORS +0 -0
  28. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/LICENSE +0 -0
  29. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/setup.cfg +0 -0
  30. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/dml.py +0 -0
  31. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/py.typed +0 -0
  32. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/dependency_links.txt +0 -0
  33. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/entry_points.txt +0 -0
  34. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/top_level.txt +0 -0
  35. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_aio_integration.py +0 -0
  36. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_aio_pycubrid_dialect.py +0 -0
  37. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_base.py +0 -0
  38. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_dialects.py +0 -0
  39. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_integration.py +0 -0
  40. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_json.py +0 -0
  41. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_logging.py +0 -0
  42. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_packaging.py +0 -0
  43. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_pycubrid_dialect.py +0 -0
  44. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_stress_pool.py +0 -0
  45. {sqlalchemy_cubrid-1.4.2 → sqlalchemy_cubrid-1.4.3}/test/test_suite.py +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlalchemy-cubrid
3
- Version: 1.4.2
3
+ Version: 1.4.3
4
4
  Summary: CUBRID dialect for SQLAlchemy
5
5
  Author-email: Yeongseon Choe <yeongseon.choe@gmail.com>, Gyeongjun Paik <paikend@gmail.com>
6
6
  License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/cubrid-labs/sqlalchemy-cubrid
8
- Project-URL: Repository, https://github.com/cubrid-labs/sqlalchemy-cubrid
9
- Project-URL: Documentation, https://github.com/cubrid-labs/sqlalchemy-cubrid/tree/main/docs
10
- Project-URL: Changelog, https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/CHANGELOG.md
11
- Project-URL: Issues, https://github.com/cubrid-labs/sqlalchemy-cubrid/issues
7
+ Project-URL: Homepage, https://github.com/cubrid-lab/sqlalchemy-cubrid
8
+ Project-URL: Repository, https://github.com/cubrid-lab/sqlalchemy-cubrid
9
+ Project-URL: Documentation, https://github.com/cubrid-lab/sqlalchemy-cubrid/tree/main/docs
10
+ Project-URL: Changelog, https://github.com/cubrid-lab/sqlalchemy-cubrid/blob/main/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/cubrid-lab/sqlalchemy-cubrid/issues
12
12
  Keywords: SQLAlchemy,CUBRID,dialect
13
- Classifier: Development Status :: 4 - Beta
13
+ Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Environment :: Console
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: Operating System :: OS Independent
@@ -36,8 +36,8 @@ Provides-Extra: dev
36
36
  Requires-Dist: pytest>=7.0; extra == "dev"
37
37
  Requires-Dist: pytest-cov; extra == "dev"
38
38
  Requires-Dist: pytest-asyncio; extra == "dev"
39
- Requires-Dist: ruff==0.15.11; extra == "dev"
40
- Requires-Dist: mypy==1.20.1; extra == "dev"
39
+ Requires-Dist: ruff==0.15.12; extra == "dev"
40
+ Requires-Dist: mypy==2.1.0; extra == "dev"
41
41
  Requires-Dist: bandit[toml]==1.9.4; extra == "dev"
42
42
  Requires-Dist: pre-commit; extra == "dev"
43
43
  Requires-Dist: tox; extra == "dev"
@@ -57,17 +57,17 @@ Dynamic: license-file
57
57
  <!-- BADGES:START -->
58
58
  [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-cubrid)](https://pypi.org/project/sqlalchemy-cubrid)
59
59
  [![python version](https://img.shields.io/pypi/pyversions/sqlalchemy-cubrid)](https://www.python.org)
60
- [![ci workflow](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml/badge.svg)](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml)
61
- [![integration-full workflow](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/integration-full.yml/badge.svg)](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
62
- [![coverage](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid/branch/main/graph/badge.svg)](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
63
- [![license](https://img.shields.io/github/license/cubrid-labs/sqlalchemy-cubrid)](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
64
- [![GitHub stars](https://img.shields.io/github/stars/cubrid-labs/sqlalchemy-cubrid)](https://github.com/cubrid-labs/sqlalchemy-cubrid)
65
- [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://cubrid-labs.github.io/sqlalchemy-cubrid/)
60
+ [![ci workflow](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml/badge.svg)](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml)
61
+ [![integration-full workflow](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/integration-full.yml/badge.svg)](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
62
+ [![coverage](https://codecov.io/gh/cubrid-lab/sqlalchemy-cubrid/branch/main/graph/badge.svg)](https://codecov.io/gh/cubrid-lab/sqlalchemy-cubrid)
63
+ [![license](https://img.shields.io/github/license/cubrid-lab/sqlalchemy-cubrid)](https://github.com/cubrid-lab/sqlalchemy-cubrid/blob/main/LICENSE)
64
+ [![GitHub stars](https://img.shields.io/github/stars/cubrid-lab/sqlalchemy-cubrid)](https://github.com/cubrid-lab/sqlalchemy-cubrid)
65
+ [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://cubrid-lab.github.io/sqlalchemy-cubrid/)
66
66
  <!-- BADGES:END -->
67
67
 
68
68
  ---
69
69
 
70
- > **Status: Beta.** The core public API follows semantic versioning; minor releases may add features and bug fixes while the project remains under active development.
70
+ > **Status: Production/Stable** `sqlalchemy-cubrid` is a maintained SQLAlchemy dialect for CUBRID supporting SQLAlchemy 2.0–2.1 and CUBRID 10.2–11.4.
71
71
 
72
72
  ## Why sqlalchemy-cubrid?
73
73
 
@@ -78,7 +78,7 @@ actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
78
78
  **sqlalchemy-cubrid** bridges that gap:
79
79
 
80
80
  - Full SQLAlchemy 2.0–2.1 dialect with **statement caching** and **PEP 561 typing**
81
- - **619 offline tests** with **~98.26% code coverage** — no database required to run them
81
+ - **Extensive offline test suite** with **high code coverage** ([CI badge above](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml)) — no database required to run them
82
82
  - **Concurrency stress tests** — `QueuePool` sync threaded + asyncio.gather workloads validated against live CUBRID
83
83
  - **SQLAlchemy 2.2-ready compat shim** — private API access wrapped in `_compat.py` (still pinned `<2.2` until full SA 2.2 validation)
84
84
  - Tested against **4 CUBRID versions** (10.2, 11.0, 11.2, 11.4) across **Python 3.10 -- 3.14**
@@ -86,6 +86,14 @@ actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
86
86
  - Alembic migration support out of the box
87
87
  - **Three driver options** — C-extension (`cubrid://`), pure Python (`cubrid+pycubrid://`), or async pure Python (`cubrid+aiopycubrid://`)
88
88
 
89
+ ## Support Status
90
+
91
+ - **Status**: Production/Stable [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-cubrid)](https://pypi.org/project/sqlalchemy-cubrid)
92
+ - Supported matrix: SQLAlchemy `>=2.0,<2.2`, CUBRID `10.2`, `11.0`, `11.2`, `11.4`, Python `3.10`–`3.14`
93
+ - Integration CI exercises Python 3.10 and 3.14 against all four CUBRID versions on every PR; intermediate versions (3.11–3.13) are supported and validated via the offline test suite
94
+ - SQLAlchemy `2.2` remains canary-only until explicitly added to the supported matrix
95
+ - See [Known Limitations](#known-limitations) for behavior boundaries and unsupported features
96
+
89
97
  ## Architecture
90
98
 
91
99
  ```mermaid
@@ -197,12 +205,15 @@ async with AsyncSession(engine) as session:
197
205
 
198
206
  ## Known Limitations
199
207
 
200
- - **No `RETURNING`** — `INSERT/UPDATE/DELETE ... RETURNING` not supported; use `cursor.lastrowid` or `LAST_INSERT_ID()`
208
+ - **No `RETURNING`** — `INSERT/UPDATE/DELETE ... RETURNING` not supported; use `cursor.lastrowid` or `SELECT LAST_INSERT_ID()` after the statement
201
209
  - **No sequences** — CUBRID uses `AUTO_INCREMENT` only
202
210
  - **No multi-schema** — single schema per database
203
- - **DDL auto-commits** — migrations are not transactional (`transactional_ddl = False`)
211
+ - **DDL auto-commits** — migrations are not transactional (`transactional_ddl = False`); use Alembic batch migrations and test rollback scenarios manually
204
212
  - **SQLAlchemy 2.0–2.1 only** — pinned to `<2.2` due to internal API dependencies ([details](docs/ARCHITECTURE.md))
205
213
  - **Async requires pycubrid >= 1.3.2,<2.0** — the `cubrid+aiopycubrid://` driver needs the async-capable pycubrid package line currently supported by this project
214
+ - **CARDINALITY() broken** — `func.cardinality()` raises `CompileError` with workaround guidance; the CUBRID server has a [known bug](https://github.com/cubrid-lab/.github/issues/3)
215
+ - **Reserved words auto-quoted** — Column names matching CUBRID reserved words (`day`, `count`, `value`, etc.) are automatically double-quoted in DDL; see [reserved word list](https://github.com/cubrid-lab/.github/issues/5)
216
+ - **Timezone type fidelity** — CUBRID `TIMESTAMPTZ` is reflected as `TIMESTAMP`; timezone offset is preserved in the stored value but the dialect does not distinguish `TZ` vs `LTZ` semantics at the SQLAlchemy type level. In practice this means `datetime` objects round-trip correctly but explicit TZ/LTZ type introspection is not available
206
217
 
207
218
  ## Documentation
208
219
 
@@ -276,14 +287,14 @@ Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid asy
276
287
 
277
288
  ## Related Projects
278
289
 
279
- - [pycubrid](https://github.com/cubrid-labs/pycubrid) — Pure Python DB-API 2.0 driver for CUBRID
280
- - [cubrid-cookbook-python](https://github.com/cubrid-labs/cubrid-cookbook-python) — Production-ready Python examples for CUBRID
290
+ - [pycubrid](https://github.com/cubrid-lab/pycubrid) — Pure Python DB-API 2.0 driver for CUBRID
291
+ - [cubrid-cookbook-python](https://github.com/cubrid-lab/cubrid-cookbook-python) — Production-ready Python examples for CUBRID
281
292
 
282
293
  ## Roadmap
283
294
 
284
295
  See [`ROADMAP.md`](ROADMAP.md) for this project's direction and next milestones.
285
296
 
286
- For the ecosystem-wide view, see the [CUBRID Labs Ecosystem Roadmap](https://github.com/cubrid-labs/.github/blob/main/ROADMAP.md) and [Project Board](https://github.com/orgs/cubrid-labs/projects/2).
297
+ For the ecosystem-wide view, see the [CUBRID Labs Ecosystem Roadmap](https://github.com/cubrid-lab/.github/blob/main/ROADMAP.md) and [Project Board](https://github.com/orgs/cubrid-lab/projects/2).
287
298
 
288
299
  ## Contributing
289
300
 
@@ -7,17 +7,17 @@
7
7
  <!-- BADGES:START -->
8
8
  [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-cubrid)](https://pypi.org/project/sqlalchemy-cubrid)
9
9
  [![python version](https://img.shields.io/pypi/pyversions/sqlalchemy-cubrid)](https://www.python.org)
10
- [![ci workflow](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml/badge.svg)](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml)
11
- [![integration-full workflow](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/integration-full.yml/badge.svg)](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
12
- [![coverage](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid/branch/main/graph/badge.svg)](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
13
- [![license](https://img.shields.io/github/license/cubrid-labs/sqlalchemy-cubrid)](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
14
- [![GitHub stars](https://img.shields.io/github/stars/cubrid-labs/sqlalchemy-cubrid)](https://github.com/cubrid-labs/sqlalchemy-cubrid)
15
- [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://cubrid-labs.github.io/sqlalchemy-cubrid/)
10
+ [![ci workflow](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml/badge.svg)](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml)
11
+ [![integration-full workflow](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/integration-full.yml/badge.svg)](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
12
+ [![coverage](https://codecov.io/gh/cubrid-lab/sqlalchemy-cubrid/branch/main/graph/badge.svg)](https://codecov.io/gh/cubrid-lab/sqlalchemy-cubrid)
13
+ [![license](https://img.shields.io/github/license/cubrid-lab/sqlalchemy-cubrid)](https://github.com/cubrid-lab/sqlalchemy-cubrid/blob/main/LICENSE)
14
+ [![GitHub stars](https://img.shields.io/github/stars/cubrid-lab/sqlalchemy-cubrid)](https://github.com/cubrid-lab/sqlalchemy-cubrid)
15
+ [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://cubrid-lab.github.io/sqlalchemy-cubrid/)
16
16
  <!-- BADGES:END -->
17
17
 
18
18
  ---
19
19
 
20
- > **Status: Beta.** The core public API follows semantic versioning; minor releases may add features and bug fixes while the project remains under active development.
20
+ > **Status: Production/Stable** `sqlalchemy-cubrid` is a maintained SQLAlchemy dialect for CUBRID supporting SQLAlchemy 2.0–2.1 and CUBRID 10.2–11.4.
21
21
 
22
22
  ## Why sqlalchemy-cubrid?
23
23
 
@@ -28,7 +28,7 @@ actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
28
28
  **sqlalchemy-cubrid** bridges that gap:
29
29
 
30
30
  - Full SQLAlchemy 2.0–2.1 dialect with **statement caching** and **PEP 561 typing**
31
- - **619 offline tests** with **~98.26% code coverage** — no database required to run them
31
+ - **Extensive offline test suite** with **high code coverage** ([CI badge above](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml)) — no database required to run them
32
32
  - **Concurrency stress tests** — `QueuePool` sync threaded + asyncio.gather workloads validated against live CUBRID
33
33
  - **SQLAlchemy 2.2-ready compat shim** — private API access wrapped in `_compat.py` (still pinned `<2.2` until full SA 2.2 validation)
34
34
  - Tested against **4 CUBRID versions** (10.2, 11.0, 11.2, 11.4) across **Python 3.10 -- 3.14**
@@ -36,6 +36,14 @@ actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
36
36
  - Alembic migration support out of the box
37
37
  - **Three driver options** — C-extension (`cubrid://`), pure Python (`cubrid+pycubrid://`), or async pure Python (`cubrid+aiopycubrid://`)
38
38
 
39
+ ## Support Status
40
+
41
+ - **Status**: Production/Stable [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-cubrid)](https://pypi.org/project/sqlalchemy-cubrid)
42
+ - Supported matrix: SQLAlchemy `>=2.0,<2.2`, CUBRID `10.2`, `11.0`, `11.2`, `11.4`, Python `3.10`–`3.14`
43
+ - Integration CI exercises Python 3.10 and 3.14 against all four CUBRID versions on every PR; intermediate versions (3.11–3.13) are supported and validated via the offline test suite
44
+ - SQLAlchemy `2.2` remains canary-only until explicitly added to the supported matrix
45
+ - See [Known Limitations](#known-limitations) for behavior boundaries and unsupported features
46
+
39
47
  ## Architecture
40
48
 
41
49
  ```mermaid
@@ -147,12 +155,15 @@ async with AsyncSession(engine) as session:
147
155
 
148
156
  ## Known Limitations
149
157
 
150
- - **No `RETURNING`** — `INSERT/UPDATE/DELETE ... RETURNING` not supported; use `cursor.lastrowid` or `LAST_INSERT_ID()`
158
+ - **No `RETURNING`** — `INSERT/UPDATE/DELETE ... RETURNING` not supported; use `cursor.lastrowid` or `SELECT LAST_INSERT_ID()` after the statement
151
159
  - **No sequences** — CUBRID uses `AUTO_INCREMENT` only
152
160
  - **No multi-schema** — single schema per database
153
- - **DDL auto-commits** — migrations are not transactional (`transactional_ddl = False`)
161
+ - **DDL auto-commits** — migrations are not transactional (`transactional_ddl = False`); use Alembic batch migrations and test rollback scenarios manually
154
162
  - **SQLAlchemy 2.0–2.1 only** — pinned to `<2.2` due to internal API dependencies ([details](docs/ARCHITECTURE.md))
155
163
  - **Async requires pycubrid >= 1.3.2,<2.0** — the `cubrid+aiopycubrid://` driver needs the async-capable pycubrid package line currently supported by this project
164
+ - **CARDINALITY() broken** — `func.cardinality()` raises `CompileError` with workaround guidance; the CUBRID server has a [known bug](https://github.com/cubrid-lab/.github/issues/3)
165
+ - **Reserved words auto-quoted** — Column names matching CUBRID reserved words (`day`, `count`, `value`, etc.) are automatically double-quoted in DDL; see [reserved word list](https://github.com/cubrid-lab/.github/issues/5)
166
+ - **Timezone type fidelity** — CUBRID `TIMESTAMPTZ` is reflected as `TIMESTAMP`; timezone offset is preserved in the stored value but the dialect does not distinguish `TZ` vs `LTZ` semantics at the SQLAlchemy type level. In practice this means `datetime` objects round-trip correctly but explicit TZ/LTZ type introspection is not available
156
167
 
157
168
  ## Documentation
158
169
 
@@ -226,14 +237,14 @@ Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid asy
226
237
 
227
238
  ## Related Projects
228
239
 
229
- - [pycubrid](https://github.com/cubrid-labs/pycubrid) — Pure Python DB-API 2.0 driver for CUBRID
230
- - [cubrid-cookbook-python](https://github.com/cubrid-labs/cubrid-cookbook-python) — Production-ready Python examples for CUBRID
240
+ - [pycubrid](https://github.com/cubrid-lab/pycubrid) — Pure Python DB-API 2.0 driver for CUBRID
241
+ - [cubrid-cookbook-python](https://github.com/cubrid-lab/cubrid-cookbook-python) — Production-ready Python examples for CUBRID
231
242
 
232
243
  ## Roadmap
233
244
 
234
245
  See [`ROADMAP.md`](ROADMAP.md) for this project's direction and next milestones.
235
246
 
236
- For the ecosystem-wide view, see the [CUBRID Labs Ecosystem Roadmap](https://github.com/cubrid-labs/.github/blob/main/ROADMAP.md) and [Project Board](https://github.com/orgs/cubrid-labs/projects/2).
247
+ For the ecosystem-wide view, see the [CUBRID Labs Ecosystem Roadmap](https://github.com/cubrid-lab/.github/blob/main/ROADMAP.md) and [Project Board](https://github.com/orgs/cubrid-lab/projects/2).
237
248
 
238
249
  ## Contributing
239
250
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sqlalchemy-cubrid"
7
- version = "1.4.2"
7
+ version = "1.4.3"
8
8
  description = "CUBRID dialect for SQLAlchemy"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -15,7 +15,7 @@ authors = [
15
15
  ]
16
16
  keywords = ["SQLAlchemy", "CUBRID", "dialect"]
17
17
  classifiers = [
18
- "Development Status :: 4 - Beta",
18
+ "Development Status :: 5 - Production/Stable",
19
19
  "Environment :: Console",
20
20
  "Intended Audience :: Developers",
21
21
  "Operating System :: OS Independent",
@@ -43,8 +43,8 @@ dev = [
43
43
  "pytest>=7.0",
44
44
  "pytest-cov",
45
45
  "pytest-asyncio",
46
- "ruff==0.15.11",
47
- "mypy==1.20.1",
46
+ "ruff==0.15.12",
47
+ "mypy==2.1.0",
48
48
  "bandit[toml]==1.9.4",
49
49
  "pre-commit",
50
50
  "tox",
@@ -58,11 +58,11 @@ pycubrid = [
58
58
  ]
59
59
 
60
60
  [project.urls]
61
- Homepage = "https://github.com/cubrid-labs/sqlalchemy-cubrid"
62
- Repository = "https://github.com/cubrid-labs/sqlalchemy-cubrid"
63
- Documentation = "https://github.com/cubrid-labs/sqlalchemy-cubrid/tree/main/docs"
64
- Changelog = "https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/CHANGELOG.md"
65
- Issues = "https://github.com/cubrid-labs/sqlalchemy-cubrid/issues"
61
+ Homepage = "https://github.com/cubrid-lab/sqlalchemy-cubrid"
62
+ Repository = "https://github.com/cubrid-lab/sqlalchemy-cubrid"
63
+ Documentation = "https://github.com/cubrid-lab/sqlalchemy-cubrid/tree/main/docs"
64
+ Changelog = "https://github.com/cubrid-lab/sqlalchemy-cubrid/blob/main/CHANGELOG.md"
65
+ Issues = "https://github.com/cubrid-lab/sqlalchemy-cubrid/issues"
66
66
 
67
67
  [project.entry-points."sqlalchemy.dialects"]
68
68
  cubrid = "sqlalchemy_cubrid.dialect:CubridDialect"
@@ -90,10 +90,6 @@ strict = true
90
90
  warn_return_any = true
91
91
  warn_unused_configs = true
92
92
 
93
- [[tool.mypy.overrides]]
94
- module = "test.*"
95
- ignore_errors = true
96
-
97
93
  [tool.bandit]
98
94
  exclude_dirs = ["test"]
99
95
  skips = ["B101"]
@@ -52,7 +52,7 @@ from sqlalchemy.sql.sqltypes import (
52
52
  TIMESTAMP,
53
53
  )
54
54
 
55
- __version__ = "1.4.2"
55
+ __version__ = "1.4.3"
56
56
 
57
57
  __all__ = (
58
58
  "insert",
@@ -4,6 +4,22 @@ from typing import Any
4
4
 
5
5
  from sqlalchemy.sql import elements, visitors
6
6
 
7
+ # DBAPIModule was added in SQLAlchemy 2.1; fall back to Any for 2.0 compat.
8
+ try:
9
+ from sqlalchemy.engine.interfaces import DBAPIModule
10
+ except ImportError: # pragma: no cover — SA 2.0
11
+ DBAPIModule = Any # type: ignore[assignment,misc]
12
+
13
+ __all__ = [
14
+ "DBAPIModule",
15
+ "is_literal_value",
16
+ "bind_with_type",
17
+ "get_for_update_arg",
18
+ "get_limit_clause",
19
+ "get_offset_clause",
20
+ "get_distinct",
21
+ ]
22
+
7
23
 
8
24
  def is_literal_value(value: Any) -> bool:
9
25
  """Check if value is a plain Python literal (not a SQL element or schema object)."""
@@ -15,16 +31,10 @@ def is_literal_value(value: Any) -> bool:
15
31
 
16
32
 
17
33
  def bind_with_type(element: elements.BindParameter[Any], type_: Any) -> elements.BindParameter[Any]:
18
- """Create a new BindParameter preserving all flags but with the given type."""
19
- return elements.BindParameter(
20
- element.key,
21
- element.value,
22
- type_=type_,
23
- unique=element.unique,
24
- expanding=element.expanding,
25
- literal_execute=element.literal_execute,
26
- isoutparam=element.isoutparam,
27
- )
34
+ """Create a copy of *element* with *type_* overridden, preserving all internal state."""
35
+ cloned = element._clone()
36
+ cloned.type = type_
37
+ return cloned
28
38
 
29
39
 
30
40
  def get_for_update_arg(select: Any) -> Any | None:
@@ -37,3 +47,7 @@ def get_limit_clause(select: Any) -> Any | None:
37
47
 
38
48
  def get_offset_clause(select: Any) -> Any | None:
39
49
  return getattr(select, "_offset_clause", None)
50
+
51
+
52
+ def get_distinct(select: Any) -> Any:
53
+ return getattr(select, "_distinct", False)
@@ -13,10 +13,16 @@ from typing import Any, Callable, cast
13
13
  from sqlalchemy.connectors.asyncio import (
14
14
  AsyncAdapt_dbapi_connection,
15
15
  AsyncAdapt_dbapi_cursor,
16
- AsyncAdapt_dbapi_module,
17
16
  )
17
+
18
+ # AsyncAdapt_dbapi_module was added in SQLAlchemy 2.1.
19
+ try:
20
+ from sqlalchemy.connectors.asyncio import AsyncAdapt_dbapi_module
21
+ except ImportError: # pragma: no cover — SA 2.0
22
+ AsyncAdapt_dbapi_module = object # type: ignore[assignment,misc]
18
23
  from sqlalchemy import pool as pool_module
19
- from sqlalchemy.engine.interfaces import ConnectArgsType, DBAPIModule
24
+ from sqlalchemy.engine.interfaces import ConnectArgsType
25
+ from sqlalchemy_cubrid._compat import DBAPIModule
20
26
  from sqlalchemy.engine.url import URL
21
27
  from sqlalchemy.util.concurrency import await_only
22
28
 
@@ -38,14 +44,14 @@ class AsyncAdapt_pycubrid_connection(AsyncAdapt_dbapi_connection):
38
44
 
39
45
  @property
40
46
  def autocommit(self) -> bool:
41
- return self._connection.autocommit # type: ignore[union-attr]
47
+ return bool(self._connection.autocommit)
42
48
 
43
49
  @autocommit.setter
44
50
  def autocommit(self, value: bool) -> None:
45
- self.await_(self._connection.set_autocommit(value)) # type: ignore[union-attr]
51
+ self.await_(self._connection.set_autocommit(value))
46
52
 
47
53
  def ping(self, reconnect: bool = True) -> bool:
48
- return self.await_(self._connection.ping(reconnect)) # type: ignore[union-attr]
54
+ return bool(self.await_(self._connection.ping(reconnect)))
49
55
 
50
56
 
51
57
  class AsyncAdapt_pycubrid_dbapi(AsyncAdapt_dbapi_module):
@@ -81,7 +81,7 @@ class CubridImpl(DefaultImpl):
81
81
  # ``Text`` / ``CLOB`` / ``STRING`` column it sees a VARCHAR with that
82
82
  # exact length, which trips Alembic's default compare_type into
83
83
  # reporting a spurious type change on every autogenerate run
84
- # (see cubrid-labs/sqlalchemy-cubrid#120).
84
+ # (see cubrid-lab/sqlalchemy-cubrid#120).
85
85
  _CUBRID_UNBOUNDED_VARCHAR_LENGTH: int = 1073741823
86
86
  _unbounded_string_type_names: set[str] = {"TEXT", "CLOB", "STRING"}
87
87
 
@@ -90,7 +90,7 @@ class CubridImpl(DefaultImpl):
90
90
  if isinstance(value, str):
91
91
  return value.strip().lower()
92
92
  if isinstance(value, sa.types.TypeEngine):
93
- return value.__class__.__name__.lower()
93
+ return repr(value).strip().lower()
94
94
  return repr(value).strip().lower()
95
95
 
96
96
  def render_type(
@@ -110,7 +110,9 @@ class CubridImpl(DefaultImpl):
110
110
  if isinstance(value, str):
111
111
  rendered_values.append(repr(value))
112
112
  elif isinstance(value, sa.types.TypeEngine):
113
- rendered_values.append(value.__class__.__name__)
113
+ # Render as sa.TypeName(...) preserving constructor args
114
+ autogen_context.imports.add("import sqlalchemy as sa")
115
+ rendered_values.append(f"sa.{repr(value)}")
114
116
  else:
115
117
  rendered_values.append(repr(value))
116
118
 
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import logging
13
13
  import re
14
- from typing import Any, cast
14
+ from typing import Any
15
15
 
16
16
  from sqlalchemy.engine import default
17
17
  from sqlalchemy.sql import compiler
@@ -402,7 +402,7 @@ class CubridExecutionContext(default.DefaultExecutionContext):
402
402
  def should_autocommit_text(self, statement: str) -> Any:
403
403
  return AUTOCOMMIT_REGEXP.match(statement)
404
404
 
405
- def get_lastrowid(self) -> int:
405
+ def get_lastrowid(self) -> int | None: # type: ignore[override]
406
406
  """Return the last inserted row ID.
407
407
 
408
408
  CUBRID's Python driver does not expose ``cursor.lastrowid``.
@@ -415,7 +415,7 @@ class CubridExecutionContext(default.DefaultExecutionContext):
415
415
  raw_conn = self.root_connection.connection.dbapi_connection
416
416
  if raw_conn is not None and hasattr(raw_conn, "get_last_insert_id"):
417
417
  last_id = raw_conn.get_last_insert_id()
418
- return cast(int, None) if last_id is None else int(last_id) # pyright: ignore[reportInvalidCast]
418
+ return None if last_id is None else int(last_id)
419
419
  except Exception: # nosec B110 — fallback to SQL when driver lacks method
420
420
  log.debug("get_last_insert_id via driver failed, falling back to SQL", exc_info=True)
421
421
 
@@ -428,4 +428,4 @@ class CubridExecutionContext(default.DefaultExecutionContext):
428
428
  return int(row[0])
429
429
  finally:
430
430
  cursor.close()
431
- return cast(int, None) # pyright: ignore[reportInvalidCast]
431
+ return None