El desarrollo FHIR en Python suele implicar lidiar con diccionarios sin tipo, adivinar nombres de campo en una especificación enorme y descubrir errores tipográficos solo en tiempo de ejecución. Hemos creado un generador que elimina todo eso.
@atomic-ehr/codegen produce modelos Pydantic fuertemente tipados a partir de cualquier paquete FHIR — con validación, autocompletado en el IDE e integración opcional con fhirpy. Funciona con cualquier servidor FHIR, no solo con Aidbox. El proyecto es de código abierto (MIT) y agradecemos las contribuciones.
Esta entrada actualiza nuestro artículo anterior sobre el SDK de Python. Desde entonces, el generador ha sido reescrito desde cero en un único stack TypeScript como @atomic-ehr/codegen.
Lo que obtendrá:
- Modelos Pydantic v2 para cada recurso y tipo de dato
- Value sets — enumeraciones
LiteralyCodeableConcept[T]genérico con bindings en el tipo - Extensiones de tipos primitivos — campos
_birthDate,_familytipados - Cliente asíncrono fhirpy — conéctese directamente a
AsyncFHIRClient - Cualquier paquete FHIR — R4, US Core y personalizados en un solo paso; parchee paquetes al vuelo
- Type Schema — tree shaking, promoción de modelos lógicos y resolución de colisiones
Genere Sus Tipos FHIR
Instale el generador (bun add y pnpm add también funcionan):
npm install @atomic-ehr/codegen
Cree 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();
Ejecútelo:
npx tsx generate.ts
Ahora tendrá un paquete Python listo para usar en ./fhir_types/. El tree shaking incluye únicamente los recursos que ha listado, junto con sus dependencias (DomainResource, Element, OperationOutcome, etc.) de forma automática.
Qué Se Genera
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.
Cada recurso es un modelo Pydantic con tipos de campo correctos, alias y validación:
# 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
# ...
Consulte el archivo generado completo para ver el modelo Patient completo.
Crear y Trabajar con Recursos
Configurar el entorno Python (Python 3.12+)
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r fhir_types/requirements.txt
El generador produce tipos y validación — no incluye ningún cliente HTTP. Para la comunicación con el servidor, use fhirpy o cualquier biblioteca HTTP. Aquí trabajamos directamente con los modelos:
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'
Comprobación de Tipos y Validación
¿Qué ocurre cuando cometemos errores?
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
)
Análisis estático con mypy. Añada el plugin de Pydantic a mypy.ini:
[mypy]
python_version = 3.12
plugins = pydantic.mypy
[pydantic-mypy]
init_typed = True
A continuación, ejecútelo:
$ 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]
Validación en tiempo de ejecución — ejecute el script y Pydantic detecta ambos errores en la instanciación:
$ 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
No es necesario ejecutar un validador por separado — los modelos aplican las restricciones en la construcción. Si necesita aceptar campos adicionales, genere con allowExtraFields: true.
Opciones de Personalización
| Opción | Tipo | Valor por defecto | Efecto |
|---|---|---|---|
fieldFormat | "snake_case" | "camelCase" | "camelCase" | Nomenclatura de campos: birth_date frente a birthDate |
allowExtraFields | boolean | false | Acepta campos desconocidos en la entrada JSON |
primitiveTypeExtension | boolean | false | Genera campos complementarios tipados _birthDate, _family |
client | fhirpy, none | fhirpy | Clase base compatible con FhirpyBaseModel |
Estas opciones se combinan con las funcionalidades a nivel de APIBuilder — tree shaking para incluir solo los recursos que necesita, carga de múltiples paquetes para mezclar el núcleo R4 con IGs y perfiles personalizados, y preprocesamiento de paquetes para parchear paquetes upstream defectuosos antes de la generación.
Próximamente: Soporte de Perfiles para Python
Nuestro generador TypeScript ya admite clases de perfil FHIR — clases generadas que rellenan automáticamente valores fijos, proporcionan accesores tipados para slices y extensiones, e incluyen validación del lado del cliente. Estamos trabajando en llevar las mismas capacidades a Python:
- Accesores de slice con valores discriminadores aplicados automáticamente
- Getters/setters de extensiones
- Validación que devuelve errores y advertencias estructurados
Si le interesa esto, agradecemos sus comentarios sobre qué patrones de perfil son más relevantes para sus flujos de trabajo en Python en GitHub.
Más Allá de Python: Qué Más Hace @atomic-ehr/codegen
Esta entrada se ha centrado en el generador de Python, pero @atomic-ehr/codegen es una plataforma de generación de código multilenguaje. Esto es lo que más ofrece:
- Cuatro generadores integrados — Python, TypeScript, C# y un motor de plantillas Mustache para cualquier lenguaje
- Preprocesamiento de paquetes — los paquetes FHIR del mundo real suelen incluir errores: URLs canónicas malformadas, conceptos de CodeSystem faltantes, ValueSets externos no disponibles. El hook
preprocessPackagepermite parchearlos antes de la generación, de modo que no tenga que bifurcar el paquete ni solucionar errores más adelante - Promoción de modelos lógicos — convierte StructureDefinitions lógicas en recursos de primera clase para la generación de código
- Resolución de colisiones de esquema — cuando varios paquetes definen bindings superpuestos, puede especificar qué fuente tiene prioridad
- Carga de múltiples fuentes — combine paquetes NPM, archivos TGZ locales y archivos JSON de StructureDefinition sueltos en un único paso de generación
Ejemplos de Python en funcionamiento:
- Python + fhirpy — camelCase, cliente fhirpy asíncrono
- Python + Pydantic + Cliente personalizado — snake_case, extensiones de tipos primitivos, cliente síncrono personalizado
@atomic-ehr/codegen es de código abierto bajo la licencia MIT. Si trabaja con FHIR en Python — ya sea desarrollando pipelines de datos, funcionalidades de ML o aplicaciones clínicas — pruébelo y cuéntenos qué le parece. Las incidencias, las solicitudes de funcionalidades y los PRs son bienvenidos.



