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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.