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.
Files changed (43) hide show
  1. {sqlalchemy_cubrid-1.4.0/sqlalchemy_cubrid.egg-info → sqlalchemy_cubrid-1.4.2}/PKG-INFO +12 -11
  2. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/README.md +8 -7
  3. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/pyproject.toml +4 -4
  4. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/__init__.py +1 -1
  5. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/aio_pycubrid_dialect.py +7 -9
  6. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/pycubrid_dialect.py +4 -13
  7. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2/sqlalchemy_cubrid.egg-info}/PKG-INFO +12 -11
  8. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/requires.txt +3 -3
  9. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_aio_pycubrid_dialect.py +20 -8
  10. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_pycubrid_dialect.py +12 -24
  11. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/AUTHORS +0 -0
  12. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/LICENSE +0 -0
  13. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/setup.cfg +0 -0
  14. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/_compat.py +0 -0
  15. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/alembic_impl.py +0 -0
  16. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/base.py +0 -0
  17. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/compiler.py +0 -0
  18. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/dialect.py +0 -0
  19. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/dml.py +0 -0
  20. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/py.typed +0 -0
  21. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/requirements.py +0 -0
  22. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/trace.py +0 -0
  23. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid/types.py +0 -0
  24. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/SOURCES.txt +0 -0
  25. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/dependency_links.txt +0 -0
  26. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/entry_points.txt +0 -0
  27. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/sqlalchemy_cubrid.egg-info/top_level.txt +0 -0
  28. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_aio_integration.py +0 -0
  29. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_alembic.py +0 -0
  30. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_base.py +0 -0
  31. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_compiler.py +0 -0
  32. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_dialect_offline.py +0 -0
  33. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_dialects.py +0 -0
  34. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_integration.py +0 -0
  35. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_json.py +0 -0
  36. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_logging.py +0 -0
  37. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_packaging.py +0 -0
  38. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_requirements.py +0 -0
  39. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_show_create_table.py +0 -0
  40. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_stress_pool.py +0 -0
  41. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_suite.py +0 -0
  42. {sqlalchemy_cubrid-1.4.0 → sqlalchemy_cubrid-1.4.2}/test/test_trace.py +0 -0
  43. {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.0
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.8; extra == "dev"
40
- Requires-Dist: mypy==1.19.1; extra == "dev"
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.0; extra == "pycubrid"
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
  [![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
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)
61
62
  [![coverage](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid/branch/main/graph/badge.svg)](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
62
63
  [![license](https://img.shields.io/github/license/cubrid-labs/sqlalchemy-cubrid)](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
63
64
  [![GitHub stars](https://img.shields.io/github/stars/cubrid-labs/sqlalchemy-cubrid)](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
- production-ready SQLAlchemy dialect that supports the modern 2.0–2.1 API.
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
- - **597 offline tests** with **99%+ code coverage** — no database required to run them
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 (available since v1.1.0) — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
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 1.1.0** — the `cubrid+aiopycubrid://` driver needs the async-capable 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.1.0`. All Core and ORM features work with `AsyncSession`.
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
  [![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
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)
11
12
  [![coverage](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid/branch/main/graph/badge.svg)](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
12
13
  [![license](https://img.shields.io/github/license/cubrid-labs/sqlalchemy-cubrid)](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
13
14
  [![GitHub stars](https://img.shields.io/github/stars/cubrid-labs/sqlalchemy-cubrid)](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
- production-ready SQLAlchemy dialect that supports the modern 2.0–2.1 API.
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
- - **597 offline tests** with **99%+ code coverage** — no database required to run them
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 (available since v1.1.0) — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
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 1.1.0** — the `cubrid+aiopycubrid://` driver needs the async-capable 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.1.0`. All Core and ORM features work with `AsyncSession`.
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.0"
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.8",
47
- "mypy==1.19.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.0,<2.0",
57
+ "pycubrid>=1.3.2,<2.0",
58
58
  ]
59
59
 
60
60
  [project.urls]
@@ -52,7 +52,7 @@ from sqlalchemy.sql.sqltypes import (
52
52
  TIMESTAMP,
53
53
  )
54
54
 
55
- __version__ = "1.4.0"
55
+ __version__ = "1.4.2"
56
56
 
57
57
  __all__ = (
58
58
  "insert",
@@ -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
- import pycubrid as sync_module
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
- import pycubrid.aio as aio_module
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
- cursor = dbapi_connection.cursor()
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
- import pycubrid as dbapi_module
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 the server to check connection liveness.
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.0
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.8; extra == "dev"
40
- Requires-Dist: mypy==1.19.1; extra == "dev"
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.0; extra == "pycubrid"
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
  [![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
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)
61
62
  [![coverage](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid/branch/main/graph/badge.svg)](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
62
63
  [![license](https://img.shields.io/github/license/cubrid-labs/sqlalchemy-cubrid)](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
63
64
  [![GitHub stars](https://img.shields.io/github/stars/cubrid-labs/sqlalchemy-cubrid)](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
- production-ready SQLAlchemy dialect that supports the modern 2.0–2.1 API.
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
- - **597 offline tests** with **99%+ code coverage** — no database required to run them
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 (available since v1.1.0) — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
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 1.1.0** — the `cubrid+aiopycubrid://` driver needs the async-capable 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.1.0`. All Core and ORM features work with `AsyncSession`.
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.8
14
- mypy==1.19.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.0
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" # type: ignore[attr-defined]
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 test_do_ping_executes_select_1(self):
210
+ def test_do_ping_propagates_boolean(self):
196
211
  dialect = PyCubridAsyncDialect()
197
212
  mock_conn = MagicMock()
198
- mock_cursor = MagicMock()
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
- mock_cursor.execute.assert_called_once_with("SELECT 1")
204
- mock_cursor.fetchone.assert_called_once()
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
- import builtins
60
-
61
- real_import = builtins.__import__
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 test_do_ping_success(self):
151
+ def test_do_ping_propagates_true(self):
158
152
  dialect = PyCubridDialect()
159
153
  dbapi_conn = MagicMock()
160
- cursor = MagicMock()
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
- cursor.execute.assert_called_once_with("SELECT 1")
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 test_do_ping_always_closes_cursor(self):
161
+ def test_do_ping_propagates_false(self):
172
162
  dialect = PyCubridDialect()
173
163
  dbapi_conn = MagicMock()
174
- cursor = MagicMock()
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
- with pytest.raises(RuntimeError, match="connection lost"):
179
- dialect.do_ping(dbapi_conn)
166
+ result = dialect.do_ping(dbapi_conn)
180
167
 
181
- cursor.close.assert_called_once()
168
+ assert result is False
169
+ dbapi_conn.ping.assert_called_once_with(False)
182
170
 
183
171
 
184
172
  class TestPyCubridExecutionContext: