Replay one application contract before and after a dependency bump. Bumpcheck exits nonzero when returned JSON, Pydantic validation errors, warnings, or captured output change.
Install uv, then save
the behavior your application depends on as contract.py:
from collections.abc import Mapping
from typing import Annotated
from pydantic import BaseModel, ConfigDict, Field
MetadataKey = Annotated[str, Field(max_length=3)]
class Model(BaseModel):
model_config = ConfigDict(strict=True)
metadata: Mapping[MetadataKey, str]
def run():
return Model(metadata={"toolong": "b"}).model_dump(mode="json")Run it against two versions. uvx fetches Bumpcheck, so there is no separate
tool installation step:
$ uvx bumpcheck check contract.py \
--baseline pydantic==2.10.6 \
--candidate pydantic==2.11.1 \
--python-version 3.12
BASELINE pydantic=2.10.6, pydantic-core=2.27.2 @ .../python
CANDIDATE pydantic=2.11.1, pydantic-core=2.33.0 @ .../python
CHANGED contract: ValidationError[string_too_long] -> return {"metadata":{"toolong":"b"}}This reproduces pydantic/pydantic#11681.
Exit code 0 means unchanged, 1 means behavior changed, and 2 means the capture
was not trustworthy. Install a persistent command with
uv tool install bumpcheck if you prefer it over uvx.
Run Bumpcheck from PyPI and install the checkout into both comparison environments:
jobs:
bumpcheck:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
python-version: "3.12"
version: 0.12.5
- run: |
uvx --from bumpcheck==0.2.1 \
bumpcheck check contract.py \
--baseline pydantic==2.10.6 \
--candidate pydantic==2.11.1 \
--with . \
--python-version 3.12Tracewright's Pydantic compatibility job uses this pattern to replay four JSON Lines fixture inputs against two Pydantic versions.
A contract is a normal Python file exposing either run() or run(value).
Return a JSON-compatible value that represents the behavior your application
depends on.
Use --with . to install the current project into both isolated environments:
bumpcheck check contract.py \
--baseline pydantic==2.10.6 \
--candidate pydantic==2.11.1 \
--with .Repeat --with for shared dependencies. Use --watch DIST[:MODULE] to add to
the default pydantic and pydantic-core provenance. Use --only-watch
instead when the named distributions should replace those defaults.
For example, the issue-derived integration suite detects the
Path / Path fix in Monty:
bumpcheck check examples/regressions/monty_path_join.py \
--baseline pydantic-monty==0.0.19 \
--candidate pydantic-monty==0.0.20 \
--only-watch pydantic-monty:pydantic_montyFor real payloads, define run(value) and pass --inputs. Bumpcheck accepts a
JSON array, .jsonl or .ndjson, or a Pydantic Evals JSON document containing
a cases array. It preserves case names or JSON Lines source line numbers and
hashes the case and input bytes before comparing results.
If environments already exist, use --baseline-python and
--candidate-python instead of requirement targets. Use --json for a
machine-readable report and --exact when warning messages, exception messages,
stdout, and stderr must also match.
Bumpcheck is a focused pre-upgrade gate, not a test runner or an API/schema diff. It is useful for data-dependent validation and serialization behavior that ordinary dependency tooling does not observe.
Cases are trusted code. The subprocess boundary enforces a timeout but is not a security sandbox. Receipts contain local interpreter and module paths, so review JSON output before sharing it. Imported project files are not hashed; run from an immutable revision when they are part of the contract.
Bumpcheck was previously named Pydantic Canary. The bumpcheck distribution
installs a temporary pydantic-canary command alias, but new code should import
bumpcheck and use the bumpcheck command.
uv run --isolated --no-project --no-config --no-cache --with . -- \
python -B -m unittest discover -s tests -v
uv run --isolated --no-project --no-config --with ruff==0.16.4 -- ruff check .
uv build