Skip to main content

Parity

Differential semantic testing for changed computation.

Parity tries to disprove that a rewrite means the same thing as the implementation it replaces. It executes both versions over fixtures and generated edge cases, compares their observable behaviour under an explicit policy, attempts to shrink generated failures and preserves failing inputs for replay. Performance is measured only after correctness.

The initial adapters support pandas-to-Polars comparisons. The contracts and artifact format are engine-neutral so the same approach can cover other dataframe, numerical and dependency changes.

Parity is an evidence generator, not a proof of mathematical equivalence. Passing means no difference was found within the configured domain and search budget.

What one run gives you

  • Deterministic fixture checks plus property-based exploration and shrinking.
  • Cross-engine comparison of shape, columns, rows, dtypes, nulls, NaNs, signed zero, numeric tolerances, datetimes, returned exceptions and input mutation.
  • Isolated execution with per-case timeouts and separate Python environments when configured.
  • Per-worker Python, platform and dependency provenance, including explicitly named target distributions, so dependency drift is distinguishable from semantic drift.
  • Reproducible Arrow counterexamples, plus Parquet when representable, with a manifest and replay command.
  • Joint generation, shrinking, mutation tracking and replay for two- or three-frame joins and lookups.
  • Exact alignment of reordered outputs by unique scalar business keys, including composite keys.
  • Frame-local valid-domain constraints for sorted inputs and row-wise column relationships.
  • Same-input stability checks that fail closed when either implementation changes across repeated observations, even when both sides happen to agree with each other.
  • Bounded discovery of several distinct mismatch signatures in one campaign, with repeated confirmation before evidence is accepted.
  • Median runtime and peak-memory comparisons with optional regression gates.
  • Terminal, JSON, Markdown, JUnit and GitHub step-summary reporting.
  • A Python API, pytest fixture, CLI and composite GitHub Action.
  • Local execution. Parity has no hosted service, telemetry or required network connection.

Install and run

Parity requires Python 3.11 or later.

python -m pip install parity-check
parity init
parity check

parity init writes a runnable parity.toml and parity_example.py. Replace the generated functions with import targets for your existing and rewritten transformations, then make the equivalence policy match the real contract.

To scaffold an existing pair directly, supply the two targets and a fixture together. This mode writes only parity.toml; it does not create demo code or install environments:

parity init --reference orders.pandas_impl:transform \
  --candidate orders.polars_impl:transform \
  --fixture tests/fixtures/orders.parquet \
  --reference-adapter pandas --candidate-adapter polars \
  --record-distribution orders-lib --row-key order_id
parity doctor --config parity.toml
parity check

A minimal case looks like this:

version = 1
artifact_dir = ".parity"

[[cases]]
name = "orders"
fixture = "tests/fixtures/orders.parquet"
tags = ["critical", "migration"]

[cases.reference]
target = "orders.pandas_impl:transform"
adapter = "pandas"
record_distributions = ["orders-lib"]

[cases.candidate]
target = "orders.polars_impl:transform"
adapter = "polars"
record_distributions = ["orders-lib"]

[cases.comparison]
row_order = "keyed"
row_keys = ["order_id"]
column_order = "strict"
dtype = "compatible"
rtol = 1e-7
atol = 0.0

[cases.generation]
max_examples = 250
max_findings = 3
stability_repeats = 2
seed = 20260813
shrink = true

[cases.performance]
enabled = true
max_slowdown = 1.25
max_memory_ratio = 1.5
fail_on_regression = false

Pandas callables receive Arrow-backed extension dtypes by default. If legacy code specifically expects pandas' conventional NumPy/object dtypes, set pandas_input = "native" in that callable's table. Native materialization can widen nullable integers and collapse null with IEEE NaN, so the choice is saved in replay artifacts as part of the input contract.

Run one case, a tag, or the whole suite:

parity check
parity check --case orders --max-examples 1000
parity check --tag critical --json .parity/report.json --junit .parity/junit.xml

When Parity finds a mismatch it writes a directory like:

.parity/orders/20260813T184205Z-a18d7e91/
├── input.arrow
├── input.parquet  # when the schema is representable in Parquet
├── manifest.json
├── replay.json
└── result.json

Multi-input failures use opaque input-000.arrow, input-001.arrow, … files bound to their logical names in replay.json; the complete bundle is integrity-checked and replayed atomically.

Reproduce it without regenerating inputs:

parity replay .parity/orders/20260813T184205Z-a18d7e91

New artifacts bind replay to the Python and dependency versions observed inside both workers. Parity probes both environments before importing either callable and returns an error if that runtime contract drifted. Older artifacts remain replayable, but are visibly marked unverified because they did not record an environment contract.

Exit code 0 means every selected case passed, 1 means a semantic or enforced performance failure, and 2 means configuration or execution error.

Generated inputs

A fixture anchors Parity in a real shape and schema. An explicit schema makes the explored domain reviewable and reproducible:

[cases.schema]
min_rows = 0
max_rows = 50
unique_together = [["order_id"]]

[[cases.schema.constraints]]
kind = "sorted_by"
columns = ["order_id"]
descending = false
nulls = "last"

[[cases.schema.columns]]
name = "order_id"
dtype = "int64"
nullable = false
unique = true
minimum = 0

