Skip to content

Commit 498a3bc

Browse files
Improve compatibility with SQLAlchemy
1 parent f4aac90 commit 498a3bc

File tree

3 files changed

+79
-22
lines changed

3 files changed

+79
-22
lines changed

src/dqlitedbapi/aio/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
from dqlitedbapi.aio.connection import AsyncConnection
44
from dqlitedbapi.aio.cursor import AsyncCursor
5+
from dqlitedbapi.exceptions import (
6+
DatabaseError,
7+
DataError,
8+
Error,
9+
IntegrityError,
10+
InterfaceError,
11+
InternalError,
12+
NotSupportedError,
13+
OperationalError,
14+
ProgrammingError,
15+
Warning,
16+
)
517

618
# PEP 249 module-level attributes (required by SQLAlchemy dialect initialization)
719
apilevel = "2.0"
@@ -13,15 +25,53 @@
1325
sqlite_version = "3.35.0"
1426

1527
__all__ = [
28+
# Module attributes
1629
"apilevel",
1730
"threadsafety",
1831
"paramstyle",
32+
# Functions
33+
"connect",
1934
"aconnect",
35+
# Classes
2036
"AsyncConnection",
2137
"AsyncCursor",
38+
# Exceptions
39+
"Warning",
40+
"Error",
41+
"InterfaceError",
42+
"DatabaseError",
43+
"DataError",
44+
"OperationalError",
45+
"IntegrityError",
46+
"InternalError",
47+
"ProgrammingError",
48+
"NotSupportedError",
2249
]
2350

2451

52+
def connect(
53+
address: str,
54+
*,
55+
database: str = "default",
56+
timeout: float = 10.0,
57+
) -> AsyncConnection:
58+
"""Create a dqlite connection (connects lazily on first use).
59+
60+
This is a sync function that returns an AsyncConnection without
61+
establishing the TCP connection yet. SQLAlchemy requires connect()
62+
to be sync; the actual connection is made when the first query runs.
63+
64+
Args:
65+
address: Node address in "host:port" format
66+
database: Database name to open
67+
timeout: Connection timeout in seconds
68+
69+
Returns:
70+
An AsyncConnection object
71+
"""
72+
return AsyncConnection(address, database=database, timeout=timeout)
73+
74+
2575
async def aconnect(
2676
address: str,
2777
*,
@@ -30,13 +80,15 @@ async def aconnect(
3080
) -> AsyncConnection:
3181
"""Connect to a dqlite database asynchronously.
3282
83+
Unlike connect(), this awaits the TCP connection before returning.
84+
3385
Args:
3486
address: Node address in "host:port" format
3587
database: Database name to open
3688
timeout: Connection timeout in seconds
3789
3890
Returns:
39-
An AsyncConnection object
91+
A connected AsyncConnection object
4092
"""
4193
conn = AsyncConnection(address, database=database, timeout=timeout)
4294
await conn.connect()

src/dqlitedbapi/aio/connection.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,28 @@ def __init__(
3030
self._async_conn: DqliteConnection | None = None
3131
self._closed = False
3232

33-
async def connect(self) -> None:
34-
"""Establish the connection."""
33+
async def _ensure_connection(self) -> DqliteConnection:
34+
"""Ensure the underlying connection is established."""
3535
if self._closed:
3636
raise InterfaceError("Connection is closed")
3737

38-
if self._async_conn is not None:
39-
return
40-
41-
self._async_conn = DqliteConnection(
42-
self._address,
43-
database=self._database,
44-
timeout=self._timeout,
45-
)
46-
try:
47-
await self._async_conn.connect()
48-
except Exception as e:
49-
self._async_conn = None
50-
raise OperationalError(f"Failed to connect: {e}") from e
38+
if self._async_conn is None:
39+
self._async_conn = DqliteConnection(
40+
self._address,
41+
database=self._database,
42+
timeout=self._timeout,
43+
)
44+
try:
45+
await self._async_conn.connect()
46+
except Exception as e:
47+
self._async_conn = None
48+
raise OperationalError(f"Failed to connect: {e}") from e
49+
50+
return self._async_conn
51+
52+
async def connect(self) -> None:
53+
"""Establish the connection."""
54+
await self._ensure_connection()
5155

5256
async def close(self) -> None:
5357
"""Close the connection."""
@@ -72,8 +76,12 @@ async def rollback(self) -> None:
7276
if self._async_conn is not None:
7377
await self._async_conn.execute("ROLLBACK")
7478

75-
async def cursor(self) -> AsyncCursor:
76-
"""Return a new AsyncCursor object."""
79+
def cursor(self) -> AsyncCursor:
80+
"""Return a new AsyncCursor object.
81+
82+
This is intentionally sync — SQLAlchemy calls cursor() from
83+
sync context within its greenlet-based async adapter.
84+
"""
7785
if self._closed:
7886
raise InterfaceError("Connection is closed")
7987
return AsyncCursor(self)

src/dqlitedbapi/aio/cursor.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,7 @@ async def execute(
5858
"""Execute a database operation (query or command)."""
5959
self._check_closed()
6060

61-
conn = self._connection._async_conn
62-
if conn is None:
63-
raise InterfaceError("Connection is not open")
64-
61+
conn = await self._connection._ensure_connection()
6562
params = list(parameters) if parameters else None
6663

6764
# Determine if this is a query that returns rows

0 commit comments

Comments
 (0)