Skip to content

Commit a654df6

Browse files
Type repr, cursor __all__, dead-assert removal
- Give _DBAPIType singletons readable repr (STRING, BINARY, NUMBER, DATETIME, ROWID) via a _name keyword on __init__. Improves pytest parametrize ids, log output, and debugging. - Remove dead `assert offset is not None` in _iso8601_from_datetime; replace with a defensive `if offset is None: return base` that both satisfies mypy type narrowing and survives python -O. The branch is unreachable from valid Python tzinfo subclasses but keeps the formatter robust to pathological tzinfo. - Add explicit `__all__ = ["Cursor"]` to cursor module so `from dqlitedbapi.cursor import *` no longer leaks private helpers (_call_client, _convert_row, etc.). Matches the pattern already established in aio/cursor.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 94ea416 commit a654df6

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

src/dqlitedbapi/cursor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
)
2222
from dqlitewire.constants import ValueType
2323

24+
__all__ = ["Cursor"]
25+
2426

2527
async def _call_client(coro: Coroutine[Any, Any, Any]) -> Any:
2628
"""Await a client-layer coroutine, mapping its exceptions into the

src/dqlitedbapi/types.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,15 @@ class _DBAPIType:
6161
(int).
6262
"""
6363

64-
def __init__(self, *values: str | int | ValueType) -> None:
64+
def __init__(self, *values: str | int | ValueType, _name: str = "") -> None:
6565
normalized: set[str | int] = set()
6666
for v in values:
6767
if isinstance(v, ValueType):
6868
normalized.add(int(v))
6969
else:
7070
normalized.add(v)
7171
self.values = normalized
72+
self._name = _name
7273

7374
def __eq__(self, other: object) -> bool:
7475
if isinstance(other, str):
@@ -82,9 +83,12 @@ def __eq__(self, other: object) -> bool:
8283
def __hash__(self) -> int:
8384
return hash(frozenset(self.values))
8485

86+
def __repr__(self) -> str:
87+
return self._name or f"_DBAPIType({sorted(self.values, key=str)!r})"
8588

86-
STRING = _DBAPIType("TEXT", "VARCHAR", "CHAR", "CLOB", ValueType.TEXT)
87-
BINARY = _DBAPIType("BLOB", "BINARY", "VARBINARY", ValueType.BLOB)
89+
90+
STRING = _DBAPIType("TEXT", "VARCHAR", "CHAR", "CLOB", ValueType.TEXT, _name="STRING")
91+
BINARY = _DBAPIType("BLOB", "BINARY", "VARBINARY", ValueType.BLOB, _name="BINARY")
8892
NUMBER = _DBAPIType(
8993
"INTEGER",
9094
"INT",
@@ -97,6 +101,7 @@ def __hash__(self) -> int:
97101
ValueType.INTEGER,
98102
ValueType.FLOAT,
99103
ValueType.BOOLEAN,
104+
_name="NUMBER",
100105
)
101106
DATETIME = _DBAPIType(
102107
"DATE",
@@ -105,8 +110,9 @@ def __hash__(self) -> int:
105110
"DATETIME",
106111
ValueType.ISO8601,
107112
ValueType.UNIXTIME,
113+
_name="DATETIME",
108114
)
109-
ROWID = _DBAPIType("ROWID", "INTEGER PRIMARY KEY", ValueType.INTEGER)
115+
ROWID = _DBAPIType("ROWID", "INTEGER PRIMARY KEY", ValueType.INTEGER, _name="ROWID")
110116

111117

112118
# Internal conversion helpers.
@@ -132,8 +138,11 @@ def _iso8601_from_datetime(value: datetime.datetime | datetime.date) -> str:
132138
base += f".{value.microsecond:06d}"
133139
if value.tzinfo is None:
134140
return base
141+
# tzinfo is set (checked above), so utcoffset() returns timedelta.
142+
# A None here would indicate a broken tzinfo subclass; be explicit.
135143
offset = value.utcoffset()
136-
assert offset is not None
144+
if offset is None:
145+
return base
137146
total_seconds = int(offset.total_seconds())
138147
sign = "+" if total_seconds >= 0 else "-"
139148
hours, remainder = divmod(abs(total_seconds), 3600)

tests/test_module_attributes.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,26 @@ def test_datetime_type(self) -> None:
9090

9191
def test_rowid_type(self) -> None:
9292
assert dqlitedbapi.ROWID == "ROWID"
93+
94+
95+
class TestDBAPITypeRepr:
96+
def test_named_types_have_readable_repr(self) -> None:
97+
assert repr(dqlitedbapi.STRING) == "STRING"
98+
assert repr(dqlitedbapi.BINARY) == "BINARY"
99+
assert repr(dqlitedbapi.NUMBER) == "NUMBER"
100+
assert repr(dqlitedbapi.DATETIME) == "DATETIME"
101+
assert repr(dqlitedbapi.ROWID) == "ROWID"
102+
103+
104+
class TestCursorModuleAll:
105+
def test_cursor_module_has_all(self) -> None:
106+
from dqlitedbapi import cursor as cursor_mod
107+
108+
assert cursor_mod.__all__ == ["Cursor"]
109+
110+
def test_cursor_module_wildcard_import_does_not_leak_helpers(self) -> None:
111+
from dqlitedbapi import cursor as cursor_mod
112+
113+
# Verify private helpers exist on the module but are not in __all__.
114+
assert hasattr(cursor_mod, "_call_client")
115+
assert "_call_client" not in cursor_mod.__all__

0 commit comments

Comments
 (0)