Skip to main content

makotest

Test & simulation toolkit for German market-communication (MaKo) platforms.

Builds regulator-conformant EDIFACT, simulates the counterparties a MaKo platform talks to — in EDIFACT, so a test can feed the answer back — and asserts on both wire contracts it exposes: the messages and the event stream.

makotest targets mako first but is not mako-specific. Everything it drives is public (EDIFACT over AS4, REST, CloudEvents), so it can exercise any MaKo implementation.

from makotest import antwort_obligation, malo_from_base, validate_edifact

malo_from_base("5123869601")  # '51238696012' — BDEW check digit applied

o = antwort_obligation(55001)  # what a Netzbetreiber owes on an Anmeldung
o.clock_time  # '11:00' — a clock time, not n × 24 h
o.due_at("2026-03-02T09:00:00Z")  # '2026-03-03T11:00:00+01:00'

validate_edifact(utilmd_bytes, "2026-10-01").is_valid  # MIG + AHB + semantic
pip install makotest                  # no runtime dependencies
pip install 'makotest[hypothesis]'    # + property-based strategies

Wheels are abi3 (abi3-py311) — one wheel serves Python 3.11 and later.

The same answers are reachable from a shell, for whoever is holding a real message rather than writing a test:

$ makotest validate inbound.edi --on 2026-04-01
$ makotest frist 55001 --received 2026-03-02T09:00:00Z
$ makotest id 9900357000004      # → satisfies NEITHER check-digit procedure

Why

Deadlines are legal obligations, message content is defined by AHB rule tables, and a wrong Prüfidentifikator is a compliance defect rather than a bug. A test has to express "this is what the counterparty is entitled to send, and this is what we owe them by when" — which a curl script cannot.

Two failure modes shape the whole design, because both produce a green suite that proves nothing:

  • a message whose Prüfidentifikator has no AHB rules validates — having checked nothing;
  • an assertion naming an event type the platform does not declare finds no such event — forever.

Every validation report therefore carries rules_applied, and every event assertion resolves the type against the platform's own catalog first.

Design

One source of truth. EDIFACT construction and validation, identifier check digits, the Werktag calendar, the published answer Fristen and the CloudEvents catalog come from the same Rust crates the platform runs, through PyO3 bindings. None is reimplemented in Python: a second implementation drifts from the BDEW documents at the first Formatumstellung, and a harness that disagrees with the system under test about what is valid — or about when a Frist expires — is worse than none.

The rule: anything a regulator defines in a table is Rust; anything shaped by test ergonomics is Python.

Concern Home
EDIFACT build + MIG/AHB/semantic validation, release per format version Rust — edi-energy
Identifier check digits (MaLo, MP-ID, EIC, §8.2 resources) Rust — rubo4e
Werktag calendar, acknowledgement clocks, answer Fristen Rust — mako-fristen
CloudEvents type catalog and subscription matcher Rust — mako-events
Counterparty behaviour, EPEX curves, fixtures Python

Because validation runs the platform's own AHB engine, makotest proves process and integration behaviour — not format conformance. The BDEW reference examples remain the authority for that.

Deterministic by construction. Every generator is seeded, every clock is injected, and the format version is an argument rather than a read of today's date. Two runs of the same scenario produce byte-identical EDIFACT.

Framework-agnostic core, pytest on top. Only makotest.plugin imports pytest, so a demo and a CI test drive the same code path.

Two failures, two exceptions. AssertionError means the system under test is wrong; ValueError means the test is.


A tour

Fristen have three shapes, so ask the table. "A Werktage Frist expires at 17:00 Berlin" is true of the WiM MSB-Wechsel windows and of nothing else — GPKE states a clock time on the 1. Werktag after the ÜT, GeLi Gas the end of the n-th Werktag.

assert_deadline_is(response["deadline"], received=received, pid=55001)
assert_frist_met(55001, received=received, answered_at=answer["sent_at"])

The send date picks the format version. Pinning a release by hand and validating on a date where another is in force reports the mismatch rather than the message.