[[cases.schema.columns]]
name = "amount"
dtype = "float64"
nullable = true
minimum = -1000000.0
maximum = 1000000.0
examples = [0.0, -0.0, 0.1]

The generator targets empty and singleton frames, nulls, NaNs, infinities, signed zero, numeric boundaries, duplicate values, awkward strings and temporal boundaries where the schema permits them. A failing input is saved as data, not merely printed as a random seed; generated failures are minimized when shrinking is enabled and succeeds. sorted_by and row_comparison constraints let the search stay inside valid domains required by as-of joins, windows and interval calculations.

For outputs with stable identities, prefer keyed alignment to globally ignoring order:

[cases.comparison]
row_order = "keyed"
row_keys = ["customer_id", "period"]

Keys must be unique on both sides and match exactly; tolerances still apply to non-key payload columns. Parity fails closed when keys are missing, duplicated, nested or ambiguous under the selected name policy.

Pytest

The installed plugin exposes a parity assertion fixture:

def test_orders_migration(parity):
    parity.check("parity.toml", cases={"orders"})

Or verify live functions with the same API as parity.verify:

from parity import ComparisonPolicy, FrameSchema, RowComparison, SortedBy


def test_live_rewrite(parity, orders_schema: FrameSchema):
    parity.verify(
        pandas_transform,
        polars_transform,
        schema=orders_schema,
        reference_adapter="pandas",
        candidate_adapter="polars",
        comparison=ComparisonPolicy(row_order="ignore"),
    )

Python callers can construct FrameSchema(constraints=[SortedBy(...), RowComparison(...)]); configured TOML cases use the same validated models.

Use --parity-config and repeatable --parity-case options for CI selection, or an explicit @pytest.mark.parity(config="...", cases=[...]) override. See the pytest guide.

GitHub Actions

permissions:
  contents: read

steps:
  - uses: actions/checkout@v4
  - uses: leighshepperson/parity@v0.8.0
    with:
      config: parity.toml
      cases: orders,customers
      upload-artifact: "true"

The action always adds a redacted Markdown report to the job summary. The example explicitly opts into uploading .parity even on failure. Reports omit compared values, but replay artifacts contain generated or fixture-derived input values; leave upload disabled unless your repository's access and retention policy permits that data. See the Action guide.

Python API

from parity import check, verify

suite = check("parity.toml", cases={"orders"})
assert suite.passed

suite = verify(
    pandas_transform,
    polars_transform,
    fixture=sample,
    reference_adapter="pandas",
    candidate_adapter="polars",
    artifact_dir=".parity/live-orders",
)

check and verify return typed Pydantic result models. They do not terminate the process; the caller decides how to enforce the result.

Design boundaries

Parity answers: did these two executable contracts differ anywhere we looked? It does not decide which implementation expresses business intent, prove correctness outside the input domain, make a probabilistic test exhaustive, or justify weakening a comparison policy. Generated inputs are synthetic probes; include representative, non-sensitive fixtures for application-specific invariants.

The reference and candidate are untrusted project code from Parity's perspective. Process isolation limits accidental interference but is not a security sandbox. Run unknown code in a container or hardened CI runner. Full details are in Security and privacy and the threat model.

Documentation

Development status

Parity 0.8 is an alpha: useful on real migrations, but its configuration and artifact contracts may evolve before 1.0. Issues and small, synthetic reproduction cases are welcome.

Licensed under the Apache License 2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parity_check-0.8.0.tar.gz (239.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

parity_check-0.8.0-py3-none-any.whl (123.4 kB view details)

Uploaded Python 3

File details

Details for the file parity_check-0.8.0.tar.gz.

File metadata

  • Download URL: parity_check-0.8.0.tar.gz
  • Upload date:
  • Size: 239.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for parity_check-0.8.0.tar.gz
Algorithm Hash digest
SHA256 7c8fabe422107e783829d0265217315aecf3a4fe4ef69a069f8c05c789f0fd71
MD5 6577d06f8432e58bb23b99e55d2954a7
BLAKE2b-256 7b51404301e9f381f5f268458551e3a50d438dc19d4b4f9704fe454ba9e11827

See more details on using hashes here.

Provenance

The following attestation bundles were made for parity_check-0.8.0.tar.gz:

Publisher: release.yml on leighshepperson/parity

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file parity_check-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: parity_check-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 123.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for parity_check-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bac2344ff4339dd3885078a8d7691d47821adf6ae4411f4abc75338196c64f3e
MD5 b62be77d27f5c9daad1c15126a930497
BLAKE2b-256 b3c07d4afe1687af1a3c5477c09b17a974fc5cd0b4230fe56912b4f67d3224a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for parity_check-0.8.0-py3-none-any.whl:

Publisher: release.yml on leighshepperson/parity

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.21.0

2 files

0.20.1

2 files

0.20.0

2 files

0.19.1

2 files

0.19.0

2 files

0.18.1

2 files

0.18.0

2 files

0.17.0

2 files

0.16.0

2 files

0.14.3

2 files

0.14.2

2 files

0.14.1

2 files

0.14.0

2 files

0.13.0

2 files

0.12.0

2 files

0.10.0

2 files

0.9.2

2 files

0.9.1

2 files

This release

0.8.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page