Skip to main content

A Python SDK for openEHR with type-safe Reference Model classes, template builders, and EHRBase client

Project description

oehrpy — openEHR, the Pythonic way

PyPI version Python versions 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
  • 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 openehr_sdk.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 openehr_sdk.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 openehr_sdk.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 openehr_sdk.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 openehr_sdk.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 openehr_sdk.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 openehr_sdk.rm import DV_QUANTITY, CODE_PHRASE, TERMINOLOGY_ID
from openehr_sdk.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 openehr_sdk.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 openehr_sdk.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},
    )

AQL Query Builder

from openehr_sdk.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,generator]"

Running Tests

pytest tests/ -v

Type Checking

mypy src/openehr_sdk

Regenerating RM Classes

The RM classes are generated from openEHR BMM specifications:

python -m generator.pydantic_generator

Project Structure

oehrpy/
├── src/openehr_sdk/       # 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
└── docs/                  # Documentation

Contributing

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

License

MIT

Documentation

References

Project details


Download files

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

Source Distribution

oehrpy-0.8.0.tar.gz (322.9 kB view details)

Uploaded Source

Built Distribution

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

oehrpy-0.8.0-py3-none-any.whl (74.4 kB view details)

Uploaded Python 3

File details

Details for the file oehrpy-0.8.0.tar.gz.

File metadata

  • Download URL: oehrpy-0.8.0.tar.gz
  • Upload date:
  • Size: 322.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for oehrpy-0.8.0.tar.gz
Algorithm Hash digest
SHA256 ea5f229038777f16b53437f3ea5a17b0e930d4105bb533f6c4adf834f038a559
MD5 760e8969d508333827d968684caf2ff8
BLAKE2b-256 e9851be29aaea8d2756e905bf7e3e71fcc28e8cf5c0dc11fe0c780ab9a2404a0

See more details on using hashes here.

Provenance

The following attestation bundles were made for oehrpy-0.8.0.tar.gz:

Publisher: publish.yml on platzhersh/oehrpy

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

File details

Details for the file oehrpy-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: oehrpy-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 74.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for oehrpy-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 181eafb211da7957848f070069393f379dfcbd806a7f5bfa2df93aef64575045
MD5 9be5bd1b0ac98ea681e7549a5e59d8cc
BLAKE2b-256 5507cbf4c5bc70124d16ad8bfcd754e53c6afc235442e7e659c0614b2f7050f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for oehrpy-0.8.0-py3-none-any.whl:

Publisher: publish.yml on platzhersh/oehrpy

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page