Skip to content

Commit d82041b

Browse files
Annotate module-level constants in dqliteclient as Final / ClassVar
Per PEP 591, immutable module-level constants should be annotated as Final and class-level singletons as ClassVar. The dqliteclient package had bare assignments throughout; mypy treated them as mutable. Touch: - connection.py: _TRANSACTION_{BEGIN,COMMIT,ROLLBACK}_SQL, _BARE_IDENT_{FIRST,REST}, _RAFT_BUSY_MESSAGE_FRAGMENTS - exceptions.py: _MAX_DISPLAY_MESSAGE on OperationalError (ClassVar) - cluster.py: _DEFAULT_CONNECT_MAX_ATTEMPTS, _MAX_ERROR_MESSAGE_SNIPPET, _LEADER_PROBE_DRAIN_TIMEOUT_SECONDS, _cluster_random - pool.py: _POOL_CLEANUP_EXCEPTIONS - protocol.py: _READ_CHUNK_SIZE, _HEARTBEAT_READ_TIMEOUT_CAP_SECONDS No runtime behavior change. Tests and mypy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f923db1 commit d82041b

5 files changed

Lines changed: 20 additions & 17 deletions

File tree

src/dqliteclient/cluster.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import random
77
from collections.abc import Callable, Iterable
8+
from typing import Final
89

