Skip to content

Commit 4fa9c9e

Browse files
Correct _datetime_from_iso8601 docstring to match two-step fallback
The docstring enumerated three fallback steps (datetime → time → date) but the body only runs two (datetime.fromisoformat then time.fromisoformat); there is no third date.fromisoformat call. Bare YYYY-MM-DD input lands on step 1 — Python 3.11+'s datetime.fromisoformat widened to accept every shape date.fromisoformat accepts, including the bare date. A secondary paragraph reinforced the same inaccuracy by claiming "the decoder's fallback path parses the string with datetime.date.fromisoformat". Rewrite both to describe the actual two-step shape and explain that step 1's auto-widen serves the bare-date round-trip. Also note that ordinal-date form YYYY-OOO is rejected as a consequence of step 1 covering everything (no upstream emits ordinals today, so the asymmetry is benign). Documentation-only; no behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cb18033 commit 4fa9c9e

2 files changed

Lines changed: 70 additions & 13 deletions

File tree

src/dqlitedbapi/types.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -519,30 +519,40 @@ def _datetime_from_iso8601(text: str) -> datetime.datetime | datetime.time | Non
519519
server still tolerates empty ISO8601 values. Returning None matches
520520
PEP 249 NULL semantics.
521521
522-
Tries, in order:
523-
524-
1. ``datetime.datetime.fromisoformat`` — ``YYYY-MM-DD HH:MM:SS[…]``
522+
Two-step fallback (in order):
523+
524+
1. ``datetime.datetime.fromisoformat`` — covers full
525+
``YYYY-MM-DD HH:MM:SS[.ffffff][±HH:MM]`` plus bare
526+
``YYYY-MM-DD``. On Python 3.11+ ``datetime.fromisoformat``
527+
widened to accept every shape ``date.fromisoformat`` accepts,
528+
so a bare-date input lands here (returning midnight datetime
529+
— see the date-widens paragraph below).
525530
2. ``datetime.time.fromisoformat`` — ``HH:MM:SS[.ffffff][±HH:MM]``,
526531
matching the ``_iso8601_from_time`` bind-path encoder so a
527532
``datetime.time`` bound via the driver round-trips as
528533
``datetime.time`` on readback rather than raising ``DataError``.
529-
3. ``datetime.date.fromisoformat`` — ``YYYY-MM-DD`` (widened to
530-
``datetime.datetime`` on return; see below).
534+
535+
There is intentionally NO third ``date.fromisoformat`` arm — step 1
536+
already covers bare ``YYYY-MM-DD`` on every supported Python.
537+
Ordinal-date form ``YYYY-OOO`` (which ``date.fromisoformat``
538+
accepts but ``datetime.fromisoformat`` rejects) is therefore
539+
rejected here too; no upstream emits ordinal dates today, so the
540+
asymmetry is benign.
531541
532542
Naive input round-trips as naive; aware input preserves the offset.
533543
Python 3.11+ ``datetime.fromisoformat`` accepts a trailing ``Z``
534544
natively; no pre-substitution is needed.
535545
536546
**``date`` widens to ``datetime`` on round-trip.** A ``datetime.date``
537547
passed to PEP 249 ``Date()`` serializes via ``isoformat()`` as
538-
``"YYYY-MM-DD"`` (no time component). The decoder's fallback path
539-
parses the string with ``datetime.date.fromisoformat`` and returns
540-
a ``datetime.datetime(year, month, day)`` — the value widens from
541-
date to datetime. This matches pysqlite's default behaviour (stdlib
542-
``sqlite3`` with ``detect_types`` does the same widen). Callers who
543-
need a strict ``date`` on readback should narrow via ``.date()`` or
544-
use the SQLAlchemy ``_DqliteDate`` type that does the narrowing at
545-
the ORM layer.
548+
``"YYYY-MM-DD"`` (no time component). Step 1 above parses that
549+
bare-date string and returns ``datetime.datetime(year, month, day)``
550+
at midnight — the value widens from date to datetime. This matches
551+
pysqlite's default behaviour (stdlib ``sqlite3`` with
552+
``detect_types`` does the same widen). Callers who need a strict
553+
``date`` on readback should narrow via ``.date()`` or use the
554+
SQLAlchemy ``_DqliteDate`` type that does the narrowing at the
555+
ORM layer.
546556
547557
``datetime.time`` does NOT widen — ``HH:MM:SS`` has no date
548558
component so widening would require an arbitrary sentinel date.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Doc-pin: ``_datetime_from_iso8601``'s docstring lists the two
2+
fallback steps that the implementation actually runs, not three.
3+
4+
The body at ``types.py`` only tries ``datetime.datetime.fromisoformat``
5+
followed by ``datetime.time.fromisoformat`` and raises ``DataError``.
6+
There is no third ``date.fromisoformat`` arm — the bare-date case is
7+
served by step 1 on Python 3.11+ (``datetime.fromisoformat("YYYY-MM-DD")``
8+
returns midnight). The previous docstring enumerated a third step and
9+
asserted "the fallback path parses the string with
10+
``datetime.date.fromisoformat``" — neither claim was true. This pin
11+
locks the docstring to the actual two-step shape so future readers
12+
trust what's written.
13+
14+
Documentation-only; no behaviour change.
15+
"""
16+
17+
from __future__ import annotations
18+
19+
from dqlitedbapi.types import _datetime_from_iso8601
20+
21+
22+
def test_docstring_does_not_claim_three_fallback_steps() -> None:
23+
doc = _datetime_from_iso8601.__doc__ or ""
24+
# The old wording numbered "3. ``datetime.date.fromisoformat``"
25+
# as a fallback step that the body never runs. The new wording
26+
# must not contain a numbered "3." fallback step.
27+
assert "3. ``datetime.date.fromisoformat``" not in doc, (
28+
"_datetime_from_iso8601 docstring still lists a numbered third "
29+
"fallback step (``datetime.date.fromisoformat``) that the "
30+
"implementation never runs."
31+
)
32+
# The old "fallback path parses the string with
33+
# ``datetime.date.fromisoformat``" claim must also be gone.
34+
assert "parses the string with ``datetime.date.fromisoformat``" not in doc, (
35+
"_datetime_from_iso8601 docstring still asserts that the "
36+
"fallback parses with ``datetime.date.fromisoformat`` — the "
37+
"body has no such call (the bare-date case is handled by "
38+
"step 1's auto-widen on Python 3.11+)."
39+
)
40+
41+
42+
def test_docstring_enumerates_two_step_fallback() -> None:
43+
doc = _datetime_from_iso8601.__doc__ or ""
44+
assert "Two-step fallback" in doc or "two-step fallback" in doc, (
45+
"_datetime_from_iso8601 docstring must explicitly state that "
46+
"the fallback is two-step (datetime → time)."
47+
)

0 commit comments

Comments
 (0)