Add to_jsonable() (Access/pyodbc value -> JSON-safe) with strict TDD (5 tests: datetime iso, bool preserved before int, decimal->str, none/numbers, dict round-trip). bool branch precedes int because bool is a subclass of int in Python. Add AccessReader: reads TableChangeLog (read_log), reads a full source row by RecordID via to_jsonable (read_row, warns on >4000-char values that JSON_VALUE would truncate), and deletes processed log IDs in chunked, retried batches (delete_log_ids, no-op on empty list). Connects via the ACE ODBC driver from config.access.driver, shared autocommit mode to coexist with the live .laccdb client. Integration test against the real 氩弧焊.accdb (read-only; delete_log_ids only exercised with []) passes. Co-Authored-By: Claude <noreply@anthropic.com>
35 lines
1.2 KiB
Python
35 lines
1.2 KiB
Python
"""Access value serialization helpers.
|
|
|
|
Converts pyodbc/Access cell values into JSON-serializable Python types so a
|
|
captured row dict can be ``json.dumps``-ed before being enqueued on SQL Server.
|
|
|
|
Order matters: ``bool`` MUST be checked before ``int`` because ``bool`` is a
|
|
subclass of ``int`` in Python (``isinstance(True, int) is True``). If the order
|
|
were reversed, ``True`` would be returned as ``1`` and lose its type.
|
|
"""
|
|
import datetime
|
|
import decimal
|
|
|
|
|
|
def to_jsonable(v):
|
|
"""Coerce a single Access/pyodbc value to a JSON-serializable form.
|
|
|
|
- ``None`` -> ``None``
|
|
- ``bool`` -> ``bool`` (must precede the ``int`` branch)
|
|
- ``datetime``/``date`` -> ISO-8601 string (``"2026-07-14T08:41:18"``)
|
|
- ``Decimal`` -> string (preserves exact scale, e.g. ``"12.50"``)
|
|
- ``int``/``float``/``str`` -> unchanged
|
|
- anything else (e.g. ``bytes`` for OLE) -> ``str(v)`` fallback
|
|
"""
|
|
if v is None:
|
|
return None
|
|
if isinstance(v, bool): # MUST be before int
|
|
return v
|
|
if isinstance(v, (datetime.datetime, datetime.date)):
|
|
return v.isoformat()
|
|
if isinstance(v, decimal.Decimal):
|
|
return str(v)
|
|
if isinstance(v, (int, float, str)):
|
|
return v
|
|
return str(v)
|