sqlalchemy-cubrid 1.4.1__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.
- {sqlalchemy_cubrid-1.4.1/sqlalchemy_cubrid.egg-info → sqlalchemy_cubrid-1.4.3}/PKG-INFO +38 -27
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/README.md +28 -17
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/pyproject.toml +10 -14
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/__init__.py +1 -1
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/_compat.py +24 -10
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/aio_pycubrid_dialect.py +17 -13
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/alembic_impl.py +5 -3
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/base.py +4 -4
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/compiler.py +93 -19
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/dialect.py +103 -21
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/pycubrid_dialect.py +9 -20
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/requirements.py +3 -3
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/trace.py +11 -4
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/types.py +41 -4
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3/sqlalchemy_cubrid.egg-info}/PKG-INFO +38 -27
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/SOURCES.txt +2 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/requires.txt +3 -3
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_aio_pycubrid_dialect.py +20 -8
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_alembic.py +19 -3
- sqlalchemy_cubrid-1.4.3/test/test_alembic_roundtrip.py +296 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_compiler.py +199 -32
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_dialect_offline.py +112 -2
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_pycubrid_dialect.py +12 -24
- sqlalchemy_cubrid-1.4.3/test/test_reflection_golden.py +291 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_requirements.py +2 -2
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_show_create_table.py +1 -1
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_trace.py +10 -5
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_types.py +5 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/AUTHORS +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/LICENSE +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/setup.cfg +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/dml.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/py.typed +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/dependency_links.txt +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/entry_points.txt +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid.egg-info/top_level.txt +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_aio_integration.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_base.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_dialects.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_integration.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_json.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_logging.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_packaging.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/test/test_stress_pool.py +0 -0
- {sqlalchemy_cubrid-1.4.1 → 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.
|
|
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-
|
|
8
|
-
Project-URL: Repository, https://github.com/cubrid-
|
|
9
|
-
Project-URL: Documentation, https://github.com/cubrid-
|
|
10
|
-
Project-URL: Changelog, https://github.com/cubrid-
|
|
11
|
-
Project-URL: Issues, https://github.com/cubrid-
|
|
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 ::
|
|
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.
|
|
40
|
-
Requires-Dist: mypy==1.
|
|
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"
|
|
@@ -45,7 +45,7 @@ Requires-Dist: alembic>=1.7; extra == "dev"
|
|
|
45
45
|
Provides-Extra: alembic
|
|
46
46
|
Requires-Dist: alembic>=1.7; extra == "alembic"
|
|
47
47
|
Provides-Extra: pycubrid
|
|
48
|
-
Requires-Dist: pycubrid<2.0,>=1.2
|
|
48
|
+
Requires-Dist: pycubrid<2.0,>=1.3.2; extra == "pycubrid"
|
|
49
49
|
Dynamic: license-file
|
|
50
50
|
|
|
51
51
|
# sqlalchemy-cubrid
|
|
@@ -57,17 +57,17 @@ Dynamic: license-file
|
|
|
57
57
|
<!-- BADGES:START -->
|
|
58
58
|
[](https://pypi.org/project/sqlalchemy-cubrid)
|
|
59
59
|
[](https://www.python.org)
|
|
60
|
-
[](https://cubrid-
|
|
60
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml)
|
|
61
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
|
|
62
|
+
[](https://codecov.io/gh/cubrid-lab/sqlalchemy-cubrid)
|
|
63
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid/blob/main/LICENSE)
|
|
64
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid)
|
|
65
|
+
[](https://cubrid-lab.github.io/sqlalchemy-cubrid/)
|
|
66
66
|
<!-- BADGES:END -->
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
> **Status:
|
|
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
|
-
- **
|
|
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 [](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
|
-
- **Async requires pycubrid >= 1.2
|
|
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
|
|
|
@@ -228,8 +239,8 @@ async with AsyncSession(engine) as session:
|
|
|
228
239
|
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
|
|
229
240
|
| SQLAlchemy | 2.0–2.1 |
|
|
230
241
|
| Alembic | >=1.7 |
|
|
231
|
-
| pycubrid (sync) | >=1.2
|
|
232
|
-
| pycubrid (async) | >=1.2
|
|
242
|
+
| pycubrid (sync) | >=1.3.2,<2.0 |
|
|
243
|
+
| pycubrid (async) | >=1.3.2,<2.0 |
|
|
233
244
|
|
|
234
245
|
## FAQ
|
|
235
246
|
|
|
@@ -271,19 +282,19 @@ stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Up
|
|
|
271
282
|
|
|
272
283
|
### Does sqlalchemy-cubrid support async?
|
|
273
284
|
|
|
274
|
-
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.2
|
|
285
|
+
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.3.2,<2.0`. Both pycubrid dialects use native `Connection.ping(False)` / `AsyncConnection.ping(False)` for `pool_pre_ping`, and all Core and ORM features work with `AsyncSession`.
|
|
275
286
|
|
|
276
287
|
|
|
277
288
|
## Related Projects
|
|
278
289
|
|
|
279
|
-
- [pycubrid](https://github.com/cubrid-
|
|
280
|
-
- [cubrid-cookbook-python](https://github.com/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-
|
|
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
|
[](https://pypi.org/project/sqlalchemy-cubrid)
|
|
9
9
|
[](https://www.python.org)
|
|
10
|
-
[](https://cubrid-
|
|
10
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/ci.yml)
|
|
11
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
|
|
12
|
+
[](https://codecov.io/gh/cubrid-lab/sqlalchemy-cubrid)
|
|
13
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid/blob/main/LICENSE)
|
|
14
|
+
[](https://github.com/cubrid-lab/sqlalchemy-cubrid)
|
|
15
|
+
[](https://cubrid-lab.github.io/sqlalchemy-cubrid/)
|
|
16
16
|
<!-- BADGES:END -->
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
> **Status:
|
|
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
|
-
- **
|
|
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 [](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
|
-
- **Async requires pycubrid >= 1.2
|
|
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
|
|
|
@@ -178,8 +189,8 @@ async with AsyncSession(engine) as session:
|
|
|
178
189
|
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
|
|
179
190
|
| SQLAlchemy | 2.0–2.1 |
|
|
180
191
|
| Alembic | >=1.7 |
|
|
181
|
-
| pycubrid (sync) | >=1.2
|
|
182
|
-
| pycubrid (async) | >=1.2
|
|
192
|
+
| pycubrid (sync) | >=1.3.2,<2.0 |
|
|
193
|
+
| pycubrid (async) | >=1.3.2,<2.0 |
|
|
183
194
|
|
|
184
195
|
## FAQ
|
|
185
196
|
|
|
@@ -221,19 +232,19 @@ stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Up
|
|
|
221
232
|
|
|
222
233
|
### Does sqlalchemy-cubrid support async?
|
|
223
234
|
|
|
224
|
-
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.2
|
|
235
|
+
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.3.2,<2.0`. Both pycubrid dialects use native `Connection.ping(False)` / `AsyncConnection.ping(False)` for `pool_pre_ping`, and all Core and ORM features work with `AsyncSession`.
|
|
225
236
|
|
|
226
237
|
|
|
227
238
|
## Related Projects
|
|
228
239
|
|
|
229
|
-
- [pycubrid](https://github.com/cubrid-
|
|
230
|
-
- [cubrid-cookbook-python](https://github.com/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-
|
|
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.
|
|
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 ::
|
|
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.
|
|
47
|
-
"mypy==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",
|
|
@@ -54,15 +54,15 @@ alembic = [
|
|
|
54
54
|
"alembic>=1.7",
|
|
55
55
|
]
|
|
56
56
|
pycubrid = [
|
|
57
|
-
"pycubrid>=1.2
|
|
57
|
+
"pycubrid>=1.3.2,<2.0",
|
|
58
58
|
]
|
|
59
59
|
|
|
60
60
|
[project.urls]
|
|
61
|
-
Homepage = "https://github.com/cubrid-
|
|
62
|
-
Repository = "https://github.com/cubrid-
|
|
63
|
-
Documentation = "https://github.com/cubrid-
|
|
64
|
-
Changelog = "https://github.com/cubrid-
|
|
65
|
-
Issues = "https://github.com/cubrid-
|
|
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"]
|
|
@@ -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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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)
|
{sqlalchemy_cubrid-1.4.1 → sqlalchemy_cubrid-1.4.3}/sqlalchemy_cubrid/aio_pycubrid_dialect.py
RENAMED
|
@@ -7,15 +7,22 @@
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
from importlib import import_module
|
|
10
11
|
from typing import Any, Callable, cast
|
|
11
12
|
|
|
12
13
|
from sqlalchemy.connectors.asyncio import (
|
|
13
14
|
AsyncAdapt_dbapi_connection,
|
|
14
15
|
AsyncAdapt_dbapi_cursor,
|
|
15
|
-
AsyncAdapt_dbapi_module,
|
|
16
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]
|
|
17
23
|
from sqlalchemy import pool as pool_module
|
|
18
|
-
from sqlalchemy.engine.interfaces import ConnectArgsType
|
|
24
|
+
from sqlalchemy.engine.interfaces import ConnectArgsType
|
|
25
|
+
from sqlalchemy_cubrid._compat import DBAPIModule
|
|
19
26
|
from sqlalchemy.engine.url import URL
|
|
20
27
|
from sqlalchemy.util.concurrency import await_only
|
|
21
28
|
|
|
@@ -37,18 +44,21 @@ class AsyncAdapt_pycubrid_connection(AsyncAdapt_dbapi_connection):
|
|
|
37
44
|
|
|
38
45
|
@property
|
|
39
46
|
def autocommit(self) -> bool:
|
|
40
|
-
return self._connection.autocommit
|
|
47
|
+
return bool(self._connection.autocommit)
|
|
41
48
|
|
|
42
49
|
@autocommit.setter
|
|
43
50
|
def autocommit(self, value: bool) -> None:
|
|
44
|
-
self.await_(self._connection.set_autocommit(value))
|
|
51
|
+
self.await_(self._connection.set_autocommit(value))
|
|
52
|
+
|
|
53
|
+
def ping(self, reconnect: bool = True) -> bool:
|
|
54
|
+
return bool(self.await_(self._connection.ping(reconnect)))
|
|
45
55
|
|
|
46
56
|
|
|
47
57
|
class AsyncAdapt_pycubrid_dbapi(AsyncAdapt_dbapi_module):
|
|
48
58
|
def __init__(self, aio_module: Any) -> None:
|
|
49
59
|
self._aio_module = aio_module
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
sync_module = import_module("pycubrid")
|
|
52
62
|
|
|
53
63
|
self.paramstyle = sync_module.paramstyle
|
|
54
64
|
self.Error = sync_module.Error
|
|
@@ -86,7 +96,7 @@ class PyCubridAsyncDialect(PyCubridDialect):
|
|
|
86
96
|
|
|
87
97
|
@classmethod
|
|
88
98
|
def import_dbapi(cls) -> DBAPIModule:
|
|
89
|
-
|
|
99
|
+
aio_module = import_module("pycubrid.aio")
|
|
90
100
|
|
|
91
101
|
return cast(DBAPIModule, AsyncAdapt_pycubrid_dbapi(aio_module))
|
|
92
102
|
|
|
@@ -118,13 +128,7 @@ class PyCubridAsyncDialect(PyCubridDialect):
|
|
|
118
128
|
return connect
|
|
119
129
|
|
|
120
130
|
def do_ping(self, dbapi_connection: Any) -> bool:
|
|
121
|
-
|
|
122
|
-
try:
|
|
123
|
-
cursor.execute("SELECT 1")
|
|
124
|
-
cursor.fetchone()
|
|
125
|
-
finally:
|
|
126
|
-
cursor.close()
|
|
127
|
-
return True
|
|
131
|
+
return bool(dbapi_connection.ping(False))
|
|
128
132
|
|
|
129
133
|
|
|
130
134
|
dialect = PyCubridAsyncDialect
|
|
@@ -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-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
431
|
+
return None
|