sqlalchemy-dqlite 0.1.1__tar.gz → 0.1.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 (20) hide show
  1. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/PKG-INFO +1 -1
  2. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/pyproject.toml +1 -1
  3. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/src/sqlalchemydqlite/__init__.py +1 -1
  4. sqlalchemy_dqlite-0.1.2/src/sqlalchemydqlite/aio.py +199 -0
  5. sqlalchemy_dqlite-0.1.1/src/sqlalchemydqlite/aio.py +0 -57
  6. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/.github/workflows/publish-to-pypi.yml +0 -0
  7. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/.gitignore +0 -0
  8. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/DEVELOPMENT.md +0 -0
  9. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/LICENSE.md +0 -0
  10. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/README.md +0 -0
  11. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/src/sqlalchemydqlite/base.py +0 -0
  12. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/src/sqlalchemydqlite/py.typed +0 -0
  13. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/src/sqlalchemydqlite/requirements.py +0 -0
  14. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/tests/__init__.py +0 -0
  15. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/tests/conftest.py +0 -0
  16. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/tests/integration/__init__.py +0 -0
  17. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/tests/integration/conftest.py +0 -0
  18. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/tests/integration/test_async_context.py +0 -0
  19. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/tests/integration/test_orm_operations.py +0 -0
  20. {sqlalchemy_dqlite-0.1.1 → sqlalchemy_dqlite-0.1.2}/tests/test_dialect.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlalchemy-dqlite
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: SQLAlchemy 2.0 dialect for dqlite distributed SQLite
5
5
  Project-URL: Homepage, https://github.com/antoineleclair/sqlalchemy-dqlite
6
6
  Project-URL: Repository, https://github.com/antoineleclair/sqlalchemy-dqlite
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sqlalchemy-dqlite"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "SQLAlchemy 2.0 dialect for dqlite distributed SQLite"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -4,4 +4,4 @@ from sqlalchemydqlite.base import DqliteDialect
4
4
 
5
5
  __all__ = ["DqliteDialect"]
6
6
 
7
- __version__ = "0.1.1"
7
+ __version__ = "0.1.2"
@@ -0,0 +1,199 @@
1
+ """Async dqlite dialect for SQLAlchemy."""
2
+
3
+ from collections import deque
4
+ from collections.abc import Sequence
5
+ from typing import Any
6
+
7
+ from sqlalchemy import pool
8
+ from sqlalchemy.engine import URL, AdaptedConnection
9
+ from sqlalchemy.engine.interfaces import DBAPIConnection
10
+ from sqlalchemy.pool import AsyncAdaptedQueuePool
11
+ from sqlalchemy.util import await_only
12
+
13
+ from sqlalchemydqlite.base import DqliteDialect
14
+
15
+
16
+ class AsyncAdaptedCursor:
17
+ """Adapts an AsyncCursor for SQLAlchemy's greenlet-based async engine.
18
+
19
+ Eagerly fetches all rows during execute() within the greenlet context,
20
+ then serves fetch* calls synchronously from the buffer. This matches
21
+ the pattern used by SQLAlchemy's aiosqlite dialect.
22
+ """
23
+
24
+ server_side = False
25
+
26
+ def __init__(self, adapt_connection: "AsyncAdaptedConnection") -> None:
27
+ self._adapt_connection = adapt_connection
28
+ self._connection = adapt_connection._connection
29
+ self.description: Any = None
30
+ self.rowcount: int = -1
31
+ self.lastrowid: int | None = None
32
+ self.arraysize: int = 1
33
+ self._rows: deque[Any] = deque()
34
+
35
+ async def _async_soft_close(self) -> None:
36
+ return
37
+
38
+ def close(self) -> None:
39
+ self._rows.clear()
40
+
41
+ def execute(self, operation: str, parameters: Any = None) -> Any:
42
+ cursor = self._connection.cursor()
43
+ if parameters is not None:
44
+ await_only(cursor.execute(operation, parameters))
45
+ else:
46
+ await_only(cursor.execute(operation))
47
+
48
+ if cursor.description:
49
+ self.description = cursor.description
50
+ self.lastrowid = self.rowcount = -1
51
+ self._rows = deque(await_only(cursor.fetchall()))
52
+ else:
53
+ self.description = None
54
+ self.lastrowid = cursor.lastrowid
55
+ self.rowcount = cursor.rowcount
56
+
57
+ await_only(cursor.close())
58
+
59
+ def executemany(self, operation: str, seq_of_parameters: Any) -> Any:
60
+ cursor = self._connection.cursor()
61
+ await_only(cursor.executemany(operation, seq_of_parameters))
62
+ self.description = None
63
+ self.lastrowid = cursor.lastrowid
64
+ self.rowcount = cursor.rowcount
65
+ await_only(cursor.close())
66
+
67
+ def fetchone(self) -> Any:
68
+ if self._rows:
69
+ return self._rows.popleft()
70
+ return None
71
+
72
+ def fetchmany(self, size: int | None = None) -> Sequence[Any]:
73
+ if size is None:
74
+ size = self.arraysize
75
+ return [self._rows.popleft() for _ in range(min(size, len(self._rows)))]
76
+
77
+ def fetchall(self) -> Sequence[Any]:
78
+ retval = list(self._rows)
79
+ self._rows.clear()
80
+ return retval
81
+
82
+ def setinputsizes(self, *inputsizes: Any) -> None:
83
+ pass
84
+
85
+ def setoutputsize(self, size: int, column: int | None = None) -> None:
86
+ pass
87
+
88
+ def __iter__(self) -> Any:
89
+ while self._rows:
90
+ yield self._rows.popleft()
91
+
92
+ def __next__(self) -> Any:
93
+ row = self.fetchone()
94
+ if row is None:
95
+ raise StopIteration
96
+ return row
97
+
98
+
99
+ class AsyncAdaptedConnection(AdaptedConnection):
100
+ """Adapts an AsyncConnection for SQLAlchemy's greenlet-based async engine.
101
+
102
+ Provides sync-looking methods that internally use await_only() to
103
+ bridge to the underlying async connection within SQLAlchemy's
104
+ greenlet context.
105
+ """
106
+
107
+ def __init__(self, connection: Any) -> None:
108
+ self._connection = connection
109
+
110
+ def cursor(self) -> AsyncAdaptedCursor:
111
+ return AsyncAdaptedCursor(self)
112
+
113
+ def commit(self) -> None:
114
+ await_only(self._connection.commit())
115
+
116
+ def rollback(self) -> None:
117
+ await_only(self._connection.rollback())
118
+
119
+ def close(self) -> None:
120
+ await_only(self._connection.close())
121
+
122
+
123
+ class DqliteDialect_aio(DqliteDialect): # noqa: N801
124
+ """Async SQLAlchemy dialect for dqlite.
125
+
126
+ Use with SQLAlchemy's async engine:
127
+ create_async_engine("dqlite+aio://host:port/database")
128
+ """
129
+
130
+ driver = "dqlitedbapi_aio"
131
+ is_async = True
132
+ supports_statement_cache = True
133
+
134
+ @classmethod
135
+ def get_pool_class(cls, url: URL) -> type[pool.Pool]:
136
+ return AsyncAdaptedQueuePool
137
+
138
+ @classmethod
139
+ def import_dbapi(cls) -> Any:
140
+ from dqlitedbapi import aio
141
+
142
+ return aio
143
+
144
+ def connect(self, *cargs: Any, **cparams: Any) -> Any:
145
+ """Create and wrap an async connection."""
146
+ raw_conn = self.loaded_dbapi.connect(*cargs, **cparams)
147
+ return AsyncAdaptedConnection(raw_conn)
148
+
149
+ def create_connect_args(self, url: URL) -> tuple[list[Any], dict[str, Any]]:
150
+ """Create connection arguments from URL.
151
+
152
+ URL format: dqlite+aio://host:port/database
153
+ """
154
+ host = url.host or "localhost"
155
+ port = url.port or 9001
156
+ database = url.database or "default"
157
+
158
+ address = f"{host}:{port}"
159
+
160
+ return [], {
161
+ "address": address,
162
+ "database": database,
163
+ }
164
+
165
+ def do_rollback(self, dbapi_connection: DBAPIConnection) -> None:
166
+ """Rollback the current transaction."""
167
+ try:
168
+ dbapi_connection.rollback()
169
+ except Exception as e:
170
+ if "no transaction is active" not in str(e):
171
+ raise
172
+
173
+ def do_commit(self, dbapi_connection: DBAPIConnection) -> None:
174
+ """Commit the current transaction."""
175
+ try:
176
+ dbapi_connection.commit()
177
+ except Exception as e:
178
+ if "no transaction is active" not in str(e):
179
+ raise
180
+
181
+ def _get_server_version_info(self, connection: Any) -> tuple[int, ...]:
182
+ """Return the server version as a tuple."""
183
+ cursor = connection.connection.dbapi_connection.cursor()
184
+ cursor.execute("SELECT sqlite_version()")
185
+ row = cursor.fetchone()
186
+ cursor.close()
187
+
188
+ if row:
189
+ version_str = row[0]
190
+ return tuple(int(x) for x in version_str.split("."))
191
+ return (3, 0, 0)
192
+
193
+ def get_driver_connection(self, connection: Any) -> Any:
194
+ """Return the driver-level connection."""
195
+ return connection
196
+
197
+
198
+ # Register the dialect
199
+ dialect = DqliteDialect_aio
@@ -1,57 +0,0 @@
1
- """Async dqlite dialect for SQLAlchemy."""
2
-
3
- from typing import Any
4
-
5
- from sqlalchemy import pool
6
- from sqlalchemy.dialects.sqlite.base import SQLiteDialect
7
- from sqlalchemy.engine import URL
8
- from sqlalchemy.pool import AsyncAdaptedQueuePool
9
-
10
-
11
- class DqliteDialect_aio(SQLiteDialect): # noqa: N801
12
- """Async SQLAlchemy dialect for dqlite.
13
-
14
- Use with SQLAlchemy's async engine:
15
- create_async_engine("dqlite+aio://host:port/database")
16
- """
17
-
18
- name = "dqlite"
19
- driver = "dqlitedbapi_aio"
20
- is_async = True
21
-
22
- # dqlite uses qmark parameter style
23
- paramstyle = "qmark"
24
-
25
- @classmethod
26
- def get_pool_class(cls, url: URL) -> type[pool.Pool]:
27
- return AsyncAdaptedQueuePool
28
-
29
- @classmethod
30
- def import_dbapi(cls) -> Any:
31
- from dqlitedbapi import aio
32
-
33
- return aio
34
-
35
- def create_connect_args(self, url: URL) -> tuple[list[Any], dict[str, Any]]:
36
- """Create connection arguments from URL.
37
-
38
- URL format: dqlite+aio://host:port/database
39
- """
40
- host = url.host or "localhost"
41
- port = url.port or 9001
42
- database = url.database or "default"
43
-
44
- address = f"{host}:{port}"
45
-
46
- return [], {
47
- "address": address,
48
- "database": database,
49
- }
50
-
51
- def get_driver_connection(self, connection: Any) -> Any:
52
- """Return the driver-level connection."""
53
- return connection
54
-
55
-
56
- # Register the dialect
57
- dialect = DqliteDialect_aio