Skip to main content

purelms-shared

Pydantic schemas that bridge PureLMS (AGPL-3.0-or-later) and the simulation backend containers (MIT, in purelms-interactive-tasks — formerly purelms-backends).

What's in here

  • purelms_shared.envelopesSimulationInputEnvelope and SimulationOutputEnvelope, the JSON contract for run input + output. Plus the helper types: InputFile, ResourceFile, OutputArtifact, Message, ExecutionContext.
  • purelms_shared.callbacksProgressCallback and CompleteCallback, the request bodies a backend POSTs to the worker during a run.
  • purelms_shared.evidenceEvidenceManifest and EvidenceReference, the immutable record of a completed run that credential issuance reads later.
  • purelms_shared.constantsRunStatus, OutputStatus, MessageLevel, BackendCallbackEvent, InputFileRole enums, plus the purelms.{input,output,evidence}.v1 schema-version literals.

What's not in here

  • No Django.
  • No business logic.
  • No dependencies beyond pydantic.

If you find yourself reaching for any of those while writing code here, you've probably wandered into the LMS's purelms.simulations Django app or a specific backend's runner — not this package.

Why it's MIT

PureLMS is AGPL-3.0-or-later (matching Validibot's Community Edition). Simulation backends are MIT so educator-contributors aren't burdened with copyleft for the per-backend image they ship.

The only artifact that crosses the AGPL/MIT boundary is this schema package — so it's MIT too. Neither side imports the other's code; they share a data contract.

Versioning

Schema versions are dotted-package strings, not integers:

  • purelms.input.v1 — input envelope shape v1
  • purelms.output.v1 — output envelope shape v1
  • purelms.evidence.v1 — evidence manifest shape v1

Additive changes (new optional field with a default) ship in v1. Incompatible changes bump to v2 with a new class living alongside the old one — running backends pin specific versions of this package, and we never break old envelopes.

"Additive within v1" is NOT backward compatible at the wire level. Pydantic's ConfigDict(extra="forbid") on every envelope class means a consumer pinning an older purelms-shared will REJECT messages from a newer producer that include the new field. Bump the package version and update every consumer's floor pin in lockstep — this was the discipline unit_block_id followed when it was added in 0.4.0.

Updating the wire format — lockstep consumer updates

When you add a field to an envelope or a value to a shared enum, update these consumer files in the SAME PR:

Python consumers (each pins a purelms-shared>=X.Y.Z floor):

  • purelms/pyproject.toml (and re-resolve the lockfile)
  • purelms-interactive-tasks/pyproject.toml
  • purelms-interactive-tasks/echo/backend/pyproject.toml
  • purelms-interactive-tasks/energyplus_single_zone/backend/pyproject.toml
  • purelms-interactive-tasks/_template/backend/pyproject.toml
  • Any other per-InteractiveTask backend/pyproject.toml

TypeScript consumers (vendor the wire types — extra="forbid" doesn't apply here, but unions out of sync silently break bundles branching on the missing case):

  • purelms/purelms/static/src/ts/sims/api/types.ts — the LMS-side dispatcher's authoritative TS types
  • purelms-interactive-tasks/echo/frontend/src/echo.ts — echo's vendored inline types
  • purelms-interactive-tasks/energyplus_single_zone/frontend/src/types.ts — EnergyPlus's vendored types
  • Any other per-InteractiveTask frontend/src/types.ts

The wire-format-sync tests in purelms/purelms/simulations/tests/test_wire_format_sync.py parametrize over each enum value and grep the LMS-side TS file for it — they fail loudly if a Python enum gains a value that doesn't appear in the matching TS union. Run them in CI on every push.

Why this matters: Slice 3d's post-shipping review round 2 caught a real drift — the TS RunStatus union was missing failed_simulation, failed_runtime, and timed_out entirely. Bundles branching on status would have silently failed to render any FAILED_* UI. The lockstep procedure above + the sync tests close that failure mode.

On extra="forbid" — why strictness over flexibility

Every envelope class in this package sets ConfigDict(extra="forbid", frozen=True). That's the choice driving the lockstep-update tax above: a consumer pinning purelms-shared<X will reject a message from a producer at >=X that includes a new field, even when the new field is purely additive and the consumer wouldn't have read it anyway.

We considered the looser alternative — extra="ignore" — and rejected it. The reasoning, recorded here so future contributors don't re-litigate:

  • Wire schemas are a credential surface, not just a transport. The output envelope is what evidence manifests and credentials are computed from. A field the producer thinks is meaningful but the consumer silently ignores is the classic shape of evidence drift. extra="forbid" turns "the consumer is one version behind" from a silent mis-credential into a loud rejection at deserialization time.
  • The lockstep cost is bounded and visible. Six pyproject.toml floor-pins plus a handful of TS type files — all enumerated in the section above, all enforced by the test_wire_format_sync.py parametrized tests. A grep-and-bump operation, not an open-ended migration. The cost is real but pays for itself the first time it catches a drift like Slice 3d round 2's RunStatus mismatch.
  • The alternative failure mode is worse. With extra="ignore", a backend author adding provenance_hash to the output envelope and an LMS one version behind would silently strip it on read. The credential issued from that run would lack the field. The bug surfaces months later in audit — if at all. Strict mode surfaces it on the first run.

The escape hatch. If the lockstep tax becomes painful in practice, the relaxation is mechanical: change extra="forbid" to extra="ignore" on the relevant envelope class (or all of them) and bump to v2. Old consumers keep working; new producers can add fields without coordinating updates. The decision is reversible — we'd lose the drift-catching property in exchange for looser coupling. We didn't bake the strictness into the wire format itself; it's a per-class Pydantic setting.

Three triggers that would prompt revisiting:

  1. A third external InteractiveTask author appears (i.e. someone outside the PureLMS core team writing a backend). At that point the shared schema + lockstep-floor-pin workflow becomes a contributor-onboarding tax worth paying for — likely by extracting the TS types into an npm-published @purelms/shared-types package so frontend bundles can npm install instead of vendoring.
  2. Backend authors complain that the lockstep update is blocking them — e.g. "I want to add simulation_seed to my outputs but I can't ship until the LMS bumps its purelms-shared floor." That's the signal the strictness cost has crossed the line from disciplined to obstructive.
  3. A second implementation language wants to write backends (Go, Rust, Node). At that point the right answer probably isn't "port Pydantic" but "generate JSON Schema from these classes and let each language consume it." The strictness invariant survives the language change; the implementation does not.

None of those triggers are tripped today. If they trip in the future, this section is the breadcrumb back to the decision.

Install

uv add purelms-shared
# or
pip install purelms-shared

Quick usage:

from purelms_shared import (
    SimulationInputEnvelope,
    SimulationOutputEnvelope,
    OutputStatus,
    Message,
    MessageLevel,
)

# Backends construct the output envelope at completion:
out = SimulationOutputEnvelope(
    run_id=run_id,
    status=OutputStatus.SUCCESS,
    outputs={"annual_eui_kbtu_ft2": 47.3},
    messages=[
        Message(level=MessageLevel.INFO, code="EPLUS.OK", text="Annual run complete"),
    ],
    runtime_seconds=23.4,
)

Architecture context

See the PureLMS simulation backend contract and simulation runtime protocol for the full surrounding design.

Legacy

purelms_shared.energyplus carries an older EnergyPlus-specific result schema used by the (soon-to-be-retired) Modal runtime in purelms-modal. It will be removed when that runtime is phased out in favour of the pluggable simulation-execution model. New code should use the generic envelope schemas above.

License

MIT — see LICENSE.

Download files

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

Source Distribution

purelms_shared-0.5.1.tar.gz (34.3 kB view details)

Uploaded Source

Built Distribution

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

purelms_shared-0.5.1-py3-none-any.whl (28.2 kB view details)

Uploaded Python 3

File details

Details for the file purelms_shared-0.5.1.tar.gz.

File metadata

  • Download URL: purelms_shared-0.5.1.tar.gz
  • Upload date:
  • Size: 34.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for purelms_shared-0.5.1.tar.gz
Algorithm Hash digest
SHA256 ca67a4668bfd32ad9392562a53209b4ac84685e37bcc282e5a50469dbfec6a70
MD5 a160ce08818d96bbb963538b98c65a39
BLAKE2b-256 49cddf08e84bef3cb3ee5a8eb36f11eadd2c03f0bec9f0c518a514555b72b321

See more details on using hashes here.

Provenance

The following attestation bundles were made for purelms_shared-0.5.1.tar.gz:

Publisher: publish.yml on danielmcquillen/purelms-shared

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

File details

Details for the file purelms_shared-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: purelms_shared-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 28.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for purelms_shared-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 45b6ad2da1869b17da846f16604f0fea807621cf03c5c1a854283cf31532c1aa
MD5 4a2638acdf6d405c0fa678ada72d2a62
BLAKE2b-256 9f52d6d7b011efd42a72b2ed7737db6dc4508657276459964b9dc6b2b1dcfd88

See more details on using hashes here.

Provenance

The following attestation bundles were made for purelms_shared-0.5.1-py3-none-any.whl:

Publisher: publish.yml on danielmcquillen/purelms-shared

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.5.1 This release

2 files

0.5.0

2 files

0.4.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