Skip to main content

ematix-probe

Declarative data-quality + load probes for Python. Rust + tokio under the hood.

Assert on the shape of your data and the behavior of your services with one decorator. Postgres, DuckDB, Parquet (local + S3), HTTP — same primitives, one runner.

from ematix_probe import probe, source

@probe.data(
    source=source.postgres("postgres://user:pass@host/db"),
    table="events",
    schema="analytics",
)
def events_are_healthy(t):
    t.row_count(at_least=1_000, at_most=1_000_000)
    t.column("event_id").not_null().unique()
    t.column("received_at").not_null()
pip install ematix-probe
ematix-probe run probes.py

Why ematix-probe

  • Same primitives, every backend. Postgres, DuckDB, Parquet (local
    • S3), HTTP. Cross-engine consistency tests cover the SQL-pushdown vs. Arrow-scan paths.
  • Three runners, one decorator. ematix-probe run, your pytest suite, or directly from Python — same probe code, same verdicts.
  • ematix-flow integration. Probe a target table from inside a flow pipeline via probe_from_table() — the verdict joins the pipeline's run-history row.
  • Run history, opt-in. Append every verdict to a sqlite file and query trends across runs with --run-history-db.

Status: v0.1.2 on PyPI as ematix-probe. PI-1 closed — data probes, load probes, pytest plugin, and ematix-flow integration are shipped and stable.


Table of contents

  1. Install
  2. Sources
  3. Data probes
  4. Assertions
  5. Load probes
  6. pytest plugin
  7. ematix-flow integration
  8. Run history
  9. CLI
  10. Python API
  11. What's shipped
  12. Development
  13. License

Install

pip install ematix-probe

The core install ships every adapter, the ematix-probe CLI binary, and the pytest plugin (auto-loaded via the pytest11 entry point — no pytest_plugins wiring required).

Optional extras

Extra What it adds Install
dev Test runner + linters + maturin + testcontainers (Postgres / LocalStack) for the local development workflow. pip install "ematix-probe[dev]"

The runtime surface (CLI, pytest plugin, every data + load adapter) needs no extras. To build from source, see Development at the bottom.


Sources

Sources are the first thing to set up. Every data probe references a source by call-site; ematix-probe doesn't ship a connection registry the way ematix-flow does — credentials live in the URL or environment variables you pass in.

from ematix_probe import source

postgres   = source.postgres("postgres://user:pass@host/db")
duckdb     = source.duckdb(":memory:")
parquet    = source.parquet("/path/to/file.parquet")
s3_parquet = source.s3_parquet(
    bucket="analytics",
    key="dim/customers.parquet",
    region="us-east-1",
    # endpoint_url= is optional — set it for LocalStack / MinIO.
)

Sources are inert factories — no connection is opened until the probe runs.


Data probes

A data probe declares a target table + the assertions it must satisfy. The decorator returns a DataProbe object you can run directly, collect via pytest, or list / explain through the CLI.

from ematix_probe import probe, source

@probe.data(
    source=source.postgres("postgres://localhost/warehouse"),
    table="dim_customers",
    schema="public",
)
def customer_dim_quality(t):
    t.column("customer_id").not_null().unique()
    t.column("email").not_null().regex(r".+@.+\..+")
    t.column("status").is_in(["active", "churned", "trial"])
    t.column("age").between(0, 120)
    t.row_count(at_least=1_000, at_most=10_000_000)
    t.freshness("updated_at", within="24h")

Run it directly:

report = customer_dim_quality.run()
print(report.verdict)              # "pass" | "fail" | "error"
for a in report.assertions:
    print(a.name, a.verdict, a.message)

Or write it to JUnit / JSON for CI:

from ematix_probe.report import write_junit, write_json

write_junit([report], "build/probe-results.xml")
write_json([report], "build/probe-results.json")

The same probe can be picked up by pytest with no extra wiring — see pytest plugin.


Assertions

The assertion vocabulary is the same across every adapter; the adapter chooses pushdown SQL vs. an Arrow scan internally.

Assertion Meaning
t.column(c).not_null() Every value in c is non-NULL.
t.column(c).unique() Every value in c is unique (NULLs allowed).
t.column(c).between(low, high) Every value in c lies in [low, high] inclusive.
t.column(c).regex(pattern) Every non-NULL value matches pattern (Postgres POSIX flavor on the SQL path; regex crate on the scan path).
t.column(c).is_in([...]) Every value is in the allowed set.
t.row_count(at_least=, at_most=) Table row count falls in [at_least, at_most] (open ends supported).
t.freshness(c, within="24h") The most recent value of c is no older than within (h / m / s / d).
t.percentile_between(c, p=99, low=, high=) The pᵗʰ percentile of c lies in [low, high]. Scan-path only.
t.cardinality_between(c, low=, high=) The count of distinct values in c lies in [low, high]. Scan-path only.
t.schema_match({col: type, ...}) The target's column types match the declared mapping. Scan-path only.

Each assertion produces one AssertionResult with verdict{"pass", "fail", "error"} and an actionable message on non-pass.


