Files
ProductionDataBaseSync_Data…/src/sync/serialize.py
Misaka_Company 1f7191421a feat: access reader and value serialization
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>
2026-07-14 12:22:48 +08:00

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)