Skip to main content

Pure Python wire protocol implementation for dqlite

Project description

dqlite-wire

Pure Python wire protocol implementation for dqlite, Canonical's distributed SQLite.

Installation

pip install dqlite-wire

Usage

from dqlitewire import encode_message, decode_message
from dqlitewire.messages import LeaderRequest

# Encode a message
data = encode_message(LeaderRequest())

# Decode a message
message = decode_message(data, is_request=True)

Thread-safety

ReadBuffer, WriteBuffer, MessageEncoder, and MessageDecoder are not thread-safe. Each instance must be owned by a single thread or a single asyncio coroutine at a time. This matches Go's driver.Conn contract from go-dqlite.

Concurrent use of a single instance from multiple threads produces silent data corruption — not exceptions. The is_poisoned mechanism catches torn state from signal delivery during single-owner execution, but it cannot detect lost-update races between concurrent threads. Fuzz testing reliably reproduces both duplicate message delivery and corrupt (garbage) message bytes with no exception surfacing.

If you need concurrent access, wrap every call site in an asyncio.Lock or threading.Lock at the layer that owns the socket and decoder.

Protocol Reference

Based on the dqlite wire protocol specification.

Deliberate divergences from upstream

This library implements the dqlite wire protocol faithfully but adds a handful of defensive guards that the upstream C server and the canonical go-dqlite client do not. They protect a Python client running in potentially adversarial network contexts and are all opt-out-able.

Python-specific caps (not present in C or Go; None disables):

  • DEFAULT_MAX_TOTAL_ROWS (MessageDecoder(max_total_rows=...), default 10,000,000) — cap on rows accumulated across continuation frames for one query. Importable from dqlitewire.
  • DEFAULT_MAX_CONTINUATION_FRAMES (MessageDecoder(max_continuation_frames=...), default 100,000) — cap on continuation frames for one query. Importable from dqlitewire.
  • RowsResponse.DEFAULT_MAX_ROWS (MessageDecoder(max_rows=...), default 1,000,000) — per-frame row cap. Class-scoped, not exported at module level.
  • ReadBuffer.DEFAULT_MAX_MESSAGE_SIZE (ReadBuffer(max_message_size=...), default 64 MiB) — envelope cap on a single frame. Class-scoped, not exported at module level.
  • _MAX_PARAM_COUNT (32,766 — matches SQLite's SQLITE_MAX_VARIABLE_NUMBER), _MAX_COLUMN_COUNT (255 — matches the C server's STMT__MAX_COLUMNS), _MAX_FILE_COUNT (100), _MAX_NODE_COUNT (10,000) — internal sanity bounds on decoded tuple / response sizes.

Stricter-than-Go validations (match the C server's intent):

  • decode_row_header requires the full 8-byte marker (C defines DQLITE_RESPONSE_ROWS_DONE = 0xff..ff / _PART = 0xee..ee; go-dqlite checks only the first byte).
  • encode_value(value, ValueType.BOOLEAN) rejects arbitrary ints (accepts only bool or exactly 0/1).
  • FilesResponse.encode_body rejects non-8-aligned file content (C's dumpFile asserts len % 8 == 0).
  • encode_params_tuple rejects ValueType.UNIXTIME outbound (C's tuple_decoder__next cannot decode it on the server side).
  • StmtResponse rejects a 16-byte body when schema=1 (C's V1 response is 24 bytes).

Asymmetric encode/decode (decoded for proxy / recorded-traffic round-trip; encoder rejects):

  • ClusterRequest format=0 — V0 response shape (id + address only). Decoded by ClusterRequest.decode_body for proxy / replay use; encoder rejects with EncodeError because production senders always emit V1 (id + address + role).

Development

See DEVELOPMENT.md for setup and contribution guidelines.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dqlite_wire-0.2.2.tar.gz (337.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dqlite_wire-0.2.2-py3-none-any.whl (115.4 kB view details)

Uploaded Python 3

File details

Details for the file dqlite_wire-0.2.2.tar.gz.

File metadata

  • Download URL: dqlite_wire-0.2.2.tar.gz
  • Upload date:
  • Size: 337.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dqlite_wire-0.2.2.tar.gz
Algorithm Hash digest
SHA256 eff65db37fdbd566d74fb4e6908fd0af8ecb596bb99d9f228af7653ed994e69a
MD5 b958caeb1362240f7994fd48a5193106
BLAKE2b-256 66e2205b4f9a955d9a5427a008004539e3e49555ffae5e7ccfcf5a57c31133e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for dqlite_wire-0.2.2.tar.gz:

Publisher: publish-to-pypi.yml on letsdiscodev/python-dqlite-wire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dqlite_wire-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: dqlite_wire-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 115.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dqlite_wire-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 6d60a17b8c6cd7bf21a95e982d9615b9f21aa7ae6aaee8700a08993c2bdbc414
MD5 3f9e5f287d09641da995d8cbd149325d
BLAKE2b-256 c5829415c0cfd2c795b1253272b4173525bf014a0754430c19734658a4478a3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for dqlite_wire-0.2.2-py3-none-any.whl:

Publisher: publish-to-pypi.yml on letsdiscodev/python-dqlite-wire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page