Load probes

Load probes drive a target with synthetic traffic and assert on the resulting samples. v0.1 ships HTTP and Postgres SQL targets under either constant-rate (open-model) or virtual-user (closed-model) schedulers. The Python surface is Rust-only in v0.1 — Python decorators land in v0.2.

Drive the engine directly today:

# Pseudocode mirroring the Rust API; full Python load surface ships in v0.2.
from ematix_probe import load
plan = load.http_plan(
    target=load.HttpTarget.get("https://api.example.com/health"),
    duration="60s",
    mode=load.ConstantRate(rps=100),
    warmup="10s",
    assertions=[
        load.p99_under("latency_ms", 200),
        load.error_rate_below(0.005),
        load.throughput_above(95),
        load.status_code_in([200, 304]),
    ],
)

Or use the Rust API directly via cargo run --example load_probe_demo / --example postgres_load_demo.


pytest plugin

pip install ematix-probe registers a pytest11 plugin; pytest auto-loads it. Any @probe.data instance at module top-level becomes one pytest test node per assertion:

# tests/test_warehouse_quality.py
from ematix_probe import probe, source

@probe.data(
    source=source.postgres("postgres://localhost/warehouse"),
    table="dim_customers",
)
def customer_dim_quality(t):
    t.column("customer_id").not_null()
    t.column("email").regex(r".+@.+\..+")

pytest -v reports:

tests/test_warehouse_quality.py::customer_dim_quality::customer_id.not_null PASSED
tests/test_warehouse_quality.py::customer_dim_quality::email.regex          FAILED

The probe runs once per pytest collection — assertion fan-out caches the RunReport so N assertions don't multiply the underlying database / HTTP work.


ematix-flow integration

Sibling project ematix-flow ships declarative table classes; ematix-probe consumes them through a duck-typed shim:

from ematix_probe.flow import probe_from_table
from ematix_probe import source

# CustomerDim is any class exposing __tablename__, optional
# __schema__, and an iterable `columns` with .name / .nullable /
# .primary_key — ematix-flow's ManagedTable matches out of the box.
quality = probe_from_table(
    CustomerDim,
    source=source.postgres("postgres://warehouse/db"),
    extend=lambda t: t.column("email").regex(r".+@.+\..+"),
)

Auto-derived: not_null on every non-nullable column + unique on each primary key. extend lets you layer extras via the same fluent API. ematix-probe has zero hard dependency on ematix-flow — the protocol-typing means any conforming class participates.


Run history

Opt-in sqlite persistence. Pass --run-history-db <path> to the CLI, or use the API directly:

from ematix_probe.run_history import RunHistory

h = RunHistory("history.sqlite")
h.record(probe.run())

Schema is runs (one row per probe execution) + assertions (one row per assertion result, joined by run_id), tagged with PRAGMA user_version = 1. Designed as the substrate for v0.2 drift detection — additive columns only, no renames.


CLI

ematix-probe run <path>           # discover + run probes; non-zero on fail
ematix-probe run <path> --run-history-db history.sqlite

ematix-probe list <path>          # enumerate probes, no execution
ematix-probe explain <path> <probe>   # print compiled plan for one probe
ematix-probe doctor               # environment health check

<path> points at any Python file containing @probe.* decorators. The CLI imports the file, finds module-level DataProbe attributes, runs each, and exits non-zero if any verdict isn't pass.


Python API

The package exposes:

  • probe.data(source=..., table=..., schema=None) — data-probe decorator.
  • source.postgres / duckdb / parquet / s3_parquet — source factories.
  • DataProbe.run() — execute a probe, return a RunReport.
  • report.write_junit(reports, path) / report.write_json(reports, path) — CI reports.
  • flow.probe_from_table(cls, source=, extend=) — ematix-flow shim.
  • run_history.RunHistory(path) — opt-in sqlite persistence.
  • pytest_plugin — auto-loaded by pytest; not imported directly.

The Rust load-probe surface (engine::load, adapters::load::http, adapters::load::postgres) is exposed through the workspace's example crates today; the Python load surface lands in v0.2.


What's shipped

Data probes: Postgres, DuckDB, local Parquet, S3 Parquet. Assertions: not_null, unique, between, regex, enum, row_count, freshness, percentile_between, cardinality_between, schema_match.

Load probes (Rust API): HTTP + Postgres SQL targets; constant-rate (open-model) and virtual-user (closed-model) schedulers. Assertions: p99_under, error_rate_below, throughput_above, status_code_in. Sample-window warmup filtering. Per-tick Samples shared across HTTP and SQL paths through one evaluate_load entry point.

Reporting: JUnit XML + JSON writers; pytest plugin with per-assertion test nodes; opt-in sqlite run history.

Out of v0.1 (planned for v0.2): async PyO3 (async def probe functions + pyo3-asyncio integration), drift detection, distributed load generation, backends beyond the v0.1 set.


Development

# Build the Rust workspace (core + CLI + Python extension crate)
cargo build --release

