ProvenPin Install

msgspec Structs with tagged unions, constraints and fast codecs: working versions (tested 2026-10-15)

Define msgspec Structs with tagged unions and Meta constraints, encode and decode JSON and MessagePack, inspect ValidationError paths, and convert plain dicts, all tested offline.

passed5 passed · 0 failed2026-10-15 13:31 UTC
Status
passed
Last passed
2026-10-15 13:31 UTC
Tests
5 / 5
Language
Python
Runtime
Python 3.13.16
pinned versions2
msgspec@0.22.0pytest@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 38742087302

The code

Exactly the files that passed. Your agent can pull them with get_recipe("msgspec-struct-validation") (install), or see them on GitHub.

requirements.txt
msgspec==0.22.0
pytest==9.1.1
models.py
from typing import Annotated

import msgspec

PositiveInt = Annotated[int, msgspec.Meta(gt=0)]
Label = Annotated[str, msgspec.Meta(min_length=1, max_length=40)]


class Circle(msgspec.Struct, tag="circle"):
    radius: PositiveInt


class Rect(msgspec.Struct, tag="rect"):
    width: PositiveInt
    height: PositiveInt


# Tagged union: the "type" field selects the variant during decode.
Shape = Circle | Rect


class Order(msgspec.Struct, kw_only=True, forbid_unknown_fields=True):
    id: int
    label: Label
    shapes: list[Shape] = msgspec.field(default_factory=list)


def parse_order(raw: bytes) -> Order:
    return msgspec.json.decode(raw, type=Order)


def order_from_dict(data: dict, strict: bool = True) -> Order:
    return msgspec.convert(data, Order, strict=strict)
tests/test_models.py
import msgspec
import pytest

from models import Circle, Order, Rect, Shape, order_from_dict, parse_order

RAW = (
    b'{"id":1,"label":"box","shapes":['
    b'{"type":"circle","radius":3},{"type":"rect","width":2,"height":4}]}'
)


def test_tagged_union_roundtrip_json_and_msgpack():
    order = parse_order(RAW)
    assert isinstance(order.shapes[0], Circle)
    assert isinstance(order.shapes[1], Rect)

    as_json = msgspec.json.decode(msgspec.json.encode(order), type=Order)
    as_mp = msgspec.msgpack.decode(msgspec.msgpack.encode(order), type=Order)
    assert as_json == order
    assert as_mp == order


def test_meta_constraint_error_reports_nested_path():
    bad = RAW.replace(b'"radius":3', b'"radius":0')
    with pytest.raises(msgspec.ValidationError, match=r"\$\.shapes\[0\]\.radius"):
        parse_order(bad)


def test_constructor_does_not_enforce_meta():
    # Meta constraints run on decode/convert, not on plain Struct __init__.
    assert Circle(radius=-1).radius == -1


def test_unknown_tag_and_unknown_fields_rejected():
    with pytest.raises(msgspec.ValidationError):
        msgspec.json.decode(b'{"type":"hexagon","radius":1}', type=Shape)

    extra = RAW.replace(b'"label":"box"', b'"label":"box","color":"red"')
    with pytest.raises(msgspec.ValidationError, match="color"):
        parse_order(extra)


def test_convert_from_dict_is_strict_by_default():
    data = {"id": "7", "label": "box", "shapes": [{"type": "circle", "radius": 2}]}
    with pytest.raises(msgspec.ValidationError):
        order_from_dict(data)

    order = order_from_dict(data, strict=False)
    assert order.id == 7
    assert order.shapes == [Circle(radius=2)]
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.