Skip to main content

oehrpy — openEHR, the Pythonic way

PyPI version Python versions Downloads License CI Changelog Documentation

Pronunciation: /oʊ.ɛər.paɪ/ ("o-air-pie") — short for "openehrpy", where "ehr" is pronounced like "air" (as in openEHR).

A comprehensive Python SDK for openEHR that provides type-safe Reference Model classes, template-specific composition builders, EHRBase client, and AQL query builder.

Overview

This project addresses the gap in the openEHR ecosystem where no comprehensive, actively maintained Python SDK exists. It eliminates the need for developers to manually construct complex nested JSON structures when working with openEHR compositions.

New to openEHR? Start with the workflow overview to see where oehrpy fits in the openEHR data lifecycle.

Installation

pip install oehrpy

Or install from source:

git clone https://github.com/platzhersh/oehrpy.git
cd oehrpy
pip install -e .

Compatibility

  • Python: 3.10+
  • openEHR RM: 1.1.0
  • EHRBase: 2.26.0+ (uses new FLAT format with composition tree IDs)

Note: EHRBase 2.0+ introduced breaking changes to the FLAT format. This SDK implements the new format used by EHRBase 2.26.0. For details, see FLAT Format Versions.

Features

  • Type-safe RM Classes: 134 Pydantic models for openEHR Reference Model 1.1.0 types (includes BASE types)
  • Template Builders: Pre-built composition builders for common templates (Vital Signs)
  • OPT Parser & Generator: Parse OPT files and auto-generate type-safe builder classes
  • FLAT Format: Full support for EHRBase 2.26.0+ FLAT format serialization
  • Canonical JSON: Convert RM objects to/from openEHR canonical JSON format
  • EHRBase Client: Async REST client for EHRBase CDR operations
  • Contributions & Audit: Commit multiple changes atomically with audit metadata via a fluent builder
  • AQL Builder: Fluent API for building type-safe AQL queries
  • OPT Validator: Validate OPT 1.4 XML files before CDR upload (well-formedness, semantics, FLAT path impact)
  • IDE Support: Full autocomplete and type checking support
  • Validation: Pydantic v2 validation for all fields

Quick Start

Creating RM Objects

from oehrpy.rm import (
    DV_QUANTITY, DV_TEXT, DV_CODED_TEXT,
    CODE_PHRASE, TERMINOLOGY_ID
)

# Create a simple text value
text = DV_TEXT(value="Patient vital signs recorded")

# Create a quantity (e.g., blood pressure)
bp_systolic = DV_QUANTITY(
    magnitude=120.0,
    units="mm[Hg]",
    property=CODE_PHRASE(
        terminology_id=TERMINOLOGY_ID(value="openehr"),
        code_string="382"
    )
)
print(f"Blood pressure: {bp_systolic.magnitude} {bp_systolic.units}")

Template Builders

Build compositions using type-safe builders without knowing FLAT paths:

from oehrpy.templates import VitalSignsBuilder

# Create a vital signs composition
builder = VitalSignsBuilder(composer_name="Dr. Smith")
builder.add_blood_pressure(systolic=120, diastolic=80)
builder.add_pulse(rate=72)
builder.add_temperature(37.2)
builder.add_respiration(rate=16)
builder.add_oxygen_saturation(spo2=98)

# Get FLAT format for EHRBase submission
flat_data = builder.build()
# {
#   "vital_signs_observations/language|code": "en",
#   "vital_signs_observations/territory|code": "US",
#   "vital_signs_observations/composer|name": "Dr. Smith",
#   "vital_signs_observations/category|code": "433",
#   "vital_signs_observations/vital_signs/blood_pressure/systolic|magnitude": 120,
#   "vital_signs_observations/vital_signs/blood_pressure/systolic|unit": "mm[Hg]",
#   "vital_signs_observations/vital_signs/body_temperature/temperature|unit": "°C",
#   ...
# }

Generate Builder Skeletons from OPT Files

Generate template metadata skeletons from OPT (Operational Template) files. The generated code includes the template ID, concept, and discovered archetypes, but not FLAT path strings — FLAT paths must come from the Web Template JSON provided by the CDR:

from oehrpy.templates import generate_builder_from_opt, parse_opt

# Parse an OPT file (metadata extraction)
template = parse_opt("path/to/your-template.opt")
print(f"Template: {template.template_id}")
print(f"Observations: {len(template.list_observations())}")

# Generate a Python builder skeleton (metadata only, no FLAT paths)
code = generate_builder_from_opt("path/to/your-template.opt")
print(code)  # Class skeleton with template_id and archetype list

# Or save directly to a file
from oehrpy.templates import BuilderGenerator

generator = BuilderGenerator()
generator.generate_to_file(template, "my_template_builder.py")

Command-line tool:

python examples/generate_builder_from_opt.py path/to/template.opt

The generated skeleton must be supplemented with FLAT paths from the Web Template. Fetch it via EHRBaseClient.get_web_template(template_id) after uploading the OPT to a CDR. See ADR-0005 for the rationale.

OPT Validation

Validate OPT 1.4 XML files before uploading to a CDR. The validator checks for XML well-formedness, semantic integrity, structural issues, and FLAT path impact:

from oehrpy.validation import OPTValidator

validator = OPTValidator()
result = validator.validate_file("path/to/template.opt")

if result.is_valid:
    print(f"Template '{result.template_id}' is valid!")
    print(f"  Archetypes: {result.archetype_count}, Nodes: {result.node_count}")
else:
    for issue in result.errors:
        print(f"[{issue.code}] {issue.message}")
        if issue.suggestion:
            print(f"  -> {issue.suggestion}")

# Warnings are always available even when valid
for w in result.warnings:
    print(f"Warning: [{w.code}] {w.message}")

Validation categories:

Category Severity Examples
Well-formedness Error Invalid XML, wrong namespace, missing template_id, unknown RM types
Semantic integrity Error Missing term definitions, orphan terminology bindings
Structural Warning Draft lifecycle, v0 archetypes, prohibited nodes, unconstrained slots
FLAT path impact Info Renamed nodes, path collisions, special characters in concept

Command-line tool:

# Validate one or more OPT files
oehrpy-validate-opt path/to/template.opt

# JSON output for CI/CD pipelines
oehrpy-validate-opt template.opt --output json

# Treat warnings as errors (strict mode)
oehrpy-validate-opt template.opt --strict

# Show FLAT path impact details
oehrpy-validate-opt template.opt --show-flat-paths

Integrate with OPT parsing and builder generation:

from oehrpy.templates import parse_opt, generate_builder_from_opt

# Validate during parsing (raises OPTValidationError on errors)
template = parse_opt("template.opt", validate=True)

# Validate before generating builder code
code = generate_builder_from_opt("template.opt", validate=True)

Canonical JSON Serialization

from oehrpy.rm import DV_QUANTITY, CODE_PHRASE, TERMINOLOGY_ID
from oehrpy.serialization import to_canonical, from_canonical

# Serialize to canonical JSON (with _type fields)
quantity = DV_QUANTITY(magnitude=120.0, units="mm[Hg]", ...)
canonical = to_canonical(quantity)
# {"_type": "DV_QUANTITY", "magnitude": 120.0, "units": "mm[Hg]", ...}

# Deserialize back to Python object
restored = from_canonical(canonical, expected_type=DV_QUANTITY)

FLAT Format Builder

from oehrpy.serialization import FlatBuilder

# For EHRBase 2.26.0+, use composition tree ID as prefix
builder = FlatBuilder(composition_prefix="vital_signs_observations")
builder.context(language="en", territory="US", composer_name="Dr. Smith")
builder.set_quantity("vital_signs_observations/vital_signs/blood_pressure/systolic", 120.0, "mm[Hg]")
builder.set_coded_text("vital_signs_observations/vital_signs/blood_pressure/position", "Sitting", "at0001")

flat_data = builder.build()
# Automatically includes required fields: category, context/start_time, context/setting

EHRBase REST Client

from oehrpy.client import EHRBaseClient

async with EHRBaseClient(
    base_url="http://localhost:8080/ehrbase",
    username="admin",
    password="admin",
) as client:
    # Create an EHR
    ehr = await client.create_ehr()
    print(f"Created EHR: {ehr.ehr_id}")

    # Create a composition
    result = await client.create_composition(
        ehr_id=ehr.ehr_id,
        template_id="IDCR - Vital Signs Encounter.v1",
        composition=flat_data,
        format="FLAT",
    )
    print(f"Created composition: {result.uid}")

    # Query compositions
    query_result = await client.query(
        "SELECT c FROM EHR e CONTAINS COMPOSITION c WHERE e/ehr_id/value = :ehr_id",
        query_parameters={"ehr_id": ehr.ehr_id},
    )

Contributions & Audit

Group one or more versioned-object changes into a single atomic changeset with shared audit metadata. ContributionBuilder assembles the CANONICAL request body so you don't hand-write ORIGINAL_VERSION wrappers. It supports all four openEHR change types: creation, amendment, modification, and deleted.

from oehrpy.client import ContributionBuilder, EHRBaseClient

async with EHRBaseClient(base_url="http://localhost:8080/ehrbase") as client:
    contribution = (
        # `system_id` is optional — EHRBase fills it (and time_committed)
        # server-side when omitted; pass it for an RM-complete audit.
        ContributionBuilder(system_id="oehrpy.example.org")
        # `composition` is a CANONICAL composition dict
        # (e.g. from oehrpy.serialization.to_canonical)
        .add_creation(composition=vitals_canonical)
        .add_amendment(
            preceding_version_uid="abc::ehrbase::1",
            composition=updated_canonical,
            description="Corrected systolic value",
        )
        .set_audit(committer="Dr. Smith", description="Routine vitals and correction")
        .build()
    )

    result = await client.create_contribution(ehr.ehr_id, contribution)
    print(result.contribution_uid, result.versions)

    # Retrieve a contribution (audit metadata + referenced version UIDs)
    fetched = await client.get_contribution(ehr.ehr_id, result.contribution_uid)

AQL Query Builder

from oehrpy.aql import AQLBuilder

# Build complex queries with a fluent API
query = (
    AQLBuilder()
    .select("c/uid/value", alias="composition_id")
    .select("c/context/start_time/value", alias="time")
    .from_ehr()
    .contains_composition()
    .contains_observation(archetype_id="openEHR-EHR-OBSERVATION.blood_pressure.v1")
    .where_ehr_id()
    .order_by_time(descending=True)
    .limit(100)
    .build()
)

print(query.to_string())
# SELECT c/uid/value AS composition_id, c/context/start_time/value AS time
# FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[...]
# WHERE e/ehr_id/value = :ehr_id
# ORDER BY c/context/start_time/value DESC
# LIMIT 100

Available RM Types

The SDK includes all major openEHR RM 1.1.0 types:

Data Types:

  • DV_TEXT, DV_CODED_TEXT, CODE_PHRASE
  • DV_QUANTITY, DV_COUNT, DV_PROPORTION, DV_SCALE (new in 1.1.0)
  • DV_ORDINAL (integer values only - use DV_SCALE for decimal scale values)
  • DV_DATE_TIME, DV_DATE, DV_TIME, DV_DURATION
  • DV_BOOLEAN, DV_IDENTIFIER, DV_URI, DV_EHR_URI
  • DV_MULTIMEDIA, DV_PARSABLE

Structures:

  • COMPOSITION, SECTION, ENTRY
  • OBSERVATION, EVALUATION, INSTRUCTION, ACTION
  • ITEM_TREE, ITEM_LIST, CLUSTER, ELEMENT
  • HISTORY, EVENT, POINT_EVENT, INTERVAL_EVENT

Support:

  • PARTY_IDENTIFIED, PARTY_SELF, PARTICIPATION
  • OBJECT_REF, OBJECT_ID, HIER_OBJECT_ID
  • ARCHETYPED, LOCATABLE, PATHABLE

New in RM 1.1.0

  • DV_SCALE: Data type for scales/scores with decimal values (extends DV_ORDINAL for non-integer scales)
  • preferred_term: New optional field in DV_CODED_TEXT for terminology mapping
  • Enhanced Folder support: Archetypeable meta-data in EHR folders

For details, see ADR-0001: Support RM 1.1.0.

Development

Prerequisites

  • Python 3.10+
  • pip

Setup

# Clone the repository
git clone https://github.com/platzhersh/oehrpy.git
cd oehrpy

# Install development dependencies
pip install -e ".[dev]"

Running Tests

pytest tests/ -v

Type Checking

mypy src/oehrpy

Regenerating RM Classes

The RM classes are generated from openEHR BMM specifications:

python -m generator.pydantic_generator

Project Structure

oehrpy/
├── src/oehrpy/       # Main package
│   ├── rm/                # Generated RM + BASE classes (134 types)
│   ├── serialization/     # JSON serialization (canonical + FLAT)
│   ├── client/            # EHRBase REST client
│   ├── templates/         # Template builders (Vital Signs, etc.)
│   ├── validation/        # FLAT composition & OPT template validation
│   │   └── opt/           # OPT 1.4 XML validator (4 check categories)
│   └── aql/               # AQL query builder
├── generator/             # Code generation tools
│   ├── bmm_parser.py      # BMM JSON parser
│   ├── pydantic_generator.py  # Pydantic code generator
│   └── bmm/               # BMM specification files
├── tests/                 # Test suite
├── website/               # GitHub Pages site (Astro)
└── docs/                  # Documentation

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines on how to get started.

License

Apache-2.0

Documentation

References

  • openEHR Explorer — cross-platform desktop app for browsing, querying, and inspecting openEHR CDRs (openehr-explorer.dev)
  • Open CIS — open-source clinical information system built on openEHR, and the project oehrpy grew out of

Star History

Star History Chart

Release files for oehrpy 0.16.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 oehrpy 0.16.1
File Size Uploaded
oehrpy-0.16.1.tar.gz 482.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for oehrpy 0.16.1
File Interpreter ABI Platform
oehrpy-0.16.1-py3-none-any.whl Python 3 none any Details

Total release size: 567.3 kB

Release files / oehrpy-0.16.1.tar.gz

Download URL oehrpy-0.16.1.tar.gz
Size 482.6 kB
Tags Source
SHA-256 checksum
How to use checksums
34cf1d6cbbcf7c451d9303485365c8b33ffcfbb2bba342ee2f2e5afb137943b8
BLAKE2b-256 checksum
How to use checksums
34eaa9d3c7734bc99d1dc12523de1f93f94a5b63d4dd0b46b3372ad023bfed12
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 24, 2026.

Transparency log

Release files / oehrpy-0.16.1-py3-none-any.whl

Download URL oehrpy-0.16.1-py3-none-any.whl
Size 84.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f6c9ee3c68fdade8a0a26900b4dcc78fca37e80f826c0fd6494c1508850989a9
BLAKE2b-256 checksum
How to use checksums
a6c1997891b8ab0785c2139fc26a61640acaa2b1a1aed03f9186708c6394244e
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 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.16.1 This release

2 release files

0.16.0

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 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