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.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 oehrpy 0.16.0
File Size Uploaded
oehrpy-0.16.0.tar.gz 565.9 kB Details

Built distribution (wheel)

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

Total release size: 650.6 kB

Release files / oehrpy-0.16.0.tar.gz

Download URL oehrpy-0.16.0.tar.gz
Size 565.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7f31de0876ef3e12db9cc5cf81a114f1312040279cba1f7ea42458c9bccaeb7e
BLAKE2b-256 checksum
How to use checksums
722a751d3ca01217c7d27c60e31a4bab5ba0ae697d574d6809ced9e55b9ed3fb
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.0-py3-none-any.whl

Download URL oehrpy-0.16.0-py3-none-any.whl
Size 84.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4579b96fc0cf0d1b1ad30061a8fbadf1b68e9e2a6fb6104ad516e7548dd2fec0
BLAKE2b-256 checksum
How to use checksums
5ff51acf14b3eea350618816538f3c7ae794657dc7c930951fd4c427f951ae27
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

0.16.1

2 release files

This release

0.16.0 This release

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