910
from dqliteclient.connection import DqliteConnection, _parse_address, _validate_timeout
1011
from dqliteclient.exceptions import (
@@ -39,27 +40,27 @@
3940
# hiding genuine cluster instability under what looks like "a slow
4041
# connect". Operators can override via ClusterClient.connect(
4142
# max_attempts=...).
42-
_DEFAULT_CONNECT_MAX_ATTEMPTS = 3
43+
_DEFAULT_CONNECT_MAX_ATTEMPTS: Final[int] = 3
4344

4445
# Cap per-node error messages at this length before concatenating them
4546
# into the final ClusterError. A failing peer that returns a multi-MB
4647
# FailureResponse message would otherwise produce an O(N * M) string
4748
# held in memory and serialised into every traceback.
48-
_MAX_ERROR_MESSAGE_SNIPPET = 200
49+
_MAX_ERROR_MESSAGE_SNIPPET: Final[int] = 200
4950

5051
# Use OS-entropy randomness for the per-sweep node shuffle so that the
5152
# stampede-avoidance is not defeated by a downstream call to
5253
# ``random.seed(...)``. Test suites and some libraries seed the global
5354
# PRNG for determinism; if we used ``random.shuffle`` directly, every
5455
# process picking up that seed would produce the same shuffle and pile
5556
# onto the same node. ``SystemRandom`` ignores ``random.seed()``.
56-
_cluster_random = random.SystemRandom()
57+
_cluster_random: Final[random.Random] = random.SystemRandom()
5758

5859
# Budget for the bounded writer-drain in ``_query_leader``. A
5960
# responsive peer drains FIN/ACK in microseconds; a slow peer must not
6061
# hold up leader discovery. 100 ms is generous for LAN and still
6162
# negligible against the per-probe ``self._timeout`` (typically seconds).
62-
_LEADER_PROBE_DRAIN_TIMEOUT_SECONDS = 0.1
63+
_LEADER_PROBE_DRAIN_TIMEOUT_SECONDS: Final[float] = 0.1
6364

6465

6566
def _addr_equiv(a: str, b: str) -> bool:

src/dqliteclient/connection.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from collections.abc import AsyncIterator, Awaitable, Callable, Mapping, Sequence
1111
from contextlib import asynccontextmanager
1212
from types import TracebackType
13-
from typing import Any
13+
from typing import Any, Final
1414

1515
from dqliteclient.exceptions import (
1616
DataError,
@@ -43,16 +43,16 @@
4343
# and Go peer clients which also emit a bare ``BEGIN``. The constant
4444
# pins the literal so a refactor can't silently upgrade to ``BEGIN
4545
# IMMEDIATE`` without showing up in review.
46-
_TRANSACTION_BEGIN_SQL = "BEGIN"
46+
_TRANSACTION_BEGIN_SQL: Final[str] = "BEGIN"
4747

4848
# ``COMMIT`` and ``ROLLBACK`` literals are pinned for the same reason as
4949
# ``_TRANSACTION_BEGIN_SQL``: a refactor must not silently switch to a
5050
# vendor-qualified form (``COMMIT TRANSACTION``) or a savepoint variant.
5151
# Consistency between ``transaction()`` and the pool's reset-on-return
5252
# path is a correctness invariant — if they diverge, a connection could
5353
# be returned to the pool with a still-open server-side transaction.
54-
_TRANSACTION_COMMIT_SQL = "COMMIT"
55-
_TRANSACTION_ROLLBACK_SQL = "ROLLBACK"
54+
_TRANSACTION_COMMIT_SQL: Final[str] = "COMMIT"
55+
_TRANSACTION_ROLLBACK_SQL: Final[str] = "ROLLBACK"
5656

5757
# ``_TX_AUTO_ROLLBACK_PRIMARY_CODES`` is imported above from ``dqlitewire``
5858
# (canonical home for SQLite-side constants). It contains the primary
@@ -78,8 +78,8 @@
7878
# a fresh transaction whose boundary the user code does not know about.
7979

8080

81-
_BARE_IDENT_FIRST = frozenset(string.ascii_letters + "_")
82-
_BARE_IDENT_REST = frozenset(string.ascii_letters + string.digits + "_")
81+
_BARE_IDENT_FIRST: Final[frozenset[str]] = frozenset(string.ascii_letters + "_")
82+
_BARE_IDENT_REST: Final[frozenset[str]] = frozenset(string.ascii_letters + string.digits + "_")
8383

8484
# Coupled to dqlite-upstream/src/gateway.c failure() emissions for the
8585
# Raft-side BUSY path that means "the in-flight write was not accepted;
@@ -90,7 +90,7 @@
9090
# the matcher here keeps the upstream-coupling explicit so a future
9191
# rewording (or addition of a new Raft-BUSY message) is one-line update
9292
# rather than a hunt through the classifier.
93-
_RAFT_BUSY_MESSAGE_FRAGMENTS: tuple[str, ...] = ("checkpoint in progress",)
93+
_RAFT_BUSY_MESSAGE_FRAGMENTS: Final[tuple[str, ...]] = ("checkpoint in progress",)
9494

9595

9696
def _is_keyword_boundary(s: str, kw_len: int) -> bool:

src/dqliteclient/exceptions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Exceptions for dqlite client."""
22

3+
from typing import ClassVar
4+
35
from dqlitewire.exceptions import ProtocolError as _WireProtocolError
46

57
__all__ = [
@@ -81,7 +83,7 @@ class OperationalError(DqliteError):
8183
``super().__init__`` keeps the raw payload on ``self.args``.
8284
"""
8385

84-
_MAX_DISPLAY_MESSAGE = 1024
86+
_MAX_DISPLAY_MESSAGE: ClassVar[int] = 1024
8587

8688
code: int
8789
message: str

src/dqliteclient/pool.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections.abc import AsyncIterator, Sequence
77
from contextlib import asynccontextmanager
88
from types import TracebackType
9-
from typing import Any
9+
from typing import Any, Final
1010

1111
from dqliteclient.cluster import ClusterClient
1212
from dqliteclient.connection import (
@@ -53,7 +53,7 @@
5353
# ``_check_in_use`` raise ``InterfaceError("owned by another task")``.
5454
# That should drop the connection, not crash the pool's release path
5555
# (which would leak a ``_size`` slot and eventually wedge the pool).
56-
_POOL_CLEANUP_EXCEPTIONS = (
56+
_POOL_CLEANUP_EXCEPTIONS: Final[tuple[type[BaseException], ...]] = (
5757
OSError,
5858
DqliteConnectionError,
5959
ProtocolError,

src/dqliteclient/protocol.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import secrets
66
import sys
77
from collections.abc import Sequence
8-
from typing import Any
8+
from typing import Any, Final
99

1010
from dqliteclient.exceptions import DqliteConnectionError, OperationalError, ProtocolError
1111
from dqlitewire import (
@@ -47,7 +47,7 @@
4747

4848
# Socket read buffer size. 4 KiB balances syscall overhead for typical
4949
# request/response payloads against latency for small wire messages.
50-
_READ_CHUNK_SIZE = 4096
50+
_READ_CHUNK_SIZE: Final[int] = 4096
5151

5252
# Upper bound on how wide a server's advertised heartbeat can stretch
5353
# the per-read deadline on a connection that opted into
@@ -60,7 +60,7 @@
6060
# ``trust_server_heartbeat`` docstrings at protocol.py:82 and :106,
6161
# connection.py:__init__ docstring, and the top-level ``connect`` /
6262
# ``create_pool`` docstrings in ``__init__.py``.
63-
_HEARTBEAT_READ_TIMEOUT_CAP_SECONDS = 300.0
63+
_HEARTBEAT_READ_TIMEOUT_CAP_SECONDS: Final[float] = 300.0
6464

6565

6666
def _failure_message(message: str, addr_suffix: str) -> str:

0 commit comments

Comments
 (0)