Le développement FHIR en Python signifie généralement se débattre avec des dictionnaires bruts, deviner les noms de champs à partir d'une spécification volumineuse et découvrir des fautes de frappe seulement à l'exécution. Nous avons conçu un générateur qui élimine tout cela.
@atomic-ehr/codegen produit des modèles Pydantic fortement typés à partir de n'importe quel paquet FHIR — avec validation, autocomplétion IDE et intégration optionnelle de fhirpy. Il fonctionne avec n'importe quel serveur FHIR, pas seulement Aidbox. Le projet est à code source ouvert (MIT) et nous accueillons les contributions avec plaisir.
Cet article met à jour notre article précédent sur le SDK Python. Depuis lors, le générateur a été réécrit de zéro dans une pile TypeScript unifiée sous le nom @atomic-ehr/codegen.
Ce que vous obtenez :
- Modèles Pydantic v2 pour chaque ressource et type de données
- Ensembles de valeurs — énumérations
LiteraletCodeableConcept[T]générique avec liaisons dans le type - Extensions de types primitifs — champs
_birthDate,_familytypés - Client asynchrone fhirpy — branchement direct sur
AsyncFHIRClient - N'importe quel paquet FHIR — R4, US Core et profils personnalisés en une seule passe ; application de correctifs aux paquets à la volée
- Type Schema — élagage d'arbre, promotion de modèles logiques, résolution de collisions
Générez vos types FHIR
Installez le générateur (bun add et pnpm add fonctionnent aussi) :
npm install @atomic-ehr/codegen
Créez un script generate.ts :
import { APIBuilder } from "@atomic-ehr/codegen";
const main = async () => {
const builder = new APIBuilder()
.fromPackage("hl7.fhir.r4.core", "4.0.1")
.python({
fieldFormat: "snake_case",
allowExtraFields: false,
primitiveTypeExtension: true,
})
.typeSchema({
treeShake: {
"hl7.fhir.r4.core": {
"http://hl7.org/fhir/StructureDefinition/Patient": {},
"http://hl7.org/fhir/StructureDefinition/Observation": {},
"http://hl7.org/fhir/StructureDefinition/Bundle": {},
},
},
})
.outputTo("./fhir_types")
.cleanOutput(true);
const report = await builder.generate();
if (!report.success) process.exit(1);
};
main();
Exécutez-le :
npx tsx generate.ts
Vous disposez maintenant d'un paquet Python prêt à l'emploi dans ./fhir_types/. L'élagage d'arbre n'inclut que les ressources que vous avez listées ainsi que leurs dépendances (DomainResource, Element, OperationOutcome, etc.) automatiquement.
Ce qui est généré
fhir_types/
├── __init__.py # Exports + model_rebuild() for forward refs
├── requirements.txt # pydantic, requests, fhirpy, mypy, pytest
├── README.md
└── hl7_fhir_r4_core/
├── __init__.py
├── base.py # Complex types: CodeableConcept, HumanName, ...
├── patient.py # Patient + nested types (PatientContact, ...)
├── observation.py
├── operation_outcome.py
├── bundle.py
├── domain_resource.py
├── resource.py
└── resource_families.py # Polymorphic validators for Bundle.entry etc.
Chaque ressource est un modèle Pydantic avec des types de champs appropriés, des alias et une validation :
# Simplified -- other fields also carry serialization_alias, omitted for brevity
class Patient(DomainResource):
model_config = ConfigDict(
validate_by_name=True,
serialize_by_alias=True,
extra="forbid"
)
resource_type: Literal['Patient'] = Field(
default='Patient', alias='resourceType',
serialization_alias='resourceType', frozen=True
)
birth_date: str | None = Field(None, alias="birthDate")
birth_date_extension: Element | None = Field(None, alias="_birthDate")
gender: Literal["male", "female", "other", "unknown"] | None = Field(None)
marital_status: CodeableConcept[
Literal["A", "D", "I", "L", "M", "P", "S", "T", "U", "W", "UNK"] | str
] | None = Field(None, alias="maritalStatus")
name: PyList[HumanName] | None = Field(None) # PyList = typing.List
# ...
Consultez le fichier généré complet pour voir le modèle Patient dans son intégralité.
Créer et manipuler des ressources
Configurer l'environnement Python (Python 3.12+)
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r fhir_types/requirements.txt
Le générateur produit des types et une validation — aucun client HTTP n'est intégré. Pour la communication avec le serveur, utilisez fhirpy ou n'importe quelle bibliothèque HTTP. Ici, nous travaillons directement avec les modèles :
from fhir_types.hl7_fhir_r4_core import Element, Extension, HumanName, Patient
patient = Patient(
name=[HumanName(given=["John"], family="Doe")],
gender="male",
birth_date="1980-01-01",
birth_date_extension=Element(
extension=[
Extension(
url="http://hl7.org/fhir/StructureDefinition/patient-birthTime",
value_date_time="1980-01-01T08:30:00-05:00",
),
],
),
)
# Serialize to FHIR JSON (camelCase keys, nulls excluded)
print(patient.to_json(indent=2))
{
"resourceType": "Patient",
"birthDate": "1980-01-01",
"_birthDate": {
"extension": [{
"url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
"valueDateTime": "1980-01-01T08:30:00-05:00"
}]
},
"gender": "male",
"name": [{ "family": "Doe", "given": ["John"] }]
}
# Round-trip: JSON -> Patient
restored = Patient.from_json(patient.to_json())
assert restored == patient
# Typed field access
patient.gender # 'male'
assert patient.name is not None
patient.name[0].family # 'Doe'
patient.birth_date # '1980-01-01'
assert patient.birth_date_extension is not None
assert patient.birth_date_extension.extension is not None
patient.birth_date_extension.extension[0].value_date_time # '1980-01-01T08:30:00-05:00'
Vérification de types et validation
Que se passe-t-il lorsque nous faisons des erreurs ?
from fhir_types.hl7_fhir_r4_core import HumanName, Patient
Patient(
name=[HumanName(family="Doe")],
gender="FOO", # wrong value
some_data="1990-01-01", # wrong field
)
Analyse statique avec mypy. Ajoutez le greffon Pydantic à mypy.ini :
[mypy]
python_version = 3.12
plugins = pydantic.mypy
[pydantic-mypy]
init_typed = True
Puis exécutez :
$ mypy . --strict
main.py:10: error: Unexpected keyword argument "some_data" for "Patient" [call-arg]
main.py:12: error: Argument "gender" to "Patient" has incompatible type "Literal['FOO']"; expected "Literal['male', 'female', 'other', 'unknown'] | None" [arg-type]
Validation à l'exécution — exécutez le script et Pydantic intercepte les deux erreurs à l'instanciation :
$ python main.py
pydantic_core._pydantic_core.ValidationError: 2 validation errors for Patient
gender
Input should be 'male', 'female', 'other' or 'unknown'
some_data
Extra inputs are not permitted
Nul besoin d'exécuter un validateur séparément — les modèles appliquent les contraintes à la construction. Si vous devez accepter des champs supplémentaires, générez avec allowExtraFields: true.
Options de personnalisation
| Option | Type | Défaut | Effet |
|---|---|---|---|
fieldFormat | "snake_case" | "camelCase" | "camelCase" | Nommage des champs : birth_date ou birthDate |
allowExtraFields | boolean | false | Accepter les champs inconnus dans les données JSON en entrée |
primitiveTypeExtension | boolean | false | Générer des champs complémentaires typés _birthDate, _family |
client | fhirpy, none | fhirpy | Classe de base compatible FhirpyBaseModel |
Ces options se combinent avec les fonctionnalités au niveau de l'APIBuilder — élagage d'arbre pour n'inclure que les ressources dont vous avez besoin, chargement multi-paquets pour combiner le noyau R4 avec des IG et des profils personnalisés, et prétraitement des paquets pour corriger les paquets amont défectueux avant la génération.
Prochainement : prise en charge des profils pour Python
Notre générateur TypeScript prend déjà en charge les classes de profils FHIR — des classes générées qui peuplent automatiquement les valeurs fixes, fournissent des accesseurs typés pour les tranches et les extensions, et incluent une validation côté client. Nous travaillons à apporter les mêmes capacités à Python :
- Accesseurs de tranches avec valeurs de discriminateur appliquées automatiquement
- Accesseurs/modificateurs d'extensions
- Validation retournant des erreurs et avertissements structurés
Si cela vous intéresse — vos commentaires sur les schémas de profils les plus importants pour vos flux de travail Python sont les bienvenus sur GitHub.
Au-delà de Python : ce que fait d'autre @atomic-ehr/codegen
Cet article s'est concentré sur le générateur Python, mais @atomic-ehr/codegen est une plateforme de génération de code multilangage. Voici ce qu'il offre également :
- Quatre générateurs intégrés — Python, TypeScript, C# et un moteur de gabarits Mustache pour tout langage
- Prétraitement des paquets — les paquets FHIR du monde réel sont souvent livrés avec des bogues : URL canoniques malformées, concepts CodeSystem manquants, ValueSets externes indisponibles. Le point d'ancrage
preprocessPackagevous permet de les corriger avant la génération, sans avoir à créer une bifurcation du paquet ni à contourner les erreurs en aval - Promotion de modèles logiques — convertir les StructureDefinitions logiques en ressources de première classe pour la génération de code
- Résolution des collisions de schémas — lorsque plusieurs paquets définissent des liaisons qui se chevauchent, vous pouvez spécifier quelle source l'emporte
- Chargement multi-sources — combiner des paquets NPM, des archives TGZ locales et des fichiers JSON StructureDefinition isolés en une seule passe de génération
Exemples Python fonctionnels :
- Python + fhirpy — camelCase, client fhirpy asynchrone
- Python + Pydantic + Client personnalisé — snake_case, extensions de types primitifs, client synchrone personnalisé
@atomic-ehr/codegen est à code source ouvert sous licence MIT. Que vous travailliez avec FHIR en Python pour créer des pipelines de données, des fonctionnalités d'apprentissage automatique ou des applications cliniques — essayez-le et dites-nous ce que vous en pensez. Les signalements de bogues, demandes de fonctionnalités et requêtes de fusion sont tous les bienvenus.



