wirelog 0.60.0 added float support, but a float could reach a program
only through its source text. The untyped session entry points carry
int64 lanes, and the engine refuses them outright where a float column
is declared: wirelog_session_insert refuses the float-bearing relation,
while wirelog_session_set_delta_cb and the untyped snapshot refuse
whenever the program declares a float column anywhere. So insert(),
step() and snapshot() do not work on such a program, and wrapping insert
alone would have shipped a write with no way to read it back.
Wrapped: insert_typed, remove_typed, snapshot_typed, step_typed and
set_typed_delta_callback. Column types come from the relation's
declaration, so callers pass plain Python rows and never build lane
arrays. Left unwrapped: wirelog_session_make_compound_typed and the
wirelog_extension_* family. Neither is needed for a float column, though
a float inside a compound term does stay unreachable as a result, since
the untyped make_compound refuses a FLOAT argument.
TypedRowError subclasses ExecError so existing handlers keep working
while gaining the row index, logical column and wirelog's own bounded
diagnostic. The message buffer is caller-owned, which is why the struct
field is POINTER(c_char) and not c_char_p - the latter surfaces as an
immutable bytes and the engine's message would land where nothing can
read it. TypedErrorCode is exported alongside it, since typed_code is
public and naming it otherwise means importing from a module
docs/api-stability.md declares private.
The lane conversion lives in _core.lanes rather than on Session because
the typed delta trampoline needs it too and cannot import the session
module without a cycle. A FLOAT lane is host-order IEEE-754 bits, so
every float crosses through an explicit reinterpret; UINT32/UINT64
decode unsigned and BOOL decodes to bool, where the untyped path returns
everything signed. encode_lane accepts anything integer-like or
float-like, so a NumPy scalar works as it does through insert_batch, and
refuses a value that would not survive the conversion rather than
storing it wrong - Decimal(2**63 - 1) names an integer no binary64 can
hold. str is the deliberate exclusion: a STRING column carries an intern
id and the advanced Session has no forward-intern entry point, so
coercing would write a wrong id.
insert_typed and remove_typed take TypedRow, not the EasySession Row
alias, and the typed readers return LaneValue. Row admits str because
EasySession.insert auto-interns it; declaring it here would let mypy
wave through the one input that always raises and reject the NumPy
scalars that work. The row count and the descriptor array size both come
from one materialized list, so a Sequence whose __len__ changes between
them cannot make wirelog read past the allocation.
_delta_cb can now hold either callback kind, so set_delta_callback(),
step() and close() dispatch on the armed kind. Reusing the other kind's
handle hands ctypes a mismatched function pointer, which it rejects only
after the stored callable has been overwritten. Clearing through the
matching entry point is insurance rather than a fix: 0.60.0's
set_delta_cb nulls typed_delta_cb unconditionally and gates its
FLOAT refusal on a non-NULL callback, so either clear disarms both today
- but nothing in the public C contract promises that stays true.
Registration is guarded by has_typed_row_api(), mirroring
wirelog_program_get_relation_ir in _parser. These symbols first ship in
0.60.0 while the loader floor still admits 0.52.0, and an unguarded
attribute access would raise AttributeError at import time and make the
package unimportable against an older engine. The typed methods raise
WirelogVersionError there instead.
Validated against both engines built from source, and mypy --strict
clean over all 28 source files.
Claude-Session: https://claude.ai/code/session_01MqHwvCimcvrg1osEY5MH7o
Wraps wirelog 0.60.0's typed row C API so a
floatcolumn is usable from the advancedSession. No version bump and no CHANGELOG entry — this is the feature only; releasing it is a separate decision.Why five entry points, not just insert
1.0.6 brought float support, but only through a program's source text. The untyped entry points carry
int64lanes, and the engine refuses them where a float is involved — verified against the C source and the live engine:wirelog_session_insert/wirelog_session_removerefuse the float-bearing relationwirelog_session_set_delta_cband the untyped snapshot refuse whenever the program carries a float anywhere, including in a compound slotSo
insert()into such a relation, andstep()/snapshot()on such a program, do not work at all. Wrapping insert alone would have shipped a write with no way to read it back or drive the session.Column types come from the relation's declaration, so callers pass plain Python rows and never build lane arrays.
API
Session.insert_typed/remove_typed/snapshot_typed/step_typed/set_typed_delta_callbackTypedRowError(ExecError)— carries theTypedErrorCode, offending row index and logical column, and wirelog's own diagnostic. Existingexcept ExecErrorhandlers keep working.TypedErrorCodeexported, soerr.typed_codeis comparable without importing a moduledocs/api-stability.mddeclares private.pyrewire._core.lanes— the value ↔ lane codec, shared with the typed delta trampoline (which cannot importsessionwithout a cycle).Value policy.
stris refused: a STRING column carries an intern id and the advancedSessionhas no forward-intern entry point, so coercing would write a wrong id (int("5")is a valid id for a different symbol). Anything integer-like or float-like is accepted, so NumPy scalars work as they do throughinsert_batch. Integer-likes go throughoperator.indexand stay exact above 2⁵³. Where PyreWire can tell the float conversion lost the value it refuses rather than storing it wrong —Decimal(2**63 - 1)names an integer no binary64 can hold. NaN and the infinities are rejected by the engine, and a batch is validated whole before any of it is applied.Left unwrapped:
wirelog_session_make_compound_typedand thewirelog_extension_*family. Neither is needed for a float column. A float inside a compound term does stay unreachable as a consequence, since the untypedmake_compoundrefuses a FLOAT argument.Note for whoever cuts the next release
This adds two names to
pyrewire.__all__(TypedRowError,TypedErrorCode), taking it from 47 to 49. Every 1.0.x release held it at 47, anddocs/api-stability.mdnames new APIs and exception subclasses as minor-release surface — so the release that ships this should be a minor bump, not a patch.docs/api-stability.mdis updated here to list both names, which its contract test requires; nothing else version-related is touched.Compatibility
The typed symbols first exist in wirelog 0.60.0 while the documented floor is 0.52.0, so registration is guarded by
has_typed_row_api(), mirroring the existingwirelog_program_get_relation_irprecedent. An unguarded attribute access raisesAttributeErrorat import time and makes the package unimportable on an older engine — that regression was caught during review. The typed methods raiseWirelogVersionErrorthere instead. The engine pin is unchanged.Validation
mypy --strictclean over all 28 source files;black,isort,flake8clean. The ABI was checked againstwirelog-types.hfield-for-field, and the typed structs satisfy every check in the engine's ownsession_typed_rowsvalidator.The single failure on both engines,
test_installed_wheel_is_recognized_as_typed_by_mypy, is environmental and reproduces on unmodifiedmain: the test builds a venv withsystem_site_packages=True, which from inside another venv resolves to the base interpreter where mypy is not installed.https://claude.ai/code/session_01MqHwvCimcvrg1osEY5MH7o