Serde
serialize and deserialize return an Either, so a bad payload is an Err rather than an exception.
from pyrula import JsonSerde
serde = JsonSerde()
serde.serialize({"id": 1, "name": "alice"}) # Ok(b'{"id":1,...}')serde.deserialize(b'{"id": 1}') # Ok({"id": 1})serde.deserialize(b'not json') # Err(DeserializationError(...))Decoding matches json.loads: key order is kept and integers of any size are exact. It refuses NaN, Infinity, unpaired surrogate escapes and nesting deeper than about 200. The error names the line and column.
Encoding writes None, bool, int, finite float, str, list and dict with str keys. Anything else (a tuple, a non-string key, nan) gives Err(SerializationError(...)). Floats use shortest round-trip formatting, which can spell a value differently from json.dumps (1e-7 against 1e-07); compare by value. JsonSchemaRegistrySerde, the par_map JSON codec and Kafka’s produce_many_json write the same way.
AvroSerde takes a schema as a JSON string.
from pyrula import AvroSerde
serde = AvroSerde(schema_json=SCHEMA)serde.serialize({"id": 1, "name": "alice"}) # Ok(bytes)deserialize takes exactly one datum. Trailing bytes are an Err, including a leftover Schema Registry frame (the error points at AvroRegistrySerde). Datums that end early, and values nested more than 500 deep, are refused.
Records and maps are dict, arrays list, string and enum str, bytes and fixed bytes, and a union holds whichever branch the value is. Named types may repeat or recurse.
Logical types match fastavro, and so confluent-kafka’s Avro serializers:
| Logical type | Decodes to | Encodes from |
|---|---|---|
timestamp-millis, timestamp-micros | UTC datetime | datetime (naive is local time) or int |
local-timestamp-millis, local-timestamp-micros | naive datetime | datetime or int |
date | date | date, ISO str or int |
time-millis, time-micros | time | time or int |
decimal (bytes or fixed) | Decimal | Decimal within precision and scale, or bytes |
uuid | UUID | UUID or str |
Nanosecond timestamps and duration stay raw (int, 12 bytes). big-decimal gives an Err. A uuid on a fixed is refused when the serde is built.
Two options apply to AvroSerde and AvroRegistrySerde:
AvroSerde(SCHEMA, strict=True)AvroSerde(SCHEMA, logical_types="raw")strict=True refuses fastavro’s guessed conversions: a naive datetime for a timestamp-* field, a datetime for a date, and a decimal with more digits than its precision. logical_types="raw" decodes every logical type to its wire value.
Batches
Section titled “Batches”The _many methods take an IList and run in one Rust call. serialize_many returns an IList of results. The packed variants return a BytesList: serialize_many_raw on JsonSerde, serialize_many_bytes on the Avro serdes.
rows = IList([{"id": 1}, {"id": 2}])json_serde.serialize_many(rows) # IList([Ok(b'{"id":1}'), Ok(b'{"id":2}')])packed = json_serde.serialize_many_raw(rows) # BytesListjson_serde.deserialize_many_raw(packed) # IList([{'id': 1}, {'id': 2}])Errors
Section titled “Errors”from pyrula import SerdeError, SerializationError, DeserializationErrorBoth specific errors subclass SerdeError.
Schema Registry
Section titled “Schema Registry”AvroRegistrySerde, JsonSchemaRegistrySerde and ProtobufRegistrySerde handle Confluent framing, schema-id lookup and TLS/mTLS. They live on the Kafka path; see Kafka.
serde = AvroRegistrySerde.for_topic("http://registry:8081", "orders", SCHEMA)JsonSchemaRegistrySerde frames and registers; it does not validate payloads against the schema.
Each takes timeout_ms (default 5000) for one registry request and caches a failed lookup for 5 seconds. A registry error names the registry (scheme, host and port, never a username or password from the URL), and a 401 or 403 adds a pointer to the registry credentials. Formats covers behavior when the registry is down.
With a pinned reader_schema that differs from the writer’s, AvroRegistrySerde resolves through apache-avro, which does not keep map key order.