Skip to content

Commit eee2b82

Browse files
Tighten sqlalchemy-dqlite packaging, slots, and requirements fences
Four small hygiene changes: - Add Framework :: AsyncIO trove classifier. The dialect ships an async surface (create_async_engine("dqlite+aio://...") via the ``dqlite.aio`` entry point); sibling packages already advertise the classifier and sqlalchemy-dqlite was an asymmetric omission. - Declare ``__slots__ = ()`` on AsyncAdaptedConnection. The parent SA AdaptedConnection uses ``__slots__ = ("_connection",)``; a subclass without slots gets a ``__dict__`` per instance, defeating the parent's memory layout. SA's own aiosqlite adapter follows the same slot pattern. - Extend test_properties_return_exclusion_objects to cover all 23 overridden Requirements properties (was 12/23). A regression to bare True/False on any of cte / window_functions / returning / insert_from_select / on_update_or_delete_cascades / the ``_reflection`` family / temporary_tables / table_ddl_if_exists is now fenced — matching the policy ISSUE-132/133 established. - Annotate set_isolation_level's AUTOCOMMIT and warn branches as belt-and-suspenders for third-party callers. SA's engine flow validates via get_isolation_level_values first and rejects unknown levels upstream, so those branches are unreachable for engine-driven paths; keep them for non-engine callers and make the reachability note explicit.
1 parent 319b148 commit eee2b82

5 files changed

Lines changed: 51 additions & 0 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ authors = [{ name = "Antoine Leclair", email = "antoineleclair@gmail.com" }]
1313
keywords = ["dqlite", "sqlite", "distributed", "database", "sqlalchemy", "orm"]
1414
classifiers = [
1515
"Development Status :: 3 - Alpha",
16+
"Framework :: AsyncIO",
1617
"Intended Audience :: Developers",
1718
"License :: OSI Approved :: MIT License",
1819
"Operating System :: OS Independent",

src/sqlalchemydqlite/aio.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ class AsyncAdaptedConnection(AdaptedConnection):
209209
greenlet context.
210210
"""
211211

212+
# Parent ``sqlalchemy.engine.interfaces.AdaptedConnection`` declares
213+
# ``__slots__ = ("_connection",)``; without our own slots declaration
214+
# each instance gets a ``__dict__`` and defeats the parent's memory
215+
# optimization (SA's own ``AsyncAdapt_aiosqlite_connection`` follows
216+
# the same pattern). We add no new instance attributes, so an empty
217+
# slots tuple is correct.
218+
__slots__ = ()
219+
212220
def __init__(self, connection: "AsyncConnection") -> None:
213221
# ``_connection`` is the concrete ``dqlitedbapi.aio.AsyncConnection``
214222
# this adapter wraps; SQLAlchemy's parent ``AdaptedConnection``

src/sqlalchemydqlite/base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,17 @@ def set_isolation_level(self, dbapi_connection: DBAPIConnection, level: str | No
286286
to lose transactionality without knowing it. Other unsupported
287287
levels emit a warning (future-proof for isolation levels dqlite
288288
may grow to support).
289+
290+
Note on reachability: SA's engine flow
291+
(``engine/default.py::_assert_and_set_isolation_level``) calls
292+
``get_isolation_level_values()`` first and rejects unknown
293+
values with ``ArgumentError`` before reaching this method, so
294+
the AUTOCOMMIT and warning branches are effectively dead for
295+
engine-driven callers. They are kept as belt-and-suspenders
296+
for third-party callers (test harnesses, custom engine
297+
implementations) that bypass SA's upstream validation, and to
298+
provide an explicit error message if the guarantees above
299+
ever change.
289300
"""
290301
if level is None or level == "SERIALIZABLE":
291302
return

tests/test_adapter_slots.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""``AsyncAdaptedConnection`` must declare ``__slots__`` to preserve
2+
the memory layout of SA's parent ``AdaptedConnection`` (which uses
3+
slots for every pooled connection). Without slots each instance
4+
silently gets a ``__dict__``, defeating the optimization.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from unittest.mock import MagicMock
10+
11+
import pytest
12+
13+
from sqlalchemydqlite.aio import AsyncAdaptedConnection
14+
15+
16+
def test_async_adapted_connection_has_slots() -> None:
17+
conn = AsyncAdaptedConnection(MagicMock())
18+
with pytest.raises(AttributeError):
19+
conn.__dict__ # noqa: B018

tests/test_requirements.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ def test_properties_return_exclusion_objects(self) -> None:
2020
"emulated_lastrowid",
2121
"supports_empty_inserts",
2222
"regexp_match",
23+
"cte",
24+
"window_functions",
25+
"returning",
26+
"insert_from_select",
27+
"on_update_or_delete_cascades",
28+
"self_referential_foreign_keys",
29+
"unique_constraint_reflection",
30+
"primary_key_constraint_reflection",
31+
"foreign_key_constraint_reflection",
32+
"index_reflection",
33+
"temporary_tables",
34+
"table_ddl_if_exists",
2335
]
2436
for prop_name in properties:
2537
value = getattr(req, prop_name)

0 commit comments

Comments
 (0)