Skip to content

py: profiles - #167

Merged
ryukzak merged 50 commits into
mainfrom
py-profiles-new
Jun 12, 2026
Merged

py: profiles #167
ryukzak merged 50 commits into
mainfrom
py-profiles-new

Conversation

@MikhailArtemyev

Copy link
Copy Markdown
Collaborator

PY: Python profile class generation

Implements generateProfile: true for the Python generator — produces a typed wrapper class for each FHIR profile alongside the base Pydantic models.

Runtime library

A static profile_helpers.py is copied into each profiles/ package. It provides all slice/extension/validation utilities the generated classes depend on: build_resource, ensure_profile, apply_slice_match, get_array_slice, set_array_slice, push_extension, get_extension_value, extract_complex_extension, ensure_slice_defaults, and validation helpers.

Profile class API

Each generated profile class exposes:

  • create(*, required_field: T, ...) -> Profile — constructs a valid resource with all fixed values, required slices, and meta.profile pre-populated
  • apply(resource) -> Profile — wraps an existing resource (adds canonical URL to meta.profile)
  • from_resource(resource) -> Profile — wraps and validates; raises on errors
  • to_resource() -> BaseType — unwraps back to the Pydantic model
  • validate() -> dict[str, list[str]] — checks required fields, fixed value constraints, slice cardinality, must-support fields (warnings), prohibited choices, enum values, and reference target types

Field accessors

  • Required and value-constrained fields get typed get_X / set_X methods; setters return self for fluent chaining
  • Choice types (value[x]) promoted to direct params when only one variant is constrained

Extension accessors

Each ProfileExtension generates overloaded get_X / set_X methods with three return modes:

  • default — flat Python value (e.g. dict, str, Coding)
  • "raw" — bare Extension model
  • "profile" — wrapped in the extension's own profile class (when one exists in the same package)

Supports complex (sub-extension), single-value, and generic extension shapes.

Slice accessors

Each named slice generates get_X / set_X methods that read and write into the correct position within the array field. Supports:

  • Value-discriminated slices (match on fixed field values)
  • Type-discriminated slices (resource-type discriminator), including unbounded (max: *) variants with get_X() -> list[T] / set_X(values: list[T])
  • Constrained choice slices (e.g. BP component.value[x] fixed to valueQuantity) — automatically wraps/unwraps the choice key

Example

Added examples/python-profiles/us-core-demo/ — Python equivalent of the TypeScript US Core demo:

Before (raw Pydantic):

obs = Observation(
    resource_type="Observation",
    status="final",
    code=CodeableConcept(coding=[Coding(code="85354-9", system="http://loinc.org")]),
    component=[ObservationComponent(...)],
    ...
)

After (profile class):

bp = UscoreBloodPressureProfile.create(status="final", subject=Reference(reference="Patient/123"))
bp.set_effective_date_time("2026-04-15")
bp.set_systolic({"value": 120, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"})
bp.set_diastolic({"value": 80, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"})
errors = bp.validate()["errors"]  # []

load.py converts patients.csv (5 patients) to a FHIR transaction bundle; avg.py reads it back via profile accessors and prints average BP.

* fixed python generator after rebase
* added profile example to the default python example directory
* mypy fixes
* slight refactoring
* implemented profile generation in python, mirroring functionality of ts generator

* made a new python example dedicated to profiles
*  added collectProhibitedChoiceValidation for prohibited value[x] variants
* added resolveExtensionProfile, multi-form getter overloads, setter dispatch; raw branch normalizes dict -> Extension and attribute access works
*  push_extension preserves Pydantic instances as-is

* all tests should pass
fixed base-import bug with BackboneElement
py: removed resource_families

py: added a test for bundle profile class
* Added nameCandidates: NameCandidates to SliceDef, populated from slice.nameCandidates in collectSliceDefs

* Replaced resolveProfileMethodBaseNames with a simple loop reading snakeCase(recommended) from each extension/slice

* Deleted resolveNameCollisions, pyExtensionMethodBaseName,pyQualifiedExtensionMethodBaseName, pySliceMethodBaseName, pyQualifiedSliceMethodBaseName from profile-naming.ts
… call to using pre-built SnapshotProfileTypeSchema snapshots from the index, making fields non-optional throughout
@MikhailArtemyev
MikhailArtemyev requested a review from ryukzak May 28, 2026 16:08

@ryukzak ryukzak left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, rebase on the main.

Comment thread assets/api/writer-generator/python/profile_helpers.py Outdated
Comment thread assets/api/writer-generator/python/profile_helpers.py
Comment thread .gitignore Outdated
Comment thread .gitignore Outdated
Comment thread examples/python-us-core/fhir_types/README.md
MikhailArtemyev and others added 5 commits June 10, 2026 15:00
Reconcile the Python generator refactor (profiles, writer dir-split, free-function
helpers) with main's resource-generics work. Resolutions:
- src/api/writer-generator/python/writer.ts: kept the branch's structure (it already
  contains the generics feature); main's parallel python.ts changes were stylistic
  (Map->Record, method vs free-function).
- examples/python-r4 test_bundle.py / test_sdk.py: took main's versions (supersets,
  added from_json validation tests).
- Generated example trees and python.test.ts snapshot: regenerated from the merged
  generator.

Verified: typecheck, lint, 290 unit/api + 38 multi-package + 26 python-writer tests,
65 us-core + 10 r4 pytest all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread Makefile
Comment thread Makefile
@ryukzak
ryukzak merged commit 55781e5 into main Jun 12, 2026
39 checks passed
@ryukzak
ryukzak deleted the py-profiles-new branch July 24, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants