Skip to content

Latest commit

 

History

History
113 lines (81 loc) · 4.46 KB

File metadata and controls

113 lines (81 loc) · 4.46 KB

Python bridge contract

Covers the generated Python bindings and the desktop package.

Read the shared contract first. This document covers what is specific to Python.

The generated layer

UniFFI produces Python from the interface definition. It is one third of the artifact set described in C1 and is never regenerated alone.

The desktop build script delegates to the same generation script, and CI builds the desktop package through it.

P1. This is the thinnest binding, and that makes it the ABI canary

Python has no platform integration to speak of: no Keychain, no Keystore, no Bluetooth stack, no React Native lifecycle. It is close to a direct view of the interface definition.

That makes it the cheapest place to detect an ABI break. If a change makes the Python package fail to import or a call fail on a checksum, the Swift and Kotlin bindings have the same problem and will surface it later, on a device, in a harder-to-diagnose form.

Run the Python tests before the mobile ones when changing the interface.

P2. Nothing here is a reference implementation of platform concerns

The Python package does not implement secure storage against a platform keystore. Do not copy its storage handling into a mobile binding, and do not treat its behaviour as the contract for one.

It does carry one shared constant, which is easy to miss precisely because the rest of the binding is platform-free: state_storage.py holds one of the four copies of the protocol-state record ceiling, and it must spell 8 * 1024 * 1024 exactly. A Rust guard reads this file, so editing the ceiling in the mobile bindings and not here fails the Rust suite, not pytest. See C5.

P3. Packaging

The package is versioned in lockstep with the workspace. A release cut touches the Python project metadata along with the Cargo manifests, the lockfile, and the third-party notices.

P4. The error enum is positional here too

The same append-only rule applies. See C2.

P5. Events

Events arrive as JSON strings, exactly as in the other bindings. Python's dynamism makes it tempting to consume them ad hoc, and that is fine for tooling, but it means the Python surface offers no drift protection at all. It will not catch a renamed event field for you.

P6. Python is where a storage adapter is cheapest to get right

Python is the only binding where an application can hand in its own ProtocolStateStorageProvider today (ProtocolManager(state_storage=...)), which makes it the best place to develop and debug an adapter before writing the same thing in Swift or Kotlin.

The contract and the gate are the same in every language:

import json
from offline_protocol_sdk.offline_protocol import run_storage_conformance

report = json.loads(run_storage_conformance(my_adapter))
assert report["failures"] == [], report["failures"]

Two Python-specific traps, both of which the suite catches:

  • Values are bytes, not str. Sealed records are ciphertext, so a provider that decodes to text anywhere in its path corrupts them. sqlite3 in particular returns memoryview for a BLOB in some configurations — wrap it in bytes() before returning.
  • An absent key returns None, it does not raise. The SDK asks for records that legitimately do not exist yet on every launch, and raising turns a normal startup into an error path.

A worked reference lives in examples/storage-adapters/python/sqlite_state_storage.py.

Python currently ships no wipePersistedState equivalent (the mobile bindings do). An application that needs logout has to clear its own storage root, and if it pointed documents at a separate backend, call DataStore.wipe_all() too. Stop the protocol first: there are no deletion tombstones, so a wipe on a running engine with live sessions is undone by the peer's next version offer, which recreates and refills every document.

Testing

cd bindings/python
# build the desktop library first, then
pytest

Tests live in bindings/python/tests/. The build script under bindings/python/scripts/ produces the library the tests load.

Note on packaging tests

Guard tests that assert on repository layout panic in a packaged tarball, because the layout is not there. Keep such assertions out of the packaged test set.