ProvenPin Install

jmespath queries over pydantic model dumps: working versions (tested 2026-10-15)

Dump pydantic models to JSON-mode dicts and filter or reshape them with jmespath expressions, covering datetime, Enum and list-root gotchas offline.

passed6 passed · 0 failed2026-10-15 21:56 UTC
Status
passed
Last passed
2026-10-15 21:56 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions3
jmespath@1.1.0pydantic@2.14.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 38813530570

The code

Exactly the files that passed. Your agent can pull them with get_recipe("jmespath-pydantic-filter") (install), or see them on GitHub.

requirements.txt
jmespath==1.1.0
pydantic==2.14.1
pytest==9.1.1
catalog.py
from datetime import datetime
from enum import Enum

import jmespath
from pydantic import BaseModel


class Category(Enum):
    book = "book"
    game = "game"


class Item(BaseModel):
    name: str
    price: float
    category: Category = Category.book
    created: datetime | None = None


class Catalog(BaseModel):
    name: str
    items: list[Item]


def records(items: list[Item]) -> list[dict]:
    # mode="json" turns datetime/Enum members into JSON primitives so jmespath
    # comparisons against literals actually match.
    return [item.model_dump(mode="json") for item in items]


def search(items: list[Item], expression: str, default=None):
    result = jmespath.search(expression, records(items))
    return default if result is None else result


def names_cheaper_than(items: list[Item], limit: float) -> list[str]:
    return search(items, f"[?price < `{limit}`].name", [])


def by_category(items: list[Item], category: Category) -> list[str]:
    return search(items, f"[?category == '{category.value}'].name", [])


def search_catalog(catalog: Catalog, expression: str, default=None):
    result = jmespath.search(expression, catalog.model_dump(mode="json"))
    return default if result is None else result
tests/test_catalog.py
from datetime import datetime

import jmespath

from catalog import (
    Catalog,
    Category,
    Item,
    by_category,
    names_cheaper_than,
    records,
    search,
    search_catalog,
)


def test_filter_by_numeric_literal():
    items = [Item(name="pen", price=1.5), Item(name="laptop", price=999.0)]
    assert names_cheaper_than(items, 100) == ["pen"]


def test_list_root_needs_star_projection():
    items = [Item(name="pen", price=1.5)]
    assert jmespath.search("name", records(items)) is None
    assert search(items, "[*].name") == ["pen"]


def test_datetime_needs_json_mode():
    item = Item(name="pen", price=1.0, created=datetime(2024, 1, 1, 12, 0, 0))
    expr = "[?created == '2024-01-01T12:00:00'].name"
    # default model_dump keeps a datetime object, which never equals the literal
    assert jmespath.search(expr, [item.model_dump()]) == []
    assert search([item], expr) == ["pen"]


def test_enum_needs_json_mode():
    item = Item(name="pen", price=1.0, category=Category.game)
    expr = "[?category == 'game'].name"
    assert jmespath.search(expr, [item.model_dump()]) == []
    assert search([item], expr) == ["pen"]


def test_missing_keys_are_silent():
    items = [Item(name="pen", price=1.0)]
    assert search(items, "[?missing == 'x'].name", []) == []
    assert search(items, "[0].missing") is None
    assert by_category(items, Category.game) == []


def test_nested_catalog_dump():
    catalog = Catalog(
        name="shop",
        items=[Item(name="pen", price=1.5), Item(name="laptop", price=999.0)],
    )
    assert search_catalog(catalog, "items[?price < `100`].name") == ["pen"]
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.