sqlalchemy-cubrid 1.4.0__tar.gz → 1.4.2__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.0/sqlalchemy_cubrid.egg-info → sqlalchemy_cubrid-1.4.2}/PKG-INFO +12 -11
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/README.md +8 -7
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/pyproject.toml +4 -4
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/__init__.py +1 -1
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/aio_pycubrid_dialect.py +7 -9
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/pycubrid_dialect.py +4 -13
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2/sqlalchemy_cubrid.egg-info}/PKG-INFO +12 -11
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/requires.txt +3 -3
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_aio_pycubrid_dialect.py +20 -8
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_pycubrid_dialect.py +12 -24
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/AUTHORS +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/LICENSE +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/setup.cfg +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/_compat.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/alembic_impl.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/base.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/compiler.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/dialect.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/dml.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/py.typed +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/requirements.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/trace.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/types.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/SOURCES.txt +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/dependency_links.txt +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/entry_points.txt +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/top_level.txt +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_aio_integration.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_alembic.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_base.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_compiler.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_dialect_offline.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_dialects.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_integration.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_json.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_logging.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_packaging.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_requirements.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_show_create_table.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_stress_pool.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_suite.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_trace.py +0 -0
- {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlalchemy-cubrid
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
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
|
|
@@ -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.11; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy==1.20.1; 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
|
|
@@ -58,6 +58,7 @@ Dynamic: license-file
|
|
|
58
58
|
[](https://pypi.org/project/sqlalchemy-cubrid)
|
|
59
59
|
[](https://www.python.org)
|
|
60
60
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml)
|
|
61
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
|
|
61
62
|
[](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
|
|
62
63
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
|
|
63
64
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid)
|
|
@@ -72,12 +73,12 @@ Dynamic: license-file
|
|
|
72
73
|
|
|
73
74
|
CUBRID is a high-performance open-source relational database, widely adopted in
|
|
74
75
|
Korean public-sector and enterprise applications. Until now, there was no
|
|
75
|
-
|
|
76
|
+
actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
|
|
76
77
|
|
|
77
78
|
**sqlalchemy-cubrid** bridges that gap:
|
|
78
79
|
|
|
79
80
|
- Full SQLAlchemy 2.0–2.1 dialect with **statement caching** and **PEP 561 typing**
|
|
80
|
-
- **
|
|
81
|
+
- **619 offline tests** with **~98.26% code coverage** — no database required to run them
|
|
81
82
|
- **Concurrency stress tests** — `QueuePool` sync threaded + asyncio.gather workloads validated against live CUBRID
|
|
82
83
|
- **SQLAlchemy 2.2-ready compat shim** — private API access wrapped in `_compat.py` (still pinned `<2.2` until full SA 2.2 validation)
|
|
83
84
|
- Tested against **4 CUBRID versions** (10.2, 11.0, 11.2, 11.4) across **Python 3.10 -- 3.14**
|
|
@@ -192,7 +193,7 @@ async with AsyncSession(engine) as session:
|
|
|
192
193
|
- Schema reflection -- tables, views, columns, PKs, FKs, indexes, unique constraints, comments
|
|
193
194
|
- Alembic migrations via `CubridImpl` (auto-discovered entry point)
|
|
194
195
|
- All 6 CUBRID isolation levels (dual-granularity: class-level + instance-level)
|
|
195
|
-
- Async support
|
|
196
|
+
- Async support — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
|
|
196
197
|
|
|
197
198
|
## Known Limitations
|
|
198
199
|
|
|
@@ -201,7 +202,7 @@ async with AsyncSession(engine) as session:
|
|
|
201
202
|
- **No multi-schema** — single schema per database
|
|
202
203
|
- **DDL auto-commits** — migrations are not transactional (`transactional_ddl = False`)
|
|
203
204
|
- **SQLAlchemy 2.0–2.1 only** — pinned to `<2.2` due to internal API dependencies ([details](docs/ARCHITECTURE.md))
|
|
204
|
-
- **Async requires pycubrid
|
|
205
|
+
- **Async requires pycubrid >= 1.3.2,<2.0** — the `cubrid+aiopycubrid://` driver needs the async-capable pycubrid package line currently supported by this project
|
|
205
206
|
|
|
206
207
|
## Documentation
|
|
207
208
|
|
|
@@ -227,8 +228,8 @@ async with AsyncSession(engine) as session:
|
|
|
227
228
|
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
|
|
228
229
|
| SQLAlchemy | 2.0–2.1 |
|
|
229
230
|
| Alembic | >=1.7 |
|
|
230
|
-
| pycubrid (sync) | >=1.2.0 |
|
|
231
|
-
| pycubrid (async) | >=1.2.0 |
|
|
231
|
+
| pycubrid (sync) | >=1.3.2,<2.0 |
|
|
232
|
+
| pycubrid (async) | >=1.3.2,<2.0 |
|
|
232
233
|
|
|
233
234
|
## FAQ
|
|
234
235
|
|
|
@@ -270,7 +271,7 @@ stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Up
|
|
|
270
271
|
|
|
271
272
|
### Does sqlalchemy-cubrid support async?
|
|
272
273
|
|
|
273
|
-
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.
|
|
274
|
+
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`.
|
|
274
275
|
|
|
275
276
|
|
|
276
277
|
## Related Projects
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
[](https://pypi.org/project/sqlalchemy-cubrid)
|
|
9
9
|
[](https://www.python.org)
|
|
10
10
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml)
|
|
11
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
|
|
11
12
|
[](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
|
|
12
13
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
|
|
13
14
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid)
|
|
@@ -22,12 +23,12 @@
|
|
|
22
23
|
|
|
23
24
|
CUBRID is a high-performance open-source relational database, widely adopted in
|
|
24
25
|
Korean public-sector and enterprise applications. Until now, there was no
|
|
25
|
-
|
|
26
|
+
actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
|
|
26
27
|
|
|
27
28
|
**sqlalchemy-cubrid** bridges that gap:
|
|
28
29
|
|
|
29
30
|
- Full SQLAlchemy 2.0–2.1 dialect with **statement caching** and **PEP 561 typing**
|
|
30
|
-
- **
|
|
31
|
+
- **619 offline tests** with **~98.26% code coverage** — no database required to run them
|
|
31
32
|
- **Concurrency stress tests** — `QueuePool` sync threaded + asyncio.gather workloads validated against live CUBRID
|
|
32
33
|
- **SQLAlchemy 2.2-ready compat shim** — private API access wrapped in `_compat.py` (still pinned `<2.2` until full SA 2.2 validation)
|
|
33
34
|
- Tested against **4 CUBRID versions** (10.2, 11.0, 11.2, 11.4) across **Python 3.10 -- 3.14**
|
|
@@ -142,7 +143,7 @@ async with AsyncSession(engine) as session:
|
|
|
142
143
|
- Schema reflection -- tables, views, columns, PKs, FKs, indexes, unique constraints, comments
|
|
143
144
|
- Alembic migrations via `CubridImpl` (auto-discovered entry point)
|
|
144
145
|
- All 6 CUBRID isolation levels (dual-granularity: class-level + instance-level)
|
|
145
|
-
- Async support
|
|
146
|
+
- Async support — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
|
|
146
147
|
|
|
147
148
|
## Known Limitations
|
|
148
149
|
|
|
@@ -151,7 +152,7 @@ async with AsyncSession(engine) as session:
|
|
|
151
152
|
- **No multi-schema** — single schema per database
|
|
152
153
|
- **DDL auto-commits** — migrations are not transactional (`transactional_ddl = False`)
|
|
153
154
|
- **SQLAlchemy 2.0–2.1 only** — pinned to `<2.2` due to internal API dependencies ([details](docs/ARCHITECTURE.md))
|
|
154
|
-
- **Async requires pycubrid
|
|
155
|
+
- **Async requires pycubrid >= 1.3.2,<2.0** — the `cubrid+aiopycubrid://` driver needs the async-capable pycubrid package line currently supported by this project
|
|
155
156
|
|
|
156
157
|
## Documentation
|
|
157
158
|
|
|
@@ -177,8 +178,8 @@ async with AsyncSession(engine) as session:
|
|
|
177
178
|
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
|
|
178
179
|
| SQLAlchemy | 2.0–2.1 |
|
|
179
180
|
| Alembic | >=1.7 |
|
|
180
|
-
| pycubrid (sync) | >=1.2.0 |
|
|
181
|
-
| pycubrid (async) | >=1.2.0 |
|
|
181
|
+
| pycubrid (sync) | >=1.3.2,<2.0 |
|
|
182
|
+
| pycubrid (async) | >=1.3.2,<2.0 |
|
|
182
183
|
|
|
183
184
|
## FAQ
|
|
184
185
|
|
|
@@ -220,7 +221,7 @@ stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Up
|
|
|
220
221
|
|
|
221
222
|
### Does sqlalchemy-cubrid support async?
|
|
222
223
|
|
|
223
|
-
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.
|
|
224
|
+
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`.
|
|
224
225
|
|
|
225
226
|
|
|
226
227
|
## Related Projects
|
|
@@ -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.2"
|
|
8
8
|
description = "CUBRID dialect for SQLAlchemy"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -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.11",
|
|
47
|
+
"mypy==1.20.1",
|
|
48
48
|
"bandit[toml]==1.9.4",
|
|
49
49
|
"pre-commit",
|
|
50
50
|
"tox",
|
|
@@ -54,7 +54,7 @@ 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]
|
{sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/aio_pycubrid_dialect.py
RENAMED
|
@@ -7,6 +7,7 @@
|
|
|
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 (
|
|
@@ -43,12 +44,15 @@ class AsyncAdapt_pycubrid_connection(AsyncAdapt_dbapi_connection):
|
|
|
43
44
|
def autocommit(self, value: bool) -> None:
|
|
44
45
|
self.await_(self._connection.set_autocommit(value)) # type: ignore[union-attr]
|
|
45
46
|
|
|
47
|
+
def ping(self, reconnect: bool = True) -> bool:
|
|
48
|
+
return self.await_(self._connection.ping(reconnect)) # type: ignore[union-attr]
|
|
49
|
+
|
|
46
50
|
|
|
47
51
|
class AsyncAdapt_pycubrid_dbapi(AsyncAdapt_dbapi_module):
|
|
48
52
|
def __init__(self, aio_module: Any) -> None:
|
|
49
53
|
self._aio_module = aio_module
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
sync_module = import_module("pycubrid")
|
|
52
56
|
|
|
53
57
|
self.paramstyle = sync_module.paramstyle
|
|
54
58
|
self.Error = sync_module.Error
|
|
@@ -86,7 +90,7 @@ class PyCubridAsyncDialect(PyCubridDialect):
|
|
|
86
90
|
|
|
87
91
|
@classmethod
|
|
88
92
|
def import_dbapi(cls) -> DBAPIModule:
|
|
89
|
-
|
|
93
|
+
aio_module = import_module("pycubrid.aio")
|
|
90
94
|
|
|
91
95
|
return cast(DBAPIModule, AsyncAdapt_pycubrid_dbapi(aio_module))
|
|
92
96
|
|
|
@@ -118,13 +122,7 @@ class PyCubridAsyncDialect(PyCubridDialect):
|
|
|
118
122
|
return connect
|
|
119
123
|
|
|
120
124
|
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
|
|
125
|
+
return bool(dbapi_connection.ping(False))
|
|
128
126
|
|
|
129
127
|
|
|
130
128
|
dialect = PyCubridAsyncDialect
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
|
+
from importlib import import_module
|
|
13
14
|
from typing import Any, Callable, cast
|
|
14
15
|
|
|
15
16
|
from sqlalchemy.engine.interfaces import DBAPIConnection, DBAPIModule, ConnectArgsType
|
|
@@ -70,7 +71,7 @@ class PyCubridDialect(CubridDialect):
|
|
|
70
71
|
def import_dbapi(cls) -> DBAPIModule:
|
|
71
72
|
"""Import and return the pycubrid DBAPI module."""
|
|
72
73
|
try:
|
|
73
|
-
|
|
74
|
+
dbapi_module = import_module("pycubrid")
|
|
74
75
|
except ImportError as e:
|
|
75
76
|
raise e
|
|
76
77
|
log.debug("Loaded pycubrid DBAPI (version %s)", getattr(dbapi_module, "__version__", "?"))
|
|
@@ -125,18 +126,8 @@ class PyCubridDialect(CubridDialect):
|
|
|
125
126
|
return connect
|
|
126
127
|
|
|
127
128
|
def do_ping(self, dbapi_connection: DBAPIConnection) -> bool:
|
|
128
|
-
"""Ping
|
|
129
|
-
|
|
130
|
-
pycubrid does not expose a native ``ping()`` method, so we
|
|
131
|
-
execute a lightweight query instead.
|
|
132
|
-
"""
|
|
133
|
-
cursor = dbapi_connection.cursor()
|
|
134
|
-
try:
|
|
135
|
-
cursor.execute("SELECT 1")
|
|
136
|
-
cursor.fetchone()
|
|
137
|
-
finally:
|
|
138
|
-
cursor.close()
|
|
139
|
-
return True
|
|
129
|
+
"""Ping using native pycubrid CHECK_CAS (FC=32). Requires pycubrid>=1.3.2."""
|
|
130
|
+
return bool(dbapi_connection.ping(False))
|
|
140
131
|
|
|
141
132
|
|
|
142
133
|
dialect = PyCubridDialect
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlalchemy-cubrid
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
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
|
|
@@ -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.11; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy==1.20.1; 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
|
|
@@ -58,6 +58,7 @@ Dynamic: license-file
|
|
|
58
58
|
[](https://pypi.org/project/sqlalchemy-cubrid)
|
|
59
59
|
[](https://www.python.org)
|
|
60
60
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml)
|
|
61
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/integration-full.yml)
|
|
61
62
|
[](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
|
|
62
63
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
|
|
63
64
|
[](https://github.com/cubrid-labs/sqlalchemy-cubrid)
|
|
@@ -72,12 +73,12 @@ Dynamic: license-file
|
|
|
72
73
|
|
|
73
74
|
CUBRID is a high-performance open-source relational database, widely adopted in
|
|
74
75
|
Korean public-sector and enterprise applications. Until now, there was no
|
|
75
|
-
|
|
76
|
+
actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
|
|
76
77
|
|
|
77
78
|
**sqlalchemy-cubrid** bridges that gap:
|
|
78
79
|
|
|
79
80
|
- Full SQLAlchemy 2.0–2.1 dialect with **statement caching** and **PEP 561 typing**
|
|
80
|
-
- **
|
|
81
|
+
- **619 offline tests** with **~98.26% code coverage** — no database required to run them
|
|
81
82
|
- **Concurrency stress tests** — `QueuePool` sync threaded + asyncio.gather workloads validated against live CUBRID
|
|
82
83
|
- **SQLAlchemy 2.2-ready compat shim** — private API access wrapped in `_compat.py` (still pinned `<2.2` until full SA 2.2 validation)
|
|
83
84
|
- Tested against **4 CUBRID versions** (10.2, 11.0, 11.2, 11.4) across **Python 3.10 -- 3.14**
|
|
@@ -192,7 +193,7 @@ async with AsyncSession(engine) as session:
|
|
|
192
193
|
- Schema reflection -- tables, views, columns, PKs, FKs, indexes, unique constraints, comments
|
|
193
194
|
- Alembic migrations via `CubridImpl` (auto-discovered entry point)
|
|
194
195
|
- All 6 CUBRID isolation levels (dual-granularity: class-level + instance-level)
|
|
195
|
-
- Async support
|
|
196
|
+
- Async support — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
|
|
196
197
|
|
|
197
198
|
## Known Limitations
|
|
198
199
|
|
|
@@ -201,7 +202,7 @@ async with AsyncSession(engine) as session:
|
|
|
201
202
|
- **No multi-schema** — single schema per database
|
|
202
203
|
- **DDL auto-commits** — migrations are not transactional (`transactional_ddl = False`)
|
|
203
204
|
- **SQLAlchemy 2.0–2.1 only** — pinned to `<2.2` due to internal API dependencies ([details](docs/ARCHITECTURE.md))
|
|
204
|
-
- **Async requires pycubrid
|
|
205
|
+
- **Async requires pycubrid >= 1.3.2,<2.0** — the `cubrid+aiopycubrid://` driver needs the async-capable pycubrid package line currently supported by this project
|
|
205
206
|
|
|
206
207
|
## Documentation
|
|
207
208
|
|
|
@@ -227,8 +228,8 @@ async with AsyncSession(engine) as session:
|
|
|
227
228
|
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
|
|
228
229
|
| SQLAlchemy | 2.0–2.1 |
|
|
229
230
|
| Alembic | >=1.7 |
|
|
230
|
-
| pycubrid (sync) | >=1.2.0 |
|
|
231
|
-
| pycubrid (async) | >=1.2.0 |
|
|
231
|
+
| pycubrid (sync) | >=1.3.2,<2.0 |
|
|
232
|
+
| pycubrid (async) | >=1.3.2,<2.0 |
|
|
232
233
|
|
|
233
234
|
## FAQ
|
|
234
235
|
|
|
@@ -270,7 +271,7 @@ stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Up
|
|
|
270
271
|
|
|
271
272
|
### Does sqlalchemy-cubrid support async?
|
|
272
273
|
|
|
273
|
-
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.
|
|
274
|
+
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`.
|
|
274
275
|
|
|
275
276
|
|
|
276
277
|
## Related Projects
|
|
@@ -10,12 +10,12 @@ CUBRID-Python
|
|
|
10
10
|
pytest>=7.0
|
|
11
11
|
pytest-cov
|
|
12
12
|
pytest-asyncio
|
|
13
|
-
ruff==0.15.
|
|
14
|
-
mypy==1.
|
|
13
|
+
ruff==0.15.11
|
|
14
|
+
mypy==1.20.1
|
|
15
15
|
bandit[toml]==1.9.4
|
|
16
16
|
pre-commit
|
|
17
17
|
tox
|
|
18
18
|
alembic>=1.7
|
|
19
19
|
|
|
20
20
|
[pycubrid]
|
|
21
|
-
pycubrid<2.0,>=1.2
|
|
21
|
+
pycubrid<2.0,>=1.3.2
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
import types
|
|
5
|
+
from typing import Any, cast
|
|
5
6
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
6
7
|
|
|
7
8
|
from sqlalchemy.engine import url
|
|
@@ -43,7 +44,7 @@ class TestPyCubridAsyncDialectImportDbapi:
|
|
|
43
44
|
def test_import_dbapi_returns_async_adapt_module(self):
|
|
44
45
|
fake_aio = types.ModuleType("pycubrid.aio")
|
|
45
46
|
fake_sync = types.ModuleType("pycubrid")
|
|
46
|
-
fake_sync.paramstyle = "qmark"
|
|
47
|
+
cast(Any, fake_sync).paramstyle = "qmark"
|
|
47
48
|
for attr in [
|
|
48
49
|
"Error",
|
|
49
50
|
"OperationalError",
|
|
@@ -163,6 +164,20 @@ class TestAsyncAdaptPycubridConnection:
|
|
|
163
164
|
|
|
164
165
|
assert isinstance(cur, AsyncAdapt_pycubrid_cursor)
|
|
165
166
|
|
|
167
|
+
def test_ping_awaits_underlying_async_ping(self):
|
|
168
|
+
mock_dbapi = MagicMock()
|
|
169
|
+
mock_async_conn = MagicMock()
|
|
170
|
+
mock_async_conn.ping.return_value = object()
|
|
171
|
+
|
|
172
|
+
conn = AsyncAdapt_pycubrid_connection(mock_dbapi, mock_async_conn)
|
|
173
|
+
|
|
174
|
+
with patch.object(conn, "await_", return_value=False) as mock_await:
|
|
175
|
+
result = conn.ping(False)
|
|
176
|
+
|
|
177
|
+
assert result is False
|
|
178
|
+
mock_async_conn.ping.assert_called_once_with(False)
|
|
179
|
+
mock_await.assert_called_once_with(mock_async_conn.ping.return_value)
|
|
180
|
+
|
|
166
181
|
|
|
167
182
|
class TestAsyncAdaptPycubridCursor:
|
|
168
183
|
def test_setinputsizes_is_noop(self):
|
|
@@ -192,15 +207,12 @@ class TestAsyncAdaptPycubridCursor:
|
|
|
192
207
|
|
|
193
208
|
|
|
194
209
|
class TestPyCubridAsyncDialectDoPing:
|
|
195
|
-
def
|
|
210
|
+
def test_do_ping_propagates_boolean(self):
|
|
196
211
|
dialect = PyCubridAsyncDialect()
|
|
197
212
|
mock_conn = MagicMock()
|
|
198
|
-
|
|
199
|
-
mock_conn.cursor.return_value = mock_cursor
|
|
213
|
+
mock_conn.ping.return_value = False
|
|
200
214
|
|
|
201
215
|
result = dialect.do_ping(mock_conn)
|
|
202
216
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
mock_cursor.close.assert_called_once()
|
|
206
|
-
assert result is True
|
|
217
|
+
mock_conn.ping.assert_called_once_with(False)
|
|
218
|
+
assert result is False
|
|
@@ -56,16 +56,10 @@ class TestPyCubridDialectBasics:
|
|
|
56
56
|
assert imported is fake_module
|
|
57
57
|
|
|
58
58
|
def test_import_dbapi_import_error(self):
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _fake_import(name, *args, **kwargs):
|
|
64
|
-
if name == "pycubrid":
|
|
65
|
-
raise ImportError("pycubrid not installed")
|
|
66
|
-
return real_import(name, *args, **kwargs)
|
|
67
|
-
|
|
68
|
-
with patch("builtins.__import__", side_effect=_fake_import):
|
|
59
|
+
with patch(
|
|
60
|
+
"sqlalchemy_cubrid.pycubrid_dialect.import_module",
|
|
61
|
+
side_effect=ImportError("pycubrid not installed"),
|
|
62
|
+
):
|
|
69
63
|
with pytest.raises(ImportError, match="pycubrid not installed"):
|
|
70
64
|
PyCubridDialect.import_dbapi()
|
|
71
65
|
|
|
@@ -154,31 +148,25 @@ class TestPyCubridOnConnect:
|
|
|
154
148
|
|
|
155
149
|
|
|
156
150
|
class TestPyCubridDoPing:
|
|
157
|
-
def
|
|
151
|
+
def test_do_ping_propagates_true(self):
|
|
158
152
|
dialect = PyCubridDialect()
|
|
159
153
|
dbapi_conn = MagicMock()
|
|
160
|
-
|
|
161
|
-
cursor.fetchone.return_value = (1,)
|
|
162
|
-
dbapi_conn.cursor.return_value = cursor
|
|
154
|
+
dbapi_conn.ping.return_value = True
|
|
163
155
|
|
|
164
156
|
result = dialect.do_ping(dbapi_conn)
|
|
165
157
|
|
|
166
158
|
assert result is True
|
|
167
|
-
|
|
168
|
-
cursor.fetchone.assert_called_once()
|
|
169
|
-
cursor.close.assert_called_once()
|
|
159
|
+
dbapi_conn.ping.assert_called_once_with(False)
|
|
170
160
|
|
|
171
|
-
def
|
|
161
|
+
def test_do_ping_propagates_false(self):
|
|
172
162
|
dialect = PyCubridDialect()
|
|
173
163
|
dbapi_conn = MagicMock()
|
|
174
|
-
|
|
175
|
-
cursor.execute.side_effect = RuntimeError("connection lost")
|
|
176
|
-
dbapi_conn.cursor.return_value = cursor
|
|
164
|
+
dbapi_conn.ping.return_value = False
|
|
177
165
|
|
|
178
|
-
|
|
179
|
-
dialect.do_ping(dbapi_conn)
|
|
166
|
+
result = dialect.do_ping(dbapi_conn)
|
|
180
167
|
|
|
181
|
-
|
|
168
|
+
assert result is False
|
|
169
|
+
dbapi_conn.ping.assert_called_once_with(False)
|
|
182
170
|
|
|
183
171
|
|
|
184
172
|
class TestPyCubridExecutionContext:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|