msg = build_utilmd(
    55001,
    sender=LF,
    receiver=NB,
    on="2026-04-01",
    transactions=[
        UtilmdTransaction("VORGANG-1", locations=[("melo", melo)], dates=[("92", start)])
    ],
)
wire = build_interchange(
    sender=LF, receiver=NB, dar="REF1", messages=[msg], on="2026-04-01"
)
assert_edifact_valid(wire, on="2026-04-01")

Counterparties answer in EDIFACT — or badly, or not at all. The unhappy modes are the ones worth having: a platform that only ever sees a punctual, conformant partner has never had its Fristüberwachung exercised.

def test_nb_bestaetigt(nb_sim, anmeldung):
    nb_sim.on(55001).bestaetigung(process_dates=[("92", "20260501")])
    reply = nb_sim.receive(anmeldung, received_at="2026-03-02T09:00:00Z")

    assert reply.pid == 55002  # the AHB answer PID — never Anfrage + 1
    platform.ingest(reply.business)  # a rendered interchange, not a dict


def test_frist_faellt(nb_sim, anmeldung):
    nb_sim.on(55001).timeout()  # no answer, not even a CONTRL
    assert not nb_sim.receive(anmeldung)


def test_verspaetete_antwort(nb_sim, anmeldung):
    nb_sim.on(55001).bestaetigung(delay_werktage=3)  # right message, wrong day
    reply = nb_sim.receive(anmeldung, received_at="2026-03-02T09:00:00Z")
    assert reply.answered_at > reply.due_at

An interchange carries several messages, each its own Vorgang — the reply answers all of them, and reply.pid raises rather than speaking for one.

Events are the other wire contract.

assert_event_emitted(webhook_bodies, "de.mako.process.*", subject=malo)
assert_no_event_emitted(webhook_bodies, "de.mako.aperak.timeout")

Strategies draw values the platform accepts. A random 11-digit string is a valid MaLo one time in ten and a random 16-character string is essentially never a valid EIC, so a hand-rolled strategy spends its budget on the rejection path.

@given(malo=malo_ids(), pid=pruefidentifikatoren(message_type="UTILMD"))
def test_every_utilmd_roundtrips(malo, pid): ...

EPEX days are Europe/Berlin days, so the DST days carry 92 and 100 quarter-hourly MTUs rather than 96 — and negative prices are supported, which §51 EEG and §41a EnWG dynamic tariffs both need.

EpexGenerator(seed=42).day("2026-06-21", profile="solar_glut", negative_hours=6)

The full reference — every builder, assertion, strategy and simulator, with the Fundstelle behind each rule — is in the mako documentation.


pytest plugin

Registered through the pytest11 entry point — no conftest.py wiring.

Fixtures nb_sim, biko_sim, imsys_sim, epex, frozen_clock, makotest_seed, makotest_on, mako_endpoint
Markers @pytest.mark.regulatory("GPKE Teil 2"), @pytest.mark.requires_docker
Options --makotest-on ISO_DATE, --makotest-seed N, --mako-endpoint URL

--makotest-on pins the format version for the whole suite, so re-running on a future date shows what the next Formatumstellung breaks. Every dated fixture takes it, so one test is never about two days. --mako-endpoint names a running deployment; bring your own HTTP client.

--hypothesis-profile=makotest selects a registered profile with deadline=None and derandomize=True — a strategy here draws through the Rust core, and Hypothesis' 200 ms per-example deadline is written for pure functions.


Development

makotest is a member of the mako Cargo workspace and builds with maturin:

just test-makotest      # maturin develop + pytest
just lint-makotest      # ruff check + format check
just build-makotest     # release wheel

pyo3/extension-module is deliberately not a Cargo feature — maturin enables it at build time. Declaring it would make cargo test --workspace --all-features link the Rust test harness against it and fail on undefined Python symbols. CI exercises both paths.

abi3-py311 sets a floor of Python 3.11, so every workspace-wide cargo command builds this crate against an interpreter. The workspace pins PYO3_PYTHON to .venv/bin/python in .cargo/config.toml; create that venv once and the rest of the workspace builds:

python3.11 -m venv .venv    # or any ≥ 3.11

Without it PyO3 falls back to the first python3 on PATH — still 3.9 on macOS — and the whole workspace fails to build on a message about a crate nobody was working on.

py.typed ships with the wheel, so _native.pyi is the only thing a consumer's type checker sees; a test pins it against the compiled module in both directions.

Scope

The Marktpartner simulator carries no AS4 transport of its own: it is a plain object with receive(), so a transport layers on top instead of being a dependency of the many tests that do not need one.

A counterparty is modelled once it has a consumer. One written ahead of its consumer encodes guesses about an interface nobody has implemented, and to the next reader those guesses are indistinguishable from requirements.

The package version tracks workspace.package.version through Cargo.toml, so the wheel and the crates it binds can never report different versions.

Licence

MIT OR Apache-2.0, matching mako.

Release files for makotest 0.16.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for makotest 0.16.0
File Size Uploaded
makotest-0.16.0.tar.gz 940.7 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for makotest 0.16.0
File
makotest-0.16.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
makotest-0.16.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
makotest-0.16.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details
makotest-0.16.0-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details
makotest-0.16.0-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details

Total release size: 14.2 MB

Release files / makotest-0.16.0.tar.gz

Download URL makotest-0.16.0.tar.gz
Size 940.7 kB
Tags Source
SHA-256 checksum
How to use checksums
f5a5dbe9f7d39ad4d6210acd498c3e0f1cb34202896072a4c03b8ec225d1797c
BLAKE2b-256 checksum
How to use checksums
89d1e2678ba0b132f2ade12f86db32b089b909f111011a86cda8cb537cfea921
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.

Transparency log

Release files / makotest-0.16.0-cp311-abi3-win_amd64.whl

Download URL makotest-0.16.0-cp311-abi3-win_amd64.whl
Size 2.8 MB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
e2044563d3e9c189876bbde3338f474bdc8394528e73726d5ce553933ebda00d
BLAKE2b-256 checksum
How to use checksums
078fea925cf386399868298ab0b14cfb601f5dc235c373ff67cd7c73dc6070a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.

Transparency log

Release files / makotest-0.16.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL makotest-0.16.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.6 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
842cdac6915a81569515e2176cf0756bf652bc2f946c29515b9938cbdfb5d866
BLAKE2b-256 checksum
How to use checksums
a23c451f4678a0f4e4a71426aa5c2efa891b215dd5df18210df12aa7b3c4879b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.

Transparency log

Release files / makotest-0.16.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL makotest-0.16.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.7 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
c8d57b17c3858a9364446367523d833b511ead10beb132dc163d2444e745bf00
BLAKE2b-256 checksum
How to use checksums
e9b6f3da6d4c52848acb0998569d23403f599a318fc0da035a9e35c9729685bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.

Transparency log

Release files / makotest-0.16.0-cp311-abi3-macosx_11_0_arm64.whl

Download URL makotest-0.16.0-cp311-abi3-macosx_11_0_arm64.whl
Size 2.5 MB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a880bd2eaf2d6fc6701696c4a90d7d92ca7d979df09abae40ef3c010f05bfde4
BLAKE2b-256 checksum
How to use checksums
fc9370db4c5c4dd15d4038ea0ff9cccb749d5b6a2e200b2de5693fa17f4a8925
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.

Transparency log

Release files / makotest-0.16.0-cp311-abi3-macosx_10_12_x86_64.whl

Download URL makotest-0.16.0-cp311-abi3-macosx_10_12_x86_64.whl
Size 2.5 MB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
d6dbbfed6b94074540fd75b6fbd369cdc1cdfc3160b239d3a600ec1f24cfc47e
BLAKE2b-256 checksum
How to use checksums
a026133c26bd01cf6c8dd51432680724727d74d1ff50e2227238f62444d3a74e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.

Transparency log

Release history Release notifications | RSS feed

0.20.0

6 release files

0.18.0

6 release files

0.17.0

6 release files

This release

0.16.0 This release

6 release 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