Skip to main content

edutap.data_models

Shared contracts of the eduTAP packages: controlled vocabularies, the Kafka message contract, and settings building blocks whose field names are meant to be identical in every container.

Why this exists

Three duplicates, all of them found in the field.

Vocabularies in three diverging copies. PassLifecycleState existed with eight values in lmu_edutap_common and with six in both lmu_edutap_full_view and edutap.data_provider. WalletType existed in four spellings.

The message contract. Header block, key rule and dead letter envelope have to be identical in six packages. Six copies diverge, and the divergence is invisible until a consumer quietly stops recognising a message.

Settings that should mean the same thing everywhere. environment is the clearest case: the same field name in every container, populated per service by the Swarm compose file, and read by the error tracker and the trace exporter alike.

What belongs here

Module Contents
vocabulary WalletType, IssuanceState, HolderState, InstanceState, FieldKind, Provider
messaging header names and construction, logical topic names, schema and action names, the pass.command body, DLQ naming
settings ServiceSettings, KafkaSettings — mixins, not a finished class
runtime the shared Kafka runtime: consume(), publish(), Unprocessable, DeadLetterQueue, serve()

The runtime package is the odd one out and says so. It is behaviour rather than a contract in the way a vocabulary is, and it is here because the behaviour is what several services have to agree on: the commit order, the dead letter naming, what a stop signal does. It is written against protocols, so the Kafka driver is not a dependency of this package — build_consumer() and build_dead_letter_producer() stay in the service, with its settings class. See the design record of 2026-08-11.

The pass lifecycle is spelled on three axes. IssuanceState is what the issuer did or wants and exists with no exemplar at all; InstanceState is what one exemplar at the holder is doing; HolderState is the summary of the second and is derived, never set. PassLifecycleState conflated the first two and is superseded — it is still reachable under edutap.data_models.vocabulary and warns when used.

Error tracking and trace export live in edutap.observability_settings, not here. The options that decide what may leave a process were chosen against measurements and are worth nothing apart from the sentry_sdk.init() call that applies them.

What does not

Table definitions of individual packages. edutap.db_definitions collects those through entry points, and that collection is what makes one package answerable for one schema. Move them here and nobody owns a schema any more.

Anything LMU-specific. lmu_edutap_common stays what it is; the edutap.* packages must not depend on it.

Dependency direction

This package depends on nothing from the eduTAP estate — only on pydantic, pydantic-settings and structlog. Everything else may depend on it. A library that knows about services is not a library.

structlog is the third and had to be argued for: the runtime loop's log records are structured — topic, partition, offset, reason — and that structure is their entire value in operation. Every consumer already uses it.

Usage

from edutap.data_models import WalletType
from edutap.data_models.messaging import TOPIC_PASS_STATE, build_headers
from edutap.data_models.settings import KafkaSettings


class MySettings(KafkaSettings):
    model_config = {"env_prefix": "EDUTAP_MY_SERVICE_"}


settings = MySettings()                      # aborts without EDUTAP_MY_SERVICE_TOPIC_PREFIX
topic = settings.topic(TOPIC_PASS_STATE)     # edutap.production.pass.state

Reaching a broker that requires mTLS is the same three settings fields everywhere — ca_file, cert_file, key_file, plus password for an encrypted key — and one function that turns them into driver options:

from edutap.data_models.runtime import transport_options

AIOKafkaConsumer(topic, bootstrap_servers=..., **transport_options(settings))

It returns {} when nothing is configured, which is the development case against a broker without TLS. Configuring only some of the three raises instead of falling back: against an SSL-only broker a silent fallback does not produce an unencrypted connection, it produces one that fails during the handshake — and that error names the broker rather than the secret nobody mounted.

The context is built from the standard library, so the Kafka driver stays out of this package's dependencies.

Sending is one call, and it builds the envelope rather than accepting one:

from edutap.data_models import messaging
from edutap.data_models.runtime import producer_options, publish

producer = AIOKafkaProducer(**producer_options(settings.bootstrap_servers),
                            **transport_options(settings))

await publish(
    producer,
    settings.topic(messaging.TOPIC_PASS_COMMAND),
    key=command.pass_id.encode(),
    value=command.model_dump_json().encode(),
    producer_name="my_service",
    schema=messaging.SCHEMA_PASS_COMMAND,
    event_id=str(uuid4()),
    occurred_at=datetime.now(tz=UTC),
    action=messaging.ACTION_CREATE,
)

There is deliberately no way to publish without the header block. It was available as a separate call for two minor versions and the estate's one producer never made it — a body the broker accepts and every consumer parks, with success reported at the sending end. A rule that cannot be broken beats a rule that is written down.

acks="all" and idempotence are not options in producer_options() either. An HTTP endpoint answers its caller on the strength of that write.

Development

make venv
make lint
make test-local

tox runs the suite across every supported Python version.

Design records

The design is written down under docs/superpowers/specs/.

Metadata

Release files for edutap.data-models 0.3.1

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

Source distribution (sdist)

Source distribution for edutap.data-models 0.3.1
File Size Uploaded Provenance Actions
edutap_data_models-0.3.1.tar.gz 51.3 kB With provenance Details for edutap_data_models-0.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for edutap.data-models 0.3.1
File Interpreter ABI Platform Actions
edutap_data_models-0.3.1-py3-none-any.whl Python 3 none any Details for edutap_data_models-0.3.1-py3-none-any.whl

Total release size: 81.2 kB

Release files / edutap_data_models-0.3.1.tar.gz

Download URL edutap_data_models-0.3.1.tar.gz
Size 51.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a40b603e6d0dff9192e15629ea3d37eb56fb9bb1107ae1269ff0c4f16bc725cc
BLAKE2b-256 checksum
How to use checksums
ddeb1aa5d19aea4304f033811c1762ac7a10a03697d8669ec2a254e8d8e22b3e
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 Sep 13, 2026.

Transparency log

Release files / edutap_data_models-0.3.1-py3-none-any.whl

Download URL edutap_data_models-0.3.1-py3-none-any.whl
Size 29.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d8d3fcd58755e235064675991545861131c968bdc309c14646f9ea16e3a7200
BLAKE2b-256 checksum
How to use checksums
2820b8bcb89dd0fb0da88fb8c6135c468b4ef35a50685db1cdcb993fc4a308f4
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 Sep 13, 2026.

Transparency log

Security for edutap.data-models 0.3.1

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for edutap_data_models-0.3.1.tar.gz

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 Sep 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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