# Build + install the Python extension into a venv
python -m venv .venv && source .venv/bin/activate
pip install maturin
maturin develop --release

# Run tests
cargo test --workspace                    # default + integration (Docker)
pytest                                    # full Python suite
coverage run -m pytest && coverage report --fail-under=90

Process docs:

Sibling project: ematix-flow.


License

Apache-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

ematix_probe-0.1.3.tar.gz (123.0 kB view details)

Uploaded Source

Built Distributions

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

ematix_probe-0.1.3-cp314-cp314-manylinux_2_28_x86_64.whl (21.2 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ x86-64

ematix_probe-0.1.3-cp314-cp314-macosx_11_0_arm64.whl (16.3 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

ematix_probe-0.1.3-cp313-cp313-manylinux_2_28_x86_64.whl (21.2 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64

ematix_probe-0.1.3-cp313-cp313-macosx_11_0_arm64.whl (16.3 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

ematix_probe-0.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (21.2 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

ematix_probe-0.1.3-cp312-cp312-macosx_11_0_arm64.whl (16.3 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

ematix_probe-0.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (21.2 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64

ematix_probe-0.1.3-cp311-cp311-macosx_11_0_arm64.whl (16.4 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

File details

Details for the file ematix_probe-0.1.3.tar.gz.

File metadata

  • Download URL: ematix_probe-0.1.3.tar.gz
  • Upload date:
  • Size: 123.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ematix_probe-0.1.3.tar.gz
Algorithm Hash digest
SHA256 5c737ffc2aabfecd24f16cf3cff4fe5230fe894e1e4d899a2efcde0dd981c0ac
MD5 331d73fab49442d5b620ff9f01622764
BLAKE2b-256 f595f5a9f56959137265587277d25713257c73a9a1c6d71bb7a57e0e49911311

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3.tar.gz:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp314-cp314-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp314-cp314-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c8df43e101c6a867a5941973d4f0236377bb43500f1045e628e79c2700f00565
MD5 e63d3f9b7f2fef9d59804d396afeb535
BLAKE2b-256 68e601605571342a489ba561b5c6df6d4dc37dd3bfb0a1de32e7f30b230c4898

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp314-cp314-manylinux_2_28_x86_64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 75611dc9572b70df0d148083bce27bc0bf988aaef83117dfd876326ed3ce6a92
MD5 2f6e5426e2afa8a9edcd4d30d5363049
BLAKE2b-256 8c45ce909d9dbeb18d6664f5f4465b7a76536bba1317d9ab24409ed085b2d423

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp314-cp314-macosx_11_0_arm64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp313-cp313-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e1b065f13712d8a611583b007c68fbd4b57ae2e94c5bef6465adc80ddc4a075a
MD5 c0521704248e06b08e66049f511747ff
BLAKE2b-256 21d56c3c2e345e8070db04e929bea6717c425fc0904c760ca4dfb0c974ff66a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp313-cp313-manylinux_2_28_x86_64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 15844f2e237689e760bc77024455dedc87d4588a95dc8061737c18b33eece013
MD5 14509cfbff2005a4e873e2060d6eee8f
BLAKE2b-256 25b1dfca1587ac26a78fe6e41d6252be106b2892d84db69b71a3da6c8268c628

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7e5be174a0e1adfb872fe6fcd4595ea23607f349e9b3c37a7a230ff68283f4c9
MD5 60c5d894e78ab9981085c794d19dacc7
BLAKE2b-256 620ec626ce98322d71c4f84d79de507ae214a3d6ef677bd8adf6f8d4a1c75141

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp312-cp312-manylinux_2_28_x86_64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6883d9c26621a470c2f7aecd500254b8b300e888f2dcc3532aa367266840edb4
MD5 4d5498d412f7619a381fda19581b93d3
BLAKE2b-256 8a05a635c214ab5d54cab26de307bb3d3a9df5fcb1361803809f81723d28147c

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a48a7c30fc4c21e4c6f0eaf5e292b4680c9cba43cf36cda9ce9af506c0a72428
MD5 66a36e837ddee3bac7a390cf62ad23ac
BLAKE2b-256 af70cbbd43b943c45984d99470d4f146fbface3ff3ad098f30a03e2f88b6d2d6

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp311-cp311-manylinux_2_28_x86_64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

File details

Details for the file ematix_probe-0.1.3-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for ematix_probe-0.1.3-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 40425d6dcb81415805ddc5f0cc8c8618c246510e552de9850ab39f4392c0cbeb
MD5 40310dbbaab8e7c0cd89f0678ed0bee2
BLAKE2b-256 4d62a2758669408dfad7278ea4ac627a23f4bcb1c0e878bd24e045156ed8095c

See more details on using hashes here.

Provenance

The following attestation bundles were made for ematix_probe-0.1.3-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: release.yml on ryan-evans-git/ematix-probe

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

Release history Release notifications | RSS feed

This release

0.1.3 This release

9 files

0.1.2

9 files

0.1.1

9 files

0.1.0

8 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