-
Notifications
You must be signed in to change notification settings - Fork 1
[GPCAPIM-305]: Create common Pydantic FHIR types #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
54342cf
ba29456
b326b51
ae3252c
9b77b80
8bb2ec8
5da3ba8
f109bfb
63ed2ea
2059e3f
36cf647
7c05a56
4b70b53
7814f8f
8827d21
923f005
6dc288f
6df3060
0bbd8c4
e48d647
7f546b5
738de94
25d5f98
3045aa6
e5e57e9
e25123f
b1ee939
068d8af
6270716
e877d0d
f1f34f8
ab26650
c48dcfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,5 @@ asid | |
| fhir | ||
| getstructuredrecord | ||
| gpconnect | ||
| searchset | ||
| usefixtures | ||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # FHIR Types in Gateway API | ||
|
|
||
| ## What is FHIR? | ||
|
|
||
| FHIR (Fast Healthcare Interoperability Resources) is the HL7 standard for exchanging healthcare information as structured resources over HTTP APIs. | ||
|
|
||
| Read more on the standards: [R4](https://hl7.org/fhir/R4/overview.html) and [STU3](https://hl7.org/fhir/STU3/overview.html). | ||
|
|
||
| In this codebase, the FHIR package provides strongly typed Python models for request validation, response parsing, and safe serialization. | ||
|
|
||
| ## FHIR versions in Clinical Data Sharing APIs | ||
|
|
||
| Two FHIR versions are used: | ||
|
|
||
| - STU3: used only for inbound Gateway API operation messages with `resourceType` Parameters (the Access Record Structured request payload). | ||
| - R4: used for all other typed resources in this module, including PDS FHIR resources such as Patient. | ||
|
|
||
| Version behaviour in the current flow: | ||
|
|
||
| - Inbound request body is validated as STU3 Parameters. | ||
| - Outbound provider response body is returned without transformation (mirrored payload). | ||
| - PDS, SDS, and internal typed handling use R4 resource models. | ||
|
|
||
| ## How Pydantic is used | ||
|
|
||
| This package uses Pydantic to make FHIR payload handling explicit and safe: | ||
|
|
||
| - Model validation: model_validate(...) is used to parse inbound JSON into typed models. | ||
| - Field aliasing: FHIR JSON names like `resourceType`, `fullUrl`, `lastUpdated` are mapped with `Field(alias=...)`. | ||
| - Type constraints: `Annotated`, `Literal`, and `min_length` constraints enforce schema-like rules. | ||
| - Runtime guards: validators check that `resourceType` and identifier system values match expected FHIR semantics. | ||
| - Polymorphism: the Resource base type dispatches to the correct subclass from `resourceType`. | ||
| - Serialization: `model_dump()`/`model_dump_json()` default to exclude_none=True to avoid emitting empty FHIR fields. | ||
|
|
||
| Typical patterns in this code: | ||
|
|
||
| - Parse JSON from API input or upstream systems into typed models. | ||
| - Access domain properties (for example, `Patient.nhs_number`) instead of raw dictionary traversal. | ||
| - Serialize models back to canonical FHIR JSON with aliases preserved. | ||
|
|
||
| ## Example usage | ||
|
|
||
| The example below shows how to load a simple FHIR R4 Patient payload and obtain the GP ODS code. | ||
|
|
||
| ```python | ||
| from fhir.r4 import Patient | ||
|
|
||
| payload = { | ||
| "resourceType": "Patient", | ||
| "identifier": [ | ||
| { | ||
| "system": "https://fhir.nhs.uk/Id/nhs-number", | ||
| "value": "9000000009", | ||
| } | ||
| ], | ||
| "generalPractitioner": [ | ||
| { | ||
| "type": "Organization", | ||
| "identifier": { | ||
| "system": "https://fhir.nhs.uk/Id/ods-organization-code", | ||
| "value": "A12345", | ||
| }, | ||
| } | ||
| ], | ||
| } | ||
|
|
||
| patient = Patient.model_validate(payload) | ||
|
|
||
| nhs_number = patient.nhs_number | ||
| gp_ods_code = patient.gp_ods_code | ||
|
|
||
| print(nhs_number) # 9000000009 | ||
| print(gp_ods_code) # A12345 | ||
| ``` | ||
|
|
||
| If `generalPractitioner` is missing, `patient.gp_ods_code` returns `None`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,3 @@ | ||
| """FHIR data types and resources.""" | ||
| from .resources.resource import Resource | ||
|
|
||
| from fhir.bundle import Bundle, BundleEntry | ||
| from fhir.general_practitioner import GeneralPractitioner | ||
| from fhir.human_name import HumanName | ||
| from fhir.identifier import Identifier | ||
| from fhir.operation_outcome import OperationOutcome, OperationOutcomeIssue | ||
| from fhir.parameters import Parameter, Parameters | ||
| from fhir.patient import Patient | ||
|
|
||
| __all__ = [ | ||
| "Bundle", | ||
| "BundleEntry", | ||
| "HumanName", | ||
| "Identifier", | ||
| "OperationOutcome", | ||
| "OperationOutcomeIssue", | ||
| "Parameter", | ||
| "Parameters", | ||
| "Patient", | ||
| "GeneralPractitioner", | ||
| ] | ||
| __all__ = ["Resource"] |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| """FHIR data types and resources.""" | ||
|
|
||
| from .elements.identifier import Identifier, NHSNumberValueIdentifier, UUIDIdentifier | ||
| from .elements.issue import Issue, IssueCode, IssueSeverity | ||
| from .elements.reference import Reference | ||
| from .resources.bundle import Bundle | ||
| from .resources.device import Device | ||
| from .resources.endpoint import Endpoint | ||
| from .resources.operation_outcome import OperationOutcome | ||
| from .resources.patient import Patient | ||
|
|
||
| __all__ = [ | ||
| "Bundle", | ||
| "Device", | ||
| "Endpoint", | ||
| "Identifier", | ||
| "Issue", | ||
| "IssueCode", | ||
| "IssueSeverity", | ||
| "OperationOutcome", | ||
| "Patient", | ||
| "NHSNumberValueIdentifier", | ||
| "Reference", | ||
| "UUIDIdentifier", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import uuid | ||
| from abc import ABC | ||
| from dataclasses import dataclass | ||
| from typing import ClassVar | ||
|
|
||
| from pydantic import model_validator | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class Identifier(ABC): | ||
| """ | ||
| A FHIR R4 Identifier element. See https://hl7.org/fhir/R4/datatypes.html#Identifier. | ||
| Attributes: | ||
| system: The namespace for the identifier value. | ||
| value: The value that is unique within the system. | ||
| """ | ||
|
|
||
| _expected_system: ClassVar[str] = "__unknown__" | ||
|
|
||
| value: str | ||
| system: str | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_system(self) -> "Identifier": | ||
| if self.system != self._expected_system: | ||
| raise ValueError( | ||
| f"Identifier system '{self.system}' does not match expected " | ||
| f"system '{self._expected_system}'." | ||
| ) | ||
| return self | ||
davidhamill1-nhs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @classmethod | ||
| def __init_subclass__(cls, expected_system: str) -> None: | ||
| cls._expected_system = expected_system | ||
|
|
||
davidhamill1-nhs marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+9
to
+35
|
||
|
|
||
| class UUIDIdentifier(Identifier, expected_system="https://tools.ietf.org/html/rfc4122"): | ||
| """A UUID identifier utilising the standard RFC 4122 system.""" | ||
|
|
||
| def __init__(self, value: uuid.UUID | None = None): | ||
| super().__init__( | ||
| value=str(value or uuid.uuid4()), | ||
| system=self._expected_system, | ||
|
Comment on lines
+40
to
+43
|
||
| ) | ||
|
|
||
|
|
||
| class NHSNumberValueIdentifier( | ||
| Identifier, expected_system="https://fhir.nhs.uk/Id/nhs-number" | ||
| ): | ||
| """A valueIdentifier NHS numbers - used in Parameter""" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| from abc import ABC | ||
| from dataclasses import dataclass | ||
| from enum import StrEnum | ||
|
|
||
|
|
||
| class IssueSeverity(StrEnum): | ||
| FATAL = "fatal" | ||
| ERROR = "error" | ||
| WARNING = "warning" | ||
| INFORMATION = "information" | ||
|
|
||
|
|
||
| class IssueCode(StrEnum): | ||
| INVALID = "invalid" | ||
| EXCEPTION = "exception" | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class Issue(ABC): | ||
| """ | ||
| A FHIR R4 OperationOutcome Issue element. See https://hl7.org/fhir/R4/datatypes.html#OperationOutcome. | ||
| """ | ||
|
|
||
| severity: IssueSeverity | ||
| code: IssueCode | ||
| diagnostics: str | None = None |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from typing import ClassVar | ||
|
|
||
| from pydantic import BaseModel, Field, model_validator | ||
|
|
||
| from .identifier import Identifier | ||
|
|
||
|
|
||
| class Reference(BaseModel): | ||
| """A FHIR R4 Reference base class.""" | ||
|
|
||
| _expected_reference_type: ClassVar[str] = "__unknown__" | ||
|
|
||
| identifier: Identifier | ||
| reference_type: str = Field(alias="type") | ||
|
|
||
| reference: str | None = None | ||
| display: str | None = None | ||
|
|
||
| def __init_subclass__(cls, reference_type: str) -> None: | ||
| cls._expected_reference_type = reference_type | ||
| super().__init_subclass__() | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_reference_type(self) -> "Reference": | ||
| if self.reference_type != self._expected_reference_type: | ||
| raise ValueError( | ||
| f"Reference type '{self.reference_type}' does not match expected " | ||
| f"type '{self._expected_reference_type}'." | ||
| ) | ||
| return self |
Uh oh!
There was an error while loading. Please reload this page.