ProvenPin Install

fastavro in-memory container round-trip with logical types: working versions (tested 2026-10-18)

Serialize records to Avro container bytes with fastavro, decode logical types and evolve a stored schema by reading with a reader schema.

passed6 passed · 0 failed2026-10-18 03:57 UTC
Status
passed
Last passed
2026-10-18 03:57 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
fastavro@1.13.1pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 39095370682

The code

Exactly the files that passed. Your agent can pull them with get_recipe("fastavro-schema-roundtrip") (install), or see them on GitHub.

requirements.txt
fastavro==1.13.1
pytest==9.1.1
avro_store.py
import io
from datetime import date, datetime, timezone
from decimal import Decimal
from typing import Any, Iterable

import fastavro

USER_SCHEMA: dict[str, Any] = {
    "type": "record",
    "name": "User",
    "namespace": "example",
    "fields": [
        {"name": "id", "type": "string"},
        {"name": "created", "type": {"type": "long", "logicalType": "timestamp-millis"}},
        {"name": "birthday", "type": {"type": "int", "logicalType": "date"}},
        {
            "name": "balance",
            "type": {"type": "bytes", "logicalType": "decimal", "precision": 10, "scale": 2},
        },
        {"name": "uid", "type": {"type": "string", "logicalType": "uuid"}},
    ],
}

READER_SCHEMA: dict[str, Any] = {
    "type": "record",
    "name": "User",
    "namespace": "example",
    "fields": [
        {"name": "id", "type": "string"},
        {
            "name": "created_at",
            "type": {"type": "long", "logicalType": "timestamp-millis"},
            "aliases": ["created"],
        },
        {
            "name": "balance",
            "type": {"type": "bytes", "logicalType": "decimal", "precision": 10, "scale": 2},
        },
        {"name": "uid", "type": {"type": "string", "logicalType": "uuid"}},
        {"name": "nickname", "type": "string", "default": "anon"},
        {"name": "tier", "type": "string", "default": "free"},
    ],
}

BAD_READER_SCHEMA: dict[str, Any] = {
    "type": "record",
    "name": "User",
    "namespace": "example",
    "fields": [
        {"name": "id", "type": "string"},
        {"name": "nickname", "type": "string"},
    ],
}

USER = fastavro.parse_schema(USER_SCHEMA)
READER = fastavro.parse_schema(READER_SCHEMA)
BAD_READER = fastavro.parse_schema(BAD_READER_SCHEMA)

SAMPLE: dict[str, Any] = {
    "id": "u-1",
    "created": datetime(2021, 2, 3, 4, 5, 6, tzinfo=timezone.utc),
    "birthday": date(1990, 5, 4),
    "balance": Decimal("10.50"),
    "uid": "12345678-1234-5678-1234-567812345678",
}

def write_container(records: Iterable[dict], schema: dict = USER, codec: str = "null") -> bytes:
    buf = io.BytesIO()
    fastavro.writer(buf, schema, list(records), codec=codec)
    return buf.getvalue()

def read_container(data: bytes, reader_schema: dict | None = None) -> list[dict]:
    return list(fastavro.reader(io.BytesIO(data), reader_schema=reader_schema))

def write_one(record: dict, schema: dict = USER) -> bytes:
    buf = io.BytesIO()
    fastavro.schemaless_writer(buf, schema, record)
    return buf.getvalue()

def read_one(data: bytes, schema: dict = USER) -> dict:
    return fastavro.schemaless_reader(io.BytesIO(data), schema)
tests/test_avro_store.py
import io
from datetime import date, datetime
from decimal import Decimal
from uuid import UUID

import fastavro
import pytest
from fastavro.read import SchemaResolutionError

from avro_store import (
    BAD_READER,
    READER,
    SAMPLE,
    read_container,
    read_one,
    write_container,
    write_one,
)

def test_roundtrip_decodes_logical_types():
    (row,) = read_container(write_container([SAMPLE]))
    assert isinstance(row["created"], datetime) and row["created"].tzinfo is not None
    assert row["created"] == SAMPLE["created"]
    assert row["birthday"] == date(1990, 5, 4)
    assert row["balance"] == Decimal("10.50")
    assert row["uid"] == UUID(SAMPLE["uid"])

def test_deflate_codec_is_recorded_in_header():
    data = write_container([SAMPLE], codec="deflate")
    reader = fastavro.reader(io.BytesIO(data))
    assert reader.metadata["avro.codec"] == "deflate"
    assert [row["id"] for row in reader] == ["u-1"]

def test_reader_schema_renames_drops_and_defaults():
    (row,) = read_container(write_container([SAMPLE]), reader_schema=READER)
    assert row["created_at"] == SAMPLE["created"]
    assert row["nickname"] == "anon" and row["tier"] == "free"
    assert "birthday" not in row

def test_new_reader_field_without_default_raises():
    with pytest.raises(SchemaResolutionError):
        read_container(write_container([SAMPLE]), reader_schema=BAD_READER)

def test_schemaless_blob_needs_its_schema():
    raw = write_one(SAMPLE)
    assert raw[:4] != b"Obj\x01"
    with pytest.raises(ValueError):
        read_container(raw)
    assert read_one(raw)["id"] == "u-1"

def test_decimal_scale_beyond_schema_raises_but_shorter_is_padded():
    with pytest.raises(ValueError):
        write_container([dict(SAMPLE, balance=Decimal("10.555"))])
    (row,) = read_container(write_container([dict(SAMPLE, balance=Decimal("10.5"))]))
    assert row["balance"] == Decimal("10.50")
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.