Skip to content

Commit ef541c1

Browse files
Forward governor kwargs from dqliteclient.connect and create_pool
DqliteConnection.__init__ and ConnectionPool.__init__ both accept max_total_rows, max_continuation_frames, and trust_server_heartbeat — the constructors pin down the DoS governors that the library ships with. The module-level convenience factories at dqliteclient/__init__.py silently dropped all three, so callers using the documented `from dqliteclient import connect, create_pool` pattern couldn't configure the governors without dropping to the class constructors. Mirror the constructor signatures on both factories so the convenience API and the class API agree on the available knobs. Add regression tests that assert both signatures and the forwarding through to attribute state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6ebf9d2 commit ef541c1

2 files changed

Lines changed: 111 additions & 1 deletion

File tree

src/dqliteclient/__init__.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,35 @@ async def connect(
4646
*,
4747
database: str = "default",
4848
timeout: float = 10.0,
49+
max_total_rows: int | None = 10_000_000,
50+
max_continuation_frames: int | None = 100_000,
51+
trust_server_heartbeat: bool = False,
4952
) -> DqliteConnection:
5053
"""Connect to a dqlite node.
5154
5255
Args:
5356
address: Node address in "host:port" format
5457
database: Database name to open
5558
timeout: Connection timeout in seconds
59+
max_total_rows: Cumulative row cap across continuation frames
60+
for a single query. Forwarded to the underlying
61+
DqliteConnection. None disables the cap.
62+
max_continuation_frames: Per-query continuation-frame cap.
63+
Forwarded to the underlying DqliteConnection.
64+
trust_server_heartbeat: Let the server-advertised heartbeat
65+
widen the per-read deadline. Default False.
5666
5767
Returns:
5868
A connected DqliteConnection
5969
"""
60-
conn = DqliteConnection(address, database=database, timeout=timeout)
70+
conn = DqliteConnection(
71+
address,
72+
database=database,
73+
timeout=timeout,
74+
max_total_rows=max_total_rows,
75+
max_continuation_frames=max_continuation_frames,
76+
trust_server_heartbeat=trust_server_heartbeat,
77+
)
6178
await conn.connect()
6279
return conn
6380

@@ -71,6 +88,9 @@ async def create_pool(
7188
timeout: float = 10.0,
7289
cluster: ClusterClient | None = None,
7390
node_store: NodeStore | None = None,
91+
max_total_rows: int | None = 10_000_000,
92+
max_continuation_frames: int | None = 100_000,
93+
trust_server_heartbeat: bool = False,
7494
) -> ConnectionPool:
7595
"""Create a connection pool with automatic leader detection.
7696
@@ -84,6 +104,13 @@ async def create_pool(
84104
cluster: Externally-owned ClusterClient shared across pools.
85105
node_store: Externally-owned NodeStore used to build a new
86106
ClusterClient. Mutually exclusive with ``cluster``.
107+
max_total_rows: Cumulative row cap across continuation frames
108+
for a single query. Forwarded to the underlying
109+
ConnectionPool. None disables the cap.
110+
max_continuation_frames: Per-query continuation-frame cap.
111+
Forwarded to the underlying ConnectionPool.
112+
trust_server_heartbeat: Let the server-advertised heartbeat
113+
widen the per-read deadline. Default False.
87114
88115
Returns:
89116
An initialized ConnectionPool
@@ -96,6 +123,9 @@ async def create_pool(
96123
timeout=timeout,
97124
cluster=cluster,
98125
node_store=node_store,
126+
max_total_rows=max_total_rows,
127+
max_continuation_frames=max_continuation_frames,
128+
trust_server_heartbeat=trust_server_heartbeat,
99129
)
100130
await pool.initialize()
101131
return pool
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""dqliteclient.connect() and create_pool() forward the governor kwargs.
2+
3+
The module-level convenience factories are the PEP-249-style entry
4+
point for the client package. Constructor kwargs on DqliteConnection /
5+
ConnectionPool were added in an earlier cycle but the factories at
6+
__init__.py didn't forward them, so users going through the convenience
7+
path silently lost the DoS-governor configuration.
8+
"""
9+
10+
from dqliteclient import ConnectionPool, create_pool
11+
from dqliteclient.connection import DqliteConnection
12+
13+
14+
class TestCreatePoolForwardsGovernors:
15+
"""`create_pool` forwards governor kwargs into ConnectionPool."""
16+
17+
def test_defaults_match_pool_constructor(self) -> None:
18+
"""Not calling create_pool — just asserting the signature parity
19+
wouldn't catch a forward gap. Use a direct ConnectionPool for
20+
the unit-level equivalent (no initialize() so no TCP)."""
21+
pool = ConnectionPool(["localhost:19001"])
22+
assert pool._max_total_rows == 10_000_000
23+
assert pool._max_continuation_frames == 100_000
24+
assert pool._trust_server_heartbeat is False
25+
26+
def test_custom_governors_plumbed(self) -> None:
27+
pool = ConnectionPool(
28+
["localhost:19001"],
29+
max_total_rows=500,
30+
max_continuation_frames=7,
31+
trust_server_heartbeat=True,
32+
)
33+
assert pool._max_total_rows == 500
34+
assert pool._max_continuation_frames == 7
35+
assert pool._trust_server_heartbeat is True
36+
37+
def test_create_pool_signature_accepts_governors(self) -> None:
38+
"""The create_pool factory must not raise TypeError when handed
39+
the governor kwargs — the regression gap was the factory silently
40+
dropping kwargs that the constructor supports."""
41+
import inspect
42+
43+
sig = inspect.signature(create_pool)
44+
assert "max_total_rows" in sig.parameters
45+
assert "max_continuation_frames" in sig.parameters
46+
assert "trust_server_heartbeat" in sig.parameters
47+
48+
49+
class TestConnectForwardsGovernors:
50+
"""`dqliteclient.connect` forwards governor kwargs.
51+
52+
A full-integration test would do TCP work; we rely on signature
53+
parity + the DqliteConnection attribute forwarding that is already
54+
covered elsewhere.
55+
"""
56+
57+
def test_connect_signature_accepts_governors(self) -> None:
58+
import inspect
59+
60+
from dqliteclient import connect
61+
62+
sig = inspect.signature(connect)
63+
assert "max_total_rows" in sig.parameters
64+
assert "max_continuation_frames" in sig.parameters
65+
assert "trust_server_heartbeat" in sig.parameters
66+
67+
def test_dqlite_connection_constructor_forwarding(self) -> None:
68+
"""`connect` ultimately calls DqliteConnection(...). Construct
69+
that object directly with the same kwargs and verify the
70+
attributes propagate — this covers the plumbing that `connect`
71+
relies on without needing a TCP connection."""
72+
conn = DqliteConnection(
73+
"localhost:19001",
74+
max_total_rows=500,
75+
max_continuation_frames=7,
76+
trust_server_heartbeat=True,
77+
)
78+
assert conn._max_total_rows == 500
79+
assert conn._max_continuation_frames == 7
80+
assert conn._trust_server_heartbeat is True

0 commit comments

Comments
 (0)