diff --git a/Makefile b/Makefile index 221cd6c96..bf2406dec 100644 --- a/Makefile +++ b/Makefile @@ -156,6 +156,10 @@ test-python-sdk: typecheck prepare-aidbox-runme generate-python-sdk python-test- . venv/bin/activate && \ python -m pytest test_raw_extension.py -v + cd $(PYTHON_EXAMPLE) && \ + . venv/bin/activate && \ + python -m pytest test_bundle.py -v + test-python-fhirpy-sdk: typecheck prepare-aidbox-runme generate-python-sdk-fhirpy python-fhirpy-test-setup # Run mypy in strict mode cd $(PYTHON_FHIRPY_EXAMPLE) && \ diff --git a/assets/api/writer-generator/python/resource_family_validator.py b/assets/api/writer-generator/python/resource_family_validator.py deleted file mode 100644 index 36e069eab..000000000 --- a/assets/api/writer-generator/python/resource_family_validator.py +++ /dev/null @@ -1,92 +0,0 @@ -import re -import importlib -import importlib.util -from typing import Any, Annotated, List - -from pydantic import BeforeValidator, BaseModel, ValidationError -from pydantic_core import ValidationError as PydanticCoreValidationError - - -def to_snake_case(name: str) -> str: - s = re.sub(r"(? bool: - """Checks if a module exists without importing it""" - return importlib.util.find_spec(name) is not None - - -def import_and_create_module(module_name: str, class_name: str) -> Any: - """ - Dynamically import a module and create an instance of a specified class. - - Args: - module_name: String name of the module (e.g., 'aidbox.hl7_fhir_r4_core.patient') - class_name: String name of the class (e.g., 'Patient') - - Returns: - Instance of the specified class - """ - try: - module = importlib.import_module(module_name) - class_obj = getattr(module, class_name) - return class_obj - - except (ImportError, AttributeError) as e: - raise ImportError(f"Could not import {class_name} from {module_name}: {e}") - - -def import_and_create_module_if_exists(package: str, class_name: str) -> Any: - """ - Dynamically import a module and create an instance of a specified class if the module exists. - - Args: - package: String name of the package (e.g., 'aidbox.hl7_fhir_r4_core') - class_name: String name of the class (e.g., 'Patient') - - Returns: - Instance of the specified class or None if the module does not exist - """ - module_name = package + "." + to_snake_case(class_name) - if module_exists(module_name): - return import_and_create_module(module_name, class_name) - else: - return None - - -def validate_and_downcast( - resource_data: dict[str, Any], package_list: List[str], family: List[str] -) -> Any: - """ - Validates and downcasts ResourceFamily to the appropriate FHIR resource class - - Args: - resource_data: Input value (dict) - package_list: List of package names to search for resource classes (e.g., ['aidbox.hl7_fhir_r4_core', 'aidbox.hl7_fhir_r4_extras']) - family: List of valid resource types (e.g., 'Group' or 'Patient') - - Returns: - Instance of the appropriate FHIR resource class - """ - - # Extract and validate resource type - resource_type = resource_data.get("resourceType") - if not resource_type: - raise ValueError("Missing 'resourceType' field in resource") - - if resource_type not in family: - raise ValueError(f"Invalid resourceType '{resource_type}'. ") - - # Dynamically import and instantiate the appropriate class - target_class = None - for package in package_list: - target_class = import_and_create_module_if_exists(package, resource_type) - if target_class is not None: - break - if target_class is None: - raise ImportError( - f"Could not find class for resourceType '{resource_type}' in packages {package_list}" - ) - - return target_class.model_validate(resource_data) diff --git a/assets/api/writer-generator/python/resource_preprocessor.py b/assets/api/writer-generator/python/resource_preprocessor.py new file mode 100644 index 000000000..da19d0126 --- /dev/null +++ b/assets/api/writer-generator/python/resource_preprocessor.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import re +import importlib +import importlib.util +from typing import Any + + +def _to_snake_case(name: str) -> str: + return re.sub(r"(? Any: + module_name = f"{package}.{_to_snake_case(resource_type)}" + if importlib.util.find_spec(module_name) is None: + return None + module = importlib.import_module(module_name) + return getattr(module, resource_type, None) + + +def _preprocess_value(value: Any, package: str) -> Any: + if isinstance(value, dict): + resource_type = value.get("resourceType") + if resource_type and isinstance(resource_type, str): + cls = _import_resource_class(package, resource_type) + if cls is not None: + return cls.model_validate(value) + return {k: _preprocess_value(v, package) for k, v in value.items()} + if isinstance(value, list): + return [_preprocess_value(item, package) for item in value] + return value + + +def preprocess_resource_fields(data: dict[str, Any], package: str) -> dict[str, Any]: + """Walk a FHIR resource dict and replace nested resource dicts with concrete model instances. + + Intended for use as a model_validator(mode='before') on generic resource containers + such as Bundle or DomainResource. Processes field values (not the root dict itself) so + the caller's own Pydantic validation still runs normally. + """ + return {k: _preprocess_value(v, package) for k, v in data.items()} \ No newline at end of file diff --git a/examples/python/fhir_types/__init__.py b/examples/python/fhir_types/__init__.py index 21844fe8c..ba95ece40 100644 --- a/examples/python/fhir_types/__init__.py +++ b/examples/python/fhir_types/__init__.py @@ -13,7 +13,6 @@ Bundle, BundleEntry, BundleEntryRequest, BundleEntryResponse, BundleEntrySearch, BundleLink ) from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily from fhir_types.hl7_fhir_r4_core.observation import (\ Observation, ObservationComponent, ObservationReferenceRange ) @@ -22,7 +21,6 @@ Patient, PatientCommunication, PatientContact, PatientLink ) from fhir_types.hl7_fhir_r4_core.resource import Resource -from fhir_types.hl7_fhir_r4_core.resource_families import ResourceFamily Address.model_rebuild() Age.model_rebuild() diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/__init__.py b/examples/python/fhir_types/hl7_fhir_r4_core/__init__.py index 50763e0ed..267886aa4 100644 --- a/examples/python/fhir_types/hl7_fhir_r4_core/__init__.py +++ b/examples/python/fhir_types/hl7_fhir_r4_core/__init__.py @@ -13,7 +13,6 @@ Bundle, BundleEntry, BundleEntryRequest, BundleEntryResponse, BundleEntrySearch, BundleLink ) from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily from fhir_types.hl7_fhir_r4_core.observation import (\ Observation, ObservationComponent, ObservationReferenceRange ) @@ -22,7 +21,6 @@ Patient, PatientCommunication, PatientContact, PatientLink ) from fhir_types.hl7_fhir_r4_core.resource import Resource -from fhir_types.hl7_fhir_r4_core.resource_families import ResourceFamily __all__ = [ 'Address', diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/bundle.py b/examples/python/fhir_types/hl7_fhir_r4_core/bundle.py index 32d26cff3..1f7496db6 100644 --- a/examples/python/fhir_types/hl7_fhir_r4_core/bundle.py +++ b/examples/python/fhir_types/hl7_fhir_r4_core/bundle.py @@ -3,24 +3,36 @@ # Any manual changes made to this file may be overwritten. from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field, PositiveInt -from typing import Any, List as PyList, Literal +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator +from typing import Any, Generic, List as PyList, Literal +from typing_extensions import Self, TypeVar from fhir_types.hl7_fhir_r4_core.base import BackboneElement, Identifier, Signature from fhir_types.hl7_fhir_r4_core.resource import Resource -from fhir_types.hl7_fhir_r4_core.resource_families import ResourceFamily from fhir_types.hl7_fhir_r4_core.base import Element +from fhir_types.hl7_fhir_r4_core.resource_preprocessor import preprocess_resource_fields +T = TypeVar('T', bound=Resource, default=Resource) +T1 = TypeVar('T1', bound=Resource, default=Resource) +T2 = TypeVar('T2', bound=Resource, default=Resource) -class BundleEntry(BackboneElement): + +class BundleEntry(BackboneElement, Generic[T1, T2]): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") full_url: str | None = Field(None, alias="fullUrl", serialization_alias="fullUrl") link: PyList[BundleLink] | None = Field(None, alias="link", serialization_alias="link") request: BundleEntryRequest | None = Field(None, alias="request", serialization_alias="request") - resource: ResourceFamily | None = Field(None, alias="resource", serialization_alias="resource") - response: BundleEntryResponse | None = Field(None, alias="response", serialization_alias="response") + resource: T1 | None = Field(None, alias="resource", serialization_alias="resource") + response: BundleEntryResponse[T2] | None = Field(None, alias="response", serialization_alias="response") search: BundleEntrySearch | None = Field(None, alias="search", serialization_alias="search") + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + class BundleEntryRequest(BackboneElement): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") if_match: str | None = Field(None, alias="ifMatch", serialization_alias="ifMatch") @@ -30,14 +42,21 @@ class BundleEntryRequest(BackboneElement): method: Literal["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"] = Field(alias="method", serialization_alias="method") url: str = Field(alias="url", serialization_alias="url") -class BundleEntryResponse(BackboneElement): +class BundleEntryResponse(BackboneElement, Generic[T]): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") etag: str | None = Field(None, alias="etag", serialization_alias="etag") last_modified: str | None = Field(None, alias="lastModified", serialization_alias="lastModified") location: str | None = Field(None, alias="location", serialization_alias="location") - outcome: ResourceFamily | None = Field(None, alias="outcome", serialization_alias="outcome") + outcome: T | None = Field(None, alias="outcome", serialization_alias="outcome") status: str = Field(alias="status", serialization_alias="status") + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + class BundleEntrySearch(BackboneElement): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") mode: Literal["match", "include", "outcome"] | None = Field(None, alias="mode", serialization_alias="mode") @@ -49,7 +68,7 @@ class BundleLink(BackboneElement): url: str = Field(alias="url", serialization_alias="url") -class Bundle(Resource): +class Bundle(Resource, Generic[T1, T2]): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") resource_type: Literal['Bundle'] = Field( default='Bundle', @@ -58,7 +77,7 @@ class Bundle(Resource): frozen=True, pattern='Bundle' ) - entry: PyList[BundleEntry] | None = Field(None, alias="entry", serialization_alias="entry") + entry: PyList[BundleEntry[T1, T2]] | None = Field(None, alias="entry", serialization_alias="entry") identifier: Identifier | None = Field(None, alias="identifier", serialization_alias="identifier") link: PyList[BundleLink] | None = Field(None, alias="link", serialization_alias="link") signature: Signature | None = Field(None, alias="signature", serialization_alias="signature") @@ -76,6 +95,13 @@ def to_json(self, indent: int | None = None) -> str: return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> Bundle: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/domain_resource.py b/examples/python/fhir_types/hl7_fhir_r4_core/domain_resource.py index f32963a83..3d9310af9 100644 --- a/examples/python/fhir_types/hl7_fhir_r4_core/domain_resource.py +++ b/examples/python/fhir_types/hl7_fhir_r4_core/domain_resource.py @@ -3,15 +3,18 @@ # Any manual changes made to this file may be overwritten. from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field, PositiveInt -from typing import Any, List as PyList, Literal +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator +from typing import Any, Generic, List as PyList, Literal +from typing_extensions import Self, TypeVar from fhir_types.hl7_fhir_r4_core.base import Extension, Narrative from fhir_types.hl7_fhir_r4_core.resource import Resource -from fhir_types.hl7_fhir_r4_core.resource_families import ResourceFamily +from fhir_types.hl7_fhir_r4_core.resource_preprocessor import preprocess_resource_fields +T = TypeVar('T', bound=Resource, default=Resource) -class DomainResource(Resource): + +class DomainResource(Resource, Generic[T]): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") resource_type: str = Field( default='DomainResource', @@ -20,7 +23,7 @@ class DomainResource(Resource): frozen=True, pattern='DomainResource' ) - contained: PyList[ResourceFamily] | None = Field(None, alias="contained", serialization_alias="contained") + contained: PyList[T] | None = Field(None, alias="contained", serialization_alias="contained") extension: PyList[Extension] | None = Field(None, alias="extension", serialization_alias="extension") modifier_extension: PyList[Extension] | None = Field(None, alias="modifierExtension", serialization_alias="modifierExtension") text: Narrative | None = Field(None, alias="text", serialization_alias="text") @@ -32,6 +35,13 @@ def to_json(self, indent: int | None = None) -> str: return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> DomainResource: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/observation.py b/examples/python/fhir_types/hl7_fhir_r4_core/observation.py index 6c9546740..59010c80f 100644 --- a/examples/python/fhir_types/hl7_fhir_r4_core/observation.py +++ b/examples/python/fhir_types/hl7_fhir_r4_core/observation.py @@ -5,13 +5,13 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r4_core.base import (\ Annotation, BackboneElement, CodeableConcept, Identifier, Period, Quantity, Range, Ratio, Reference, SampledData, \ Timing ) from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily from fhir_types.hl7_fhir_r4_core.base import Element @@ -106,6 +106,6 @@ def to_json(self, indent: int | None = None) -> str: return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> Observation: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/operation_outcome.py b/examples/python/fhir_types/hl7_fhir_r4_core/operation_outcome.py index c1b7ded21..af0c3a9f3 100644 --- a/examples/python/fhir_types/hl7_fhir_r4_core/operation_outcome.py +++ b/examples/python/fhir_types/hl7_fhir_r4_core/operation_outcome.py @@ -5,10 +5,10 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r4_core.base import BackboneElement, CodeableConcept from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily class OperationOutcomeIssue(BackboneElement): @@ -39,6 +39,6 @@ def to_json(self, indent: int | None = None) -> str: return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> OperationOutcome: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/patient.py b/examples/python/fhir_types/hl7_fhir_r4_core/patient.py index f3d065902..5e94d0e53 100644 --- a/examples/python/fhir_types/hl7_fhir_r4_core/patient.py +++ b/examples/python/fhir_types/hl7_fhir_r4_core/patient.py @@ -5,12 +5,12 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r4_core.base import (\ Address, Attachment, BackboneElement, CodeableConcept, ContactPoint, HumanName, Identifier, Period, Reference ) from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily from fhir_types.hl7_fhir_r4_core.base import Element @@ -77,6 +77,6 @@ def to_json(self, indent: int | None = None) -> str: return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> Patient: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/resource.py b/examples/python/fhir_types/hl7_fhir_r4_core/resource.py index 4fd585285..ce1b4b216 100644 --- a/examples/python/fhir_types/hl7_fhir_r4_core/resource.py +++ b/examples/python/fhir_types/hl7_fhir_r4_core/resource.py @@ -5,6 +5,7 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r4_core.base import Meta from fhir_types.hl7_fhir_r4_core.base import Element @@ -34,6 +35,6 @@ def to_json(self, indent: int | None = None) -> str: return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> Resource: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/resource_families.py b/examples/python/fhir_types/hl7_fhir_r4_core/resource_families.py deleted file mode 100644 index e4ebe30f3..000000000 --- a/examples/python/fhir_types/hl7_fhir_r4_core/resource_families.py +++ /dev/null @@ -1,115 +0,0 @@ -# WARNING: This file is autogenerated by @atomic-ehr/codegen. -# GitHub: https://github.com/atomic-ehr/codegen -# Any manual changes made to this file may be overwritten. - -import re -import importlib -import importlib.util -from typing import Any, Annotated, List - -from pydantic import BeforeValidator, BaseModel, ValidationError -from pydantic_core import ValidationError as PydanticCoreValidationError - - -def to_snake_case(name: str) -> str: - s = re.sub(r"(? bool: - """Checks if a module exists without importing it""" - return importlib.util.find_spec(name) is not None - - -def import_and_create_module(module_name: str, class_name: str) -> Any: - """ - Dynamically import a module and create an instance of a specified class. - - Args: - module_name: String name of the module (e.g., 'aidbox.hl7_fhir_r4_core.patient') - class_name: String name of the class (e.g., 'Patient') - - Returns: - Instance of the specified class - """ - try: - module = importlib.import_module(module_name) - class_obj = getattr(module, class_name) - return class_obj - - except (ImportError, AttributeError) as e: - raise ImportError(f"Could not import {class_name} from {module_name}: {e}") - - -def import_and_create_module_if_exists(package: str, class_name: str) -> Any: - """ - Dynamically import a module and create an instance of a specified class if the module exists. - - Args: - package: String name of the package (e.g., 'aidbox.hl7_fhir_r4_core') - class_name: String name of the class (e.g., 'Patient') - - Returns: - Instance of the specified class or None if the module does not exist - """ - module_name = package + "." + to_snake_case(class_name) - if module_exists(module_name): - return import_and_create_module(module_name, class_name) - else: - return None - - -def validate_and_downcast( - resource_data: dict[str, Any], package_list: List[str], family: List[str] -) -> Any: - """ - Validates and downcasts ResourceFamily to the appropriate FHIR resource class - - Args: - resource_data: Input value (dict) - package_list: List of package names to search for resource classes (e.g., ['aidbox.hl7_fhir_r4_core', 'aidbox.hl7_fhir_r4_extras']) - family: List of valid resource types (e.g., 'Group' or 'Patient') - - Returns: - Instance of the appropriate FHIR resource class - """ - - # Extract and validate resource type - resource_type = resource_data.get("resourceType") - if not resource_type: - raise ValueError("Missing 'resourceType' field in resource") - - if resource_type not in family: - raise ValueError(f"Invalid resourceType '{resource_type}'. ") - - # Dynamically import and instantiate the appropriate class - target_class = None - for package in package_list: - target_class = import_and_create_module_if_exists(package, resource_type) - if target_class is not None: - break - if target_class is None: - raise ImportError( - f"Could not find class for resourceType '{resource_type}' in packages {package_list}" - ) - - return target_class.model_validate(resource_data) - - -packages = ['fhir_types.hl7_fhir_r4_core'] - -DomainResourceFamily_resources = ['Observation', 'OperationOutcome', 'Patient'] - -def validate_and_downcast_DomainResourceFamily(v: Any) -> Any: - return validate_and_downcast(v, packages, DomainResourceFamily_resources) - -type DomainResourceFamily = Annotated[Any, BeforeValidator(validate_and_downcast_DomainResourceFamily)] - -ResourceFamily_resources = ['Bundle', 'DomainResource', 'Observation', 'OperationOutcome', 'Patient'] - -def validate_and_downcast_ResourceFamily(v: Any) -> Any: - return validate_and_downcast(v, packages, ResourceFamily_resources) - -type ResourceFamily = Annotated[Any, BeforeValidator(validate_and_downcast_ResourceFamily)] - -__all__ = ['DomainResourceFamily', 'ResourceFamily'] diff --git a/examples/python/fhir_types/hl7_fhir_r4_core/resource_preprocessor.py b/examples/python/fhir_types/hl7_fhir_r4_core/resource_preprocessor.py new file mode 100644 index 000000000..da19d0126 --- /dev/null +++ b/examples/python/fhir_types/hl7_fhir_r4_core/resource_preprocessor.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import re +import importlib +import importlib.util +from typing import Any + + +def _to_snake_case(name: str) -> str: + return re.sub(r"(? Any: + module_name = f"{package}.{_to_snake_case(resource_type)}" + if importlib.util.find_spec(module_name) is None: + return None + module = importlib.import_module(module_name) + return getattr(module, resource_type, None) + + +def _preprocess_value(value: Any, package: str) -> Any: + if isinstance(value, dict): + resource_type = value.get("resourceType") + if resource_type and isinstance(resource_type, str): + cls = _import_resource_class(package, resource_type) + if cls is not None: + return cls.model_validate(value) + return {k: _preprocess_value(v, package) for k, v in value.items()} + if isinstance(value, list): + return [_preprocess_value(item, package) for item in value] + return value + + +def preprocess_resource_fields(data: dict[str, Any], package: str) -> dict[str, Any]: + """Walk a FHIR resource dict and replace nested resource dicts with concrete model instances. + + Intended for use as a model_validator(mode='before') on generic resource containers + such as Bundle or DomainResource. Processes field values (not the root dict itself) so + the caller's own Pydantic validation still runs normally. + """ + return {k: _preprocess_value(v, package) for k, v in data.items()} \ No newline at end of file diff --git a/examples/python/test_bundle.py b/examples/python/test_bundle.py new file mode 100644 index 000000000..42e810b9a --- /dev/null +++ b/examples/python/test_bundle.py @@ -0,0 +1,59 @@ +import pytest +from pydantic import ValidationError + +from fhir_types.hl7_fhir_r4_core.base import CodeableConcept +from fhir_types.hl7_fhir_r4_core.bundle import Bundle, BundleEntry +from fhir_types.hl7_fhir_r4_core.observation import Observation +from fhir_types.hl7_fhir_r4_core.patient import Patient + + +def test_bundle_generic_narrows_entry_resources() -> None: + patient = Patient(id="p-1") + observation = Observation(id="obs-1", status="final", code=CodeableConcept()) + + bundle: Bundle[Patient | Observation] = Bundle( + type="transaction", + entry=[ + BundleEntry(resource=patient), + BundleEntry(resource=observation), + ], + ) + + observations = [ + e.resource + for e in (bundle.entry or []) + if e.resource and e.resource.resource_type == "Observation" + ] + assert len(observations) == 1 + assert observations[0].id == "obs-1" + + +def test_bundle_entry_generic_narrows_resource() -> None: + patient = Patient(id="p-1") + entry: BundleEntry[Patient] = BundleEntry(resource=patient) + assert entry.resource.resource_type == "Patient" + + +def test_bundle_without_type_param_is_backwards_compatible() -> None: + patient = Patient(id="p-1") + bundle: Bundle = Bundle( + type="collection", + entry=[BundleEntry(resource=patient)], + ) + assert len(bundle.entry) == 1 + + +def test_bundle_from_json_raises_on_invalid_resource() -> None: + # Observation requires `status` and `code` — omitting them causes a runtime ValidationError + bundle_json = """{ + "resourceType": "Bundle", + "type": "searchset", + "entry": [{ + "resource": { + "resourceType": "Observation", + "id": "obs-1" + } + }] + }""" + with pytest.raises(ValidationError): + Bundle.from_json(bundle_json) \ No newline at end of file diff --git a/examples/python/test_sdk.py b/examples/python/test_sdk.py index 461883687..78cb6007b 100644 --- a/examples/python/test_sdk.py +++ b/examples/python/test_sdk.py @@ -218,6 +218,28 @@ def test_to_from_json() -> None: assert p == p2 +def test_bundle_from_json() -> None: + json = """{ + "resourceType": "Bundle", + "type": "searchset", + "total": 1, + "entry": [{ + "resource": { + "resourceType": "Patient", + "id": "p-1", + "gender": "female" + } + }] + }""" + bundle = Bundle.from_json(json) + assert bundle.entry is not None + assert len(bundle.entry) == 1 + resource = bundle.entry[0].resource + assert resource is not None + assert resource.id == "p-1" + assert type(resource) is Patient + + def test_to_json_shape() -> None: import json as json_mod diff --git a/src/api/writer-generator/python.ts b/src/api/writer-generator/python.ts index 7329781bf..a4aaef29a 100644 --- a/src/api/writer-generator/python.ts +++ b/src/api/writer-generator/python.ts @@ -1,5 +1,4 @@ import assert from "node:assert"; -import fs from "node:fs"; import * as Path from "node:path"; import { fileURLToPath } from "node:url"; import { camelCase, pascalCase, snakeCase, uppercaseFirstLetterOfEach } from "@root/api/writer-generator/utils"; @@ -9,6 +8,7 @@ import { type CanonicalUrl, type EnumDefinition, type Field, + isNestedTypeSchema, isPrimitiveIdentifier, isResourceTypeSchema, isSpecializationTypeSchema, @@ -95,6 +95,24 @@ const GENERIC_FIELD_REWRITES: Record> = { CodeableConcept: { coding: "Coding[T]" }, }; +const leafOf = (path: string[]): string => path[path.length - 1] ?? ""; + +const collectResourceGenericTypeVars = ( + schema: SpecializationTypeSchema, +): Array<{ typeVar: string; constraint: string }> => { + const all = new Map(); + const addParams = (s: SpecializationTypeSchema | NestedTypeSchema) => { + for (const p of s.generic?.params ?? []) { + if (!all.has(p.typeVar)) all.set(p.typeVar, p.constraint.name); + } + }; + addParams(schema); + for (const nested of schema.nested ?? []) addParams(nested); + return Array.from(all.entries()) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([typeVar, constraint]) => ({ typeVar, constraint })); +}; + const pyEnumType = (enumDef: EnumDefinition): string => { const values = enumDef.values.map((e) => `"${e}"`).join(", "); return enumDef.isOpen ? `Literal[${values}] | str` : `Literal[${values}]`; @@ -230,7 +248,11 @@ export class Python extends Writer { const pyPackageName = this.pyFhirPackageByName(packageName); this.generateResourcePackageInit(pyPackageName, packageResources, packageComplexTypes); - this.generateResourceFamilies(packageResources); + + const hasAnyResourceGenericParams = packageResources.some((s) => collectResourceGenericTypeVars(s).length > 0); + if (hasAnyResourceGenericParams) { + this.copyAssets(resolvePyAssets("resource_preprocessor.py"), "resource_preprocessor.py"); + } for (const schema of packageResources) { this.generateResourceModule(schema); @@ -354,14 +376,7 @@ export class Python extends Writer { this.pyImportFrom(moduleName, ...importNames); - const names = [...importNames]; - - if (this.shouldImportResourceFamily(resource)) { - const familyName = `${resource.identifier.name}Family`; - this.pyImportFrom(`${fullPyPackageName}.resource_families`, familyName); - } - - return names; + return [...importNames]; } private collectResourceImportNames(resource: SpecializationTypeSchema): string[] { @@ -375,10 +390,6 @@ export class Python extends Writer { return names; } - private shouldImportResourceFamily(resource: SpecializationTypeSchema): boolean { - return resource.identifier.kind === "resource" && (resource.typeFamily?.resources?.length ?? 0) > 0; - } - private generateExportsDeclaration( packageComplexTypes: SpecializationTypeSchema[] | undefined, allResourceNames: string[], @@ -396,12 +407,23 @@ export class Python extends Writer { } private generateResourceModule(schema: SpecializationTypeSchema): void { + const typeVars = collectResourceGenericTypeVars(schema); + const hasResourceGenericParams = typeVars.length > 0; + this.cat(`${snakeCase(schema.identifier.name)}.py`, () => { this.generateDisclaimer(); - this.generateDefaultImports(false); + this.generateDefaultImports(false, hasResourceGenericParams, true); this.generateFhirBaseModelImport(); this.line(); this.generateDependenciesImports(schema); + if (hasResourceGenericParams) { + const pyFhirPackage = this.pyFhirPackageByName(schema.identifier.package); + this.pyImportFrom(`${pyFhirPackage}.resource_preprocessor`, "preprocess_resource_fields"); + this.line(); + for (const { typeVar, constraint } of typeVars) { + this.line(`${typeVar} = TypeVar('${typeVar}', bound=${constraint}, default=${constraint})`); + } + } this.line(); this.generateNestedTypes(schema); this.line(); @@ -430,6 +452,11 @@ export class Python extends Writer { if (schema.base) bases.push(schema.base.name); bases.push(...this.injectSuperClasses(schema.identifier.url)); if (schema.identifier.name in GENERIC_FIELD_REWRITES) bases.push("Generic[T]"); + const params = schema.generic?.params ?? []; + if (params.length > 0) { + const typeVars = params.map((p) => p.typeVar).join(", "); + bases.push(`Generic[${typeVars}]`); + } return bases; } @@ -445,11 +472,26 @@ export class Python extends Writer { this.generateResourceTypeField(schema); } - this.generateFields(schema, schema.identifier.name); + this.generateFields(schema); if (isResourceTypeSchema(schema)) { this.generateResourceMethods(schema); } + + if ((schema.generic?.params?.length ?? 0) > 0) { + this.generateResourcePreprocessorMethod(schema); + } + } + + private generateResourcePreprocessorMethod(schema: SpecializationTypeSchema | NestedTypeSchema): void { + const pyFhirPackage = this.pyFhirPackageByName(schema.identifier.package); + this.line(); + this.line("@model_validator(mode='before')"); + this.line("@classmethod"); + this.line("def _preprocess_resources(cls, data: Any) -> Any:"); + this.line(" if isinstance(data, dict):"); + this.line(` return preprocess_resource_fields(data, "${pyFhirPackage}")`); + this.line(" return data"); } private generateModelConfig(): void { @@ -478,14 +520,14 @@ export class Python extends Writer { this.line(")"); } - private generateFields(schema: SpecializationTypeSchema | NestedTypeSchema, schemaName: string): void { + private generateFields(schema: SpecializationTypeSchema | NestedTypeSchema): void { const sortedFields = Object.entries(schema.fields ?? []).sort(([a], [b]) => a.localeCompare(b)); const withExtensions = this.shouldAddPrimitiveExtensions(schema); for (const [fieldName, field] of sortedFields) { if ("choices" in field && field.choices) continue; - const fieldInfo = this.buildFieldInfo(fieldName, field, schemaName); + const fieldInfo = this.buildFieldInfo(fieldName, field, schema); this.line(`${fieldInfo.name}: ${fieldInfo.type}${fieldInfo.defaultValue}`); if (withExtensions && "type" in field && isPrimitiveIdentifier(field.type)) { @@ -512,9 +554,13 @@ export class Python extends Writer { this.line(`${pyFieldName}: ${typeExpr} = Field(None, ${aliasSpec})`); } - private buildFieldInfo(fieldName: string, field: Field, schemaName: string): FieldInfo { + private buildFieldInfo( + fieldName: string, + field: Field, + schema: SpecializationTypeSchema | NestedTypeSchema, + ): FieldInfo { const pyFieldName = fixReservedWords(this.nameFormatFunction(fieldName)); - const fieldType = this.determineFieldType(field, fieldName, schemaName); + const fieldType = this.determineFieldType(field, fieldName, schema); const defaultValue = this.getFieldDefaultValue(field, fieldName); return { @@ -524,10 +570,15 @@ export class Python extends Writer { }; } - private determineFieldType(field: Field, fieldName: string, schemaName: string): string { + private determineFieldType( + field: Field, + fieldName: string, + schema: SpecializationTypeSchema | NestedTypeSchema, + ): string { + const schemaName = schema.identifier.name; let fieldType = field ? this.getBaseFieldType(field) : ""; - // Check for generic type field rewrites (e.g., Coding.code → T, CodeableConcept.coding → Coding[T]) + // String-bound generics (Coding.code → T, CodeableConcept.coding → Coding[T]) const rewrite = GENERIC_FIELD_REWRITES[schemaName]?.[fieldName]; if (rewrite) { fieldType = rewrite; @@ -536,6 +587,31 @@ export class Python extends Writer { return fieldType; } + // Resource-bound generics: field IS the param (direct introduce) + const params = schema.generic?.params ?? []; + const directParam = params.find((p) => leafOf(p.path) === fieldName); + if (directParam) { + fieldType = directParam.typeVar; + if (field.array) fieldType = `PyList[${fieldType}]`; + if (!field.required) fieldType = `${fieldType} | None`; + return fieldType; + } + + // Resource-bound generics: field's type is itself a generic schema (passthrough) + if ("type" in field && field.type && params.length > 0) { + assert(this.tsIndex !== undefined); + const target = this.tsIndex.resolveType(field.type); + if (target && (isNestedTypeSchema(target) || isSpecializationTypeSchema(target))) { + const nestedParams = target.generic?.params ?? []; + if (nestedParams.length > 0) { + const args = nestedParams.map( + (np) => params.find((p) => leafOf(p.path) === leafOf(np.path))?.typeVar ?? np.typeVar, + ); + fieldType = `${fieldType}[${args.join(", ")}]`; + } + } + } + if ("enum" in field && field.enum) { const baseTypeName = "type" in field ? field.type.name : ""; if (baseTypeName in GENERIC_FIELD_REWRITES) { @@ -578,9 +654,7 @@ export class Python extends Writer { return ` = Field(${aliasSpec})`; } - private generateResourceMethods(schema: SpecializationTypeSchema): void { - const className = schema.identifier.name.toString(); - + private generateResourceMethods(_schema: SpecializationTypeSchema): void { this.line(); this.line("def model_post_init(self, __context: Any) -> None:"); this.line(' self.__pydantic_fields_set__.add("resource_type")'); @@ -589,7 +663,7 @@ export class Python extends Writer { this.line(" return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent)"); this.line(); this.line("@classmethod"); - this.line(`def from_json(cls, json: str) -> ${className}:`); + this.line("def from_json(cls, json: str) -> Self:"); this.line(" return cls.model_validate_json(json)"); } @@ -602,17 +676,24 @@ export class Python extends Writer { } } - private generateDefaultImports(includeGenericImports: boolean): void { + private generateDefaultImports( + includeGenericImports: boolean, + includeResourceGenericImports = false, + includeResourceMethods = false, + ): void { this.pyImportFrom("__future__", "annotations"); - this.pyImportFrom("pydantic", "BaseModel", "ConfigDict", "Field", "PositiveInt"); + const pydanticImports = ["BaseModel", "ConfigDict", "Field", "PositiveInt"]; + if (includeResourceGenericImports) pydanticImports.push("model_validator"); + this.pyImportFrom("pydantic", ...pydanticImports.sort()); const typingImports = ["Any", "List as PyList", "Literal"]; - if (includeGenericImports) { + if (includeGenericImports || includeResourceGenericImports) { typingImports.push("Generic"); } this.pyImportFrom("typing", ...typingImports.sort()); - if (includeGenericImports) { - this.pyImportFrom("typing_extensions", "TypeVar"); - } + const typingExtImports: string[] = []; + if (includeGenericImports || includeResourceGenericImports) typingExtImports.push("TypeVar"); + if (includeResourceMethods) typingExtImports.push("Self"); + if (typingExtImports.length > 0) this.pyImportFrom("typing_extensions", ...typingExtImports.sort()); } private generateDependenciesImports(schema: SpecializationTypeSchema): void { @@ -651,10 +732,6 @@ export class Python extends Writer { for (const dep of resourceDeps) { this.pyImportType(dep); - - const familyName = `${pascalCase(dep.name)}Family`; - const familyPackage = `${this.pyFhirPackage(dep)}.resource_families`; - this.pyImportFrom(familyPackage, familyName); } } @@ -702,78 +779,6 @@ export class Python extends Writer { this.pyImportFrom(this.pyPackage(identifier), pascalCase(identifier.name)); } - private generateResourceFamilies(packageResources: SpecializationTypeSchema[]): void { - assert(this.tsIndex !== undefined); - const packages = //this.helper.getPackages(packageResources, this.opts.rootPackageName); - Object.keys(groupByPackages(packageResources)).map( - (pkgName) => `${this.opts.rootPackageName}.${pkgName.replaceAll(".", "_")}`, - ); - const families: Record = {}; - for (const resource of this.tsIndex.collectResources()) { - const children = (resource.typeFamily?.resources ?? []).map((c) => c.name); - if (children.length > 0) { - const familyName = `${resource.identifier.name}Family`; - families[familyName] = children; - } - } - const exportList = Object.keys(families); - - if (exportList.length === 0) return; - - this.buildResourceFamiliesFile(packages, families, exportList); - } - - private buildResourceFamiliesFile( - packages: string[], - families: Record, - exportList: string[], - ): void { - this.cat("resource_families.py", () => { - this.generateDisclaimer(); - this.includeResourceFamilyValidator(); - this.line(); - this.generateFamilyDefinitions(packages, families); - this.generateFamilyExports(exportList); - }); - } - - private includeResourceFamilyValidator(): void { - const content = fs.readFileSync(resolvePyAssets("resource_family_validator.py"), "utf-8"); - this.line(content); - } - - private generateFamilyDefinitions(packages: string[], families: Record): void { - this.line(`packages = [${packages.map((p) => `'${p}'`).join(", ")}]`); - this.line(); - - for (const [familyName, resources] of Object.entries(families)) { - this.generateFamilyDefinition(familyName, resources); - } - } - - private generateFamilyDefinition(familyName: string, resources: string[]): void { - const listName = `${familyName}_resources`; - - this.line( - `${listName} = [${resources - .map((r) => `'${r}'`) - .sort() - .join(", ")}]`, - ); - this.line(); - - this.line(`def validate_and_downcast_${familyName}(v: Any) -> Any:`); - this.line(` return validate_and_downcast(v, packages, ${listName})`); - this.line(); - - this.line(`type ${familyName} = Annotated[Any, BeforeValidator(validate_and_downcast_${familyName})]`); - this.line(); - } - - private generateFamilyExports(exportList: string[]): void { - this.line(`__all__ = [${exportList.map((e) => `'${e}'`).join(", ")}]`); - } - private buildPyPackageName(packageName: string): string { const parts = packageName ? [snakeCase(packageName)] : [""]; return parts.join("."); diff --git a/test/api/write-generator/__snapshots__/python.test.ts.snap b/test/api/write-generator/__snapshots__/python.test.ts.snap index d09f853e8..d592a0e11 100644 --- a/test/api/write-generator/__snapshots__/python.test.ts.snap +++ b/test/api/write-generator/__snapshots__/python.test.ts.snap @@ -1,6 +1,124 @@ // Bun Snapshot v1, https://bun.sh/docs/test/snapshots -exports[`Python Writer Generator generates Patient resource in inMemoryOnly mode with snapshot 1`] = ` +exports[`Python Writer Generator static files 1`] = ` +"fhirpy>=2.0.0,<3.0.0 +mypy>=1.9.0,<2.0.0 +pydantic>=2.11.0,<3.0.0 +pytest>=8.3.0,<9.0.0 +pytest-asyncio>=0.24.0,<1.0.0 +requests>=2.32.0,<3.0.0 +types-requests>=2.32.0,<3.0.0 +" +`; + +exports[`Python Writer Generator bundle.py matches snapshot 1`] = ` +"# WARNING: This file is autogenerated by @atomic-ehr/codegen. +# GitHub: https://github.com/atomic-ehr/codegen +# Any manual changes made to this file may be overwritten. + +from __future__ import annotations +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator +from typing import Any, Generic, List as PyList, Literal +from typing_extensions import Self, TypeVar + +from fhir_types.hl7_fhir_r4_core.base import BackboneElement, Identifier, Signature +from fhir_types.hl7_fhir_r4_core.resource import Resource +from fhir_types.hl7_fhir_r4_core.resource_preprocessor import preprocess_resource_fields + +T = TypeVar('T', bound=Resource, default=Resource) +T1 = TypeVar('T1', bound=Resource, default=Resource) +T2 = TypeVar('T2', bound=Resource, default=Resource) + + +class BundleEntry(BackboneElement, Generic[T1, T2]): + model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") + full_url: str | None = Field(None, alias="fullUrl", serialization_alias="fullUrl") + link: PyList[BundleLink] | None = Field(None, alias="link", serialization_alias="link") + request: BundleEntryRequest | None = Field(None, alias="request", serialization_alias="request") + resource: T1 | None = Field(None, alias="resource", serialization_alias="resource") + response: BundleEntryResponse[T2] | None = Field(None, alias="response", serialization_alias="response") + search: BundleEntrySearch | None = Field(None, alias="search", serialization_alias="search") + + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + +class BundleEntryRequest(BackboneElement): + model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") + if_match: str | None = Field(None, alias="ifMatch", serialization_alias="ifMatch") + if_modified_since: str | None = Field(None, alias="ifModifiedSince", serialization_alias="ifModifiedSince") + if_none_exist: str | None = Field(None, alias="ifNoneExist", serialization_alias="ifNoneExist") + if_none_match: str | None = Field(None, alias="ifNoneMatch", serialization_alias="ifNoneMatch") + method: Literal["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"] = Field(alias="method", serialization_alias="method") + url: str = Field(alias="url", serialization_alias="url") + +class BundleEntryResponse(BackboneElement, Generic[T]): + model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") + etag: str | None = Field(None, alias="etag", serialization_alias="etag") + last_modified: str | None = Field(None, alias="lastModified", serialization_alias="lastModified") + location: str | None = Field(None, alias="location", serialization_alias="location") + outcome: T | None = Field(None, alias="outcome", serialization_alias="outcome") + status: str = Field(alias="status", serialization_alias="status") + + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + +class BundleEntrySearch(BackboneElement): + model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") + mode: Literal["match", "include", "outcome"] | None = Field(None, alias="mode", serialization_alias="mode") + score: float | None = Field(None, alias="score", serialization_alias="score") + +class BundleLink(BackboneElement): + model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") + relation: str = Field(alias="relation", serialization_alias="relation") + url: str = Field(alias="url", serialization_alias="url") + + +class Bundle(Resource, Generic[T1, T2]): + model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") + resource_type: Literal['Bundle'] = Field( + default='Bundle', + alias='resourceType', + serialization_alias='resourceType', + frozen=True, + pattern='Bundle' + ) + entry: PyList[BundleEntry[T1, T2]] | None = Field(None, alias="entry", serialization_alias="entry") + identifier: Identifier | None = Field(None, alias="identifier", serialization_alias="identifier") + link: PyList[BundleLink] | None = Field(None, alias="link", serialization_alias="link") + signature: Signature | None = Field(None, alias="signature", serialization_alias="signature") + timestamp: str | None = Field(None, alias="timestamp", serialization_alias="timestamp") + total: int | None = Field(None, alias="total", serialization_alias="total") + type: Literal["document", "message", "transaction", "transaction-response", "batch", "batch-response", "history", "searchset", "collection"] = Field(alias="type", serialization_alias="type") + + def model_post_init(self, __context: Any) -> None: + self.__pydantic_fields_set__.add("resource_type") + + def to_json(self, indent: int | None = None) -> str: + return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) + + @classmethod + def from_json(cls, json: str) -> Self: + return cls.model_validate_json(json) + + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + +" +`; + +exports[`Python Writer Generator patient.py matches snapshot 1`] = ` "# WARNING: This file is autogenerated by @atomic-ehr/codegen. # GitHub: https://github.com/atomic-ehr/codegen # Any manual changes made to this file may be overwritten. @@ -8,12 +126,12 @@ exports[`Python Writer Generator generates Patient resource in inMemoryOnly mode from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r4_core.base import (\\ Address, Attachment, BackboneElement, CodeableConcept, ContactPoint, HumanName, Identifier, Period, Reference ) from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily class PatientCommunication(BackboneElement): @@ -72,19 +190,8 @@ class Patient(DomainResource): return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> Patient: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) " `; - -exports[`Python Writer Generator static files 1`] = ` -"fhirpy>=2.0.0,<3.0.0 -mypy>=1.9.0,<2.0.0 -pydantic>=2.11.0,<3.0.0 -pytest>=8.3.0,<9.0.0 -pytest-asyncio>=0.24.0,<1.0.0 -requests>=2.32.0,<3.0.0 -types-requests>=2.32.0,<3.0.0 -" -`; diff --git a/test/api/write-generator/multi-package/__snapshots__/cda.test.ts.snap b/test/api/write-generator/multi-package/__snapshots__/cda.test.ts.snap index 4bb07c684..323292bd5 100644 --- a/test/api/write-generator/multi-package/__snapshots__/cda.test.ts.snap +++ b/test/api/write-generator/multi-package/__snapshots__/cda.test.ts.snap @@ -78,13 +78,18 @@ exports[`CDA Python Generation should generate ClinicalDocument type (promoted l # Any manual changes made to this file may be overwritten. from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field, PositiveInt -from typing import Any, List as PyList, Literal +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator +from typing import Any, Generic, List as PyList, Literal +from typing_extensions import Self, TypeVar from fhir_types.hl7_fhir_r5_core.base import Base +from fhir_types.hl7_cda_uv_core.resource_preprocessor import preprocess_resource_fields +T1 = TypeVar('T1', bound=Base, default=Base) +T2 = TypeVar('T2', bound=ANY, default=ANY) -class ClinicalDocument(ANY): + +class ClinicalDocument(ANY, Generic[T1, T2]): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") resource_type: Literal['ClinicalDocument'] = Field( default='ClinicalDocument', @@ -93,29 +98,29 @@ class ClinicalDocument(ANY): frozen=True, pattern='ClinicalDocument' ) - authenticator: PyList[Authenticator] | None = Field(None, alias="authenticator", serialization_alias="authenticator") - author: PyList[Author] = Field(alias="author", serialization_alias="author") + authenticator: PyList[Authenticator[T1]] | None = Field(None, alias="authenticator", serialization_alias="authenticator") + author: PyList[Author[T1]] = Field(alias="author", serialization_alias="author") authorization: PyList[Authorization] | None = Field(None, alias="authorization", serialization_alias="authorization") class_code: str | None = Field(None, alias="classCode", serialization_alias="classCode") code: CE = Field(alias="code", serialization_alias="code") - component: Component = Field(alias="component", serialization_alias="component") - component_of: ComponentOf | None = Field(None, alias="componentOf", serialization_alias="componentOf") + component: Component[T1, T2] = Field(alias="component", serialization_alias="component") + component_of: ComponentOf[T1] | None = Field(None, alias="componentOf", serialization_alias="componentOf") confidentiality_code: CE = Field(alias="confidentialityCode", serialization_alias="confidentialityCode") copy_time: TS | None = Field(None, alias="copyTime", serialization_alias="copyTime") custodian: Custodian = Field(alias="custodian", serialization_alias="custodian") - data_enterer: DataEnterer | None = Field(None, alias="dataEnterer", serialization_alias="dataEnterer") - documentation_of: PyList[DocumentationOf] | None = Field(None, alias="documentationOf", serialization_alias="documentationOf") + data_enterer: DataEnterer[T1] | None = Field(None, alias="dataEnterer", serialization_alias="dataEnterer") + documentation_of: PyList[DocumentationOf[T1]] | None = Field(None, alias="documentationOf", serialization_alias="documentationOf") effective_time: TS = Field(alias="effectiveTime", serialization_alias="effectiveTime") id: II = Field(alias="id", serialization_alias="id") - informant: PyList[Informant] | None = Field(None, alias="informant", serialization_alias="informant") - information_recipient: PyList[InformationRecipient] | None = Field(None, alias="informationRecipient", serialization_alias="informationRecipient") + informant: PyList[Informant[T1]] | None = Field(None, alias="informant", serialization_alias="informant") + information_recipient: PyList[InformationRecipient[T1]] | None = Field(None, alias="informationRecipient", serialization_alias="informationRecipient") in_fulfillment_of: PyList[InFulfillmentOf] | None = Field(None, alias="inFulfillmentOf", serialization_alias="inFulfillmentOf") language_code: CS | None = Field(None, alias="languageCode", serialization_alias="languageCode") - legal_authenticator: LegalAuthenticator | None = Field(None, alias="legalAuthenticator", serialization_alias="legalAuthenticator") + legal_authenticator: LegalAuthenticator[T1] | None = Field(None, alias="legalAuthenticator", serialization_alias="legalAuthenticator") mood_code: Literal["INT", "APT", "ARQ", "PRMS", "PRP", "RQO", "SLOT", "DEF", "EVN", "EVN.CRT", "GOL", "OPT", "PERM", "PERMRQ"] | None = Field(None, alias="moodCode", serialization_alias="moodCode") - participant: PyList[Participant1] | None = Field(None, alias="participant", serialization_alias="participant") + participant: PyList[Participant1[T1]] | None = Field(None, alias="participant", serialization_alias="participant") realm_code: PyList[CS] | None = Field(None, alias="realmCode", serialization_alias="realmCode") - record_target: PyList[RecordTarget] = Field(alias="recordTarget", serialization_alias="recordTarget") + record_target: PyList[RecordTarget[T1]] = Field(alias="recordTarget", serialization_alias="recordTarget") related_document: PyList[RelatedDocument] | None = Field(None, alias="relatedDocument", serialization_alias="relatedDocument") sdtc_category: PyList[CD] | None = Field(None, alias="sdtcCategory", serialization_alias="sdtcCategory") sdtc_status_code: CS | None = Field(None, alias="sdtcStatusCode", serialization_alias="sdtcStatusCode") @@ -132,9 +137,16 @@ class ClinicalDocument(ANY): return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> ClinicalDocument: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_cda_uv_core") + return data + " `; diff --git a/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap b/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap index ed2d9111e..f57aec70c 100644 --- a/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap +++ b/test/api/write-generator/multi-package/__snapshots__/local-package.test.ts.snap @@ -200,10 +200,10 @@ exports[`Local Package Folder - Multi-Package Generation Python Generation shoul from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r4_core.base import Coding, Identifier, Reference from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily class ExampleNotebook(DomainResource): @@ -228,7 +228,7 @@ class ExampleNotebook(DomainResource): return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> ExampleNotebook: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) " @@ -240,15 +240,18 @@ exports[`Local Package Folder - Multi-Package Generation Python Generation shoul # Any manual changes made to this file may be overwritten. from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field, PositiveInt -from typing import Any, List as PyList, Literal +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator +from typing import Any, Generic, List as PyList, Literal +from typing_extensions import Self, TypeVar from fhir_types.hl7_fhir_r4_core.base import Extension, Narrative from fhir_types.hl7_fhir_r4_core.resource import Resource -from fhir_types.hl7_fhir_r4_core.resource_families import ResourceFamily +from fhir_types.hl7_fhir_r4_core.resource_preprocessor import preprocess_resource_fields + +T = TypeVar('T', bound=Resource, default=Resource) -class DomainResource(Resource): +class DomainResource(Resource, Generic[T]): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") resource_type: str = Field( default='DomainResource', @@ -257,7 +260,7 @@ class DomainResource(Resource): frozen=True, pattern='DomainResource' ) - contained: PyList[ResourceFamily] | None = Field(None, alias="contained", serialization_alias="contained") + contained: PyList[T] | None = Field(None, alias="contained", serialization_alias="contained") extension: PyList[Extension] | None = Field(None, alias="extension", serialization_alias="extension") modifier_extension: PyList[Extension] | None = Field(None, alias="modifierExtension", serialization_alias="modifierExtension") text: Narrative | None = Field(None, alias="text", serialization_alias="text") @@ -269,9 +272,16 @@ class DomainResource(Resource): return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> DomainResource: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r4_core") + return data + " `; @@ -283,12 +293,12 @@ exports[`Local Package Folder - Multi-Package Generation Python Generation shoul from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r4_core.base import (\\ Address, Attachment, BackboneElement, CodeableConcept, ContactPoint, HumanName, Identifier, Period, Reference ) from fhir_types.hl7_fhir_r4_core.domain_resource import DomainResource -from fhir_types.hl7_fhir_r4_core.resource_families import DomainResourceFamily class PatientCommunication(BackboneElement): @@ -347,7 +357,7 @@ class Patient(DomainResource): return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> Patient: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) " diff --git a/test/api/write-generator/multi-package/__snapshots__/sql-on-fhir.test.ts.snap b/test/api/write-generator/multi-package/__snapshots__/sql-on-fhir.test.ts.snap index 12427c37a..c39aef07e 100644 --- a/test/api/write-generator/multi-package/__snapshots__/sql-on-fhir.test.ts.snap +++ b/test/api/write-generator/multi-package/__snapshots__/sql-on-fhir.test.ts.snap @@ -87,10 +87,10 @@ exports[`SQL-on-FHIR Python Generation should generate ViewDefinition type (prom from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, PositiveInt from typing import Any, List as PyList, Literal +from typing_extensions import Self from fhir_types.hl7_fhir_r5_core.base import BackboneElement from fhir_types.hl7_fhir_r5_core.canonical_resource import CanonicalResource -from fhir_types.hl7_fhir_r5_core.resource_families import CanonicalResourceFamily class ViewDefinitionConstant(BackboneElement): @@ -169,7 +169,7 @@ class ViewDefinition(CanonicalResource): return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> ViewDefinition: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) " @@ -181,15 +181,18 @@ exports[`SQL-on-FHIR Python Generation should generate domain_resource for R5 1` # Any manual changes made to this file may be overwritten. from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field, PositiveInt -from typing import Any, List as PyList, Literal +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator +from typing import Any, Generic, List as PyList, Literal +from typing_extensions import Self, TypeVar from fhir_types.hl7_fhir_r5_core.base import Extension, Narrative from fhir_types.hl7_fhir_r5_core.resource import Resource -from fhir_types.hl7_fhir_r5_core.resource_families import ResourceFamily +from fhir_types.hl7_fhir_r5_core.resource_preprocessor import preprocess_resource_fields + +T = TypeVar('T', bound=Resource, default=Resource) -class DomainResource(Resource): +class DomainResource(Resource, Generic[T]): model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="forbid") resource_type: str = Field( default='DomainResource', @@ -198,7 +201,7 @@ class DomainResource(Resource): frozen=True, pattern='DomainResource' ) - contained: PyList[ResourceFamily] | None = Field(None, alias="contained", serialization_alias="contained") + contained: PyList[T] | None = Field(None, alias="contained", serialization_alias="contained") extension: PyList[Extension] | None = Field(None, alias="extension", serialization_alias="extension") modifier_extension: PyList[Extension] | None = Field(None, alias="modifierExtension", serialization_alias="modifierExtension") text: Narrative | None = Field(None, alias="text", serialization_alias="text") @@ -210,9 +213,16 @@ class DomainResource(Resource): return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent) @classmethod - def from_json(cls, json: str) -> DomainResource: + def from_json(cls, json: str) -> Self: return cls.model_validate_json(json) + @model_validator(mode='before') + @classmethod + def _preprocess_resources(cls, data: Any) -> Any: + if isinstance(data, dict): + return preprocess_resource_fields(data, "fhir_types.hl7_fhir_r5_core") + return data + " `; diff --git a/test/api/write-generator/python.test.ts b/test/api/write-generator/python.test.ts index 26ac7f2b6..72b5e00ca 100644 --- a/test/api/write-generator/python.test.ts +++ b/test/api/write-generator/python.test.ts @@ -11,32 +11,69 @@ describe("Python Writer Generator", async () => { expect(result.success).toBeTrue(); const files = result.filesGenerated.python!; expect(Object.keys(files).length).toEqual(153); - it("generates Patient resource in inMemoryOnly mode with snapshot", async () => { - expect(files["generated/hl7_fhir_r4_core/patient.py"]).toMatchSnapshot(); - }); + it("static files", async () => { expect(files["generated/requirements.txt"]).toMatchSnapshot(); }); - it("generates Coding with Generic[T] parameter", async () => { + + describe("base.py", () => { const basePy = files["generated/hl7_fhir_r4_core/base.py"]; - expect(basePy).toContain("class Coding(Element, Generic[T]):"); - expect(basePy).toContain("code: T | None"); + it("generates Coding with Generic[T] parameter", () => { + expect(basePy).toContain("class Coding(Element, Generic[T]):"); + expect(basePy).toContain("code: T | None"); + }); + it("generates CodeableConcept with Generic[T] parameter", () => { + expect(basePy).toContain("class CodeableConcept(Element, Generic[T]):"); + expect(basePy).toContain("coding: PyList[Coding[T]] | None"); + }); + it("generates TypeVar import and declaration", () => { + expect(basePy).toContain("from typing import Any, Generic, List as PyList, Literal"); + expect(basePy).toContain("from typing_extensions import TypeVar"); + expect(basePy).toContain("T = TypeVar('T', bound=str, default=str)"); + }); }); - it("generates CodeableConcept with Generic[T] parameter", async () => { - const basePy = files["generated/hl7_fhir_r4_core/base.py"]; - expect(basePy).toContain("class CodeableConcept(Element, Generic[T]):"); - expect(basePy).toContain("coding: PyList[Coding[T]] | None"); + + describe("bundle.py", () => { + const bundlePy = files["generated/hl7_fhir_r4_core/bundle.py"]; + it("generates BundleEntryResponse with generic type-family parameter", () => { + expect(bundlePy).toContain("class BundleEntryResponse(BackboneElement, Generic[T]):"); + expect(bundlePy).toContain("outcome: T | None"); + }); + it("generates BundleEntry with generic type-family parameters", () => { + expect(bundlePy).toContain("class BundleEntry(BackboneElement, Generic[T1, T2]):"); + expect(bundlePy).toContain("resource: T1 | None"); + expect(bundlePy).toContain("response: BundleEntryResponse[T2] | None"); + }); + it("generates Bundle with inherited generic params from BundleEntry", () => { + expect(bundlePy).toContain("class Bundle(Resource, Generic[T1, T2]):"); + expect(bundlePy).toContain("entry: PyList[BundleEntry[T1, T2]] | None"); + }); + it("declares resource-constrained TypeVars", () => { + expect(bundlePy).toContain("T1 = TypeVar('T1', bound=Resource, default=Resource)"); + expect(bundlePy).toContain("T2 = TypeVar('T2', bound=Resource, default=Resource)"); + }); + it("matches snapshot", () => { + expect(bundlePy).toMatchSnapshot(); + }); }); - it("generates CodeableConcept fields with enum bindings", async () => { - const patientPy = files["generated/hl7_fhir_r4_core/patient.py"]; - expect(patientPy).toContain( - 'marital_status: CodeableConcept[Literal["A", "D", "I", "L", "M", "P", "S", "T", "U", "W", "UNK"] | str] | None', - ); + + describe("domain_resource.py", () => { + const domainResourcePy = files["generated/hl7_fhir_r4_core/domain_resource.py"]; + it("generates DomainResource with generic type-family parameter", () => { + expect(domainResourcePy).toContain("class DomainResource(Resource, Generic[T]):"); + expect(domainResourcePy).toContain("contained: PyList[T] | None"); + }); }); - it("generates base.py with TypeVar import and declaration", async () => { - const basePy = files["generated/hl7_fhir_r4_core/base.py"]; - expect(basePy).toContain("from typing import Any, Generic, List as PyList, Literal"); - expect(basePy).toContain("from typing_extensions import TypeVar"); - expect(basePy).toContain("T = TypeVar('T', bound=str, default=str)"); + + describe("patient.py", () => { + const patientPy = files["generated/hl7_fhir_r4_core/patient.py"]; + it("generates CodeableConcept fields with enum bindings", () => { + expect(patientPy).toContain( + 'marital_status: CodeableConcept[Literal["A", "D", "I", "L", "M", "P", "S", "T", "U", "W", "UNK"] | str] | None', + ); + }); + it("matches snapshot", () => { + expect(patientPy).toMatchSnapshot(); + }); }); }); diff --git a/test/unit/typeschema/__snapshots__/snapshot.test.ts.snap b/test/unit/typeschema/__snapshots__/snapshot.test.ts.snap index 66fd94366..c93d0803a 100644 --- a/test/unit/typeschema/__snapshots__/snapshot.test.ts.snap +++ b/test/unit/typeschema/__snapshots__/snapshot.test.ts.snap @@ -1,4 +1,4 @@ -// Bun Snapshot v1, https://goo.gl/fbAQLP +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots exports[`ValueSet to Type Schema (snapshot) administrative-gender 1`] = ` "{ @@ -124,15 +124,15 @@ exports[`ValueSet to Type Schema (snapshot) marital-status 1`] = ` }" `; -exports[`FHIR Schema to Type Schema (snapshot) Real world examples coding primitive type 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) with cardinality 1`] = ` "[ { "identifier": { "kind": "complex-type", - "package": "realworld.test", - "version": "1.0.0", - "name": "Coding", - "url": "http://hl7.org/fhir/StructureDefinition/Coding" + "package": "mypackage", + "version": "0.0.0", + "name": "Cardinality", + "url": "http://hl7.org/fhir/StructureDefinition/Cardinality" }, "base": { "kind": "complex-type", @@ -142,19 +142,21 @@ exports[`FHIR Schema to Type Schema (snapshot) Real world examples coding primit "url": "http://hl7.org/fhir/StructureDefinition/Element" }, "fields": { - "system": { + "zero_or_one": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "uri", - "url": "http://hl7.org/fhir/StructureDefinition/uri" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" }, "required": false, "excluded": false, - "array": false + "array": true, + "min": 0, + "max": 1 }, - "version": { + "one": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", @@ -164,21 +166,25 @@ exports[`FHIR Schema to Type Schema (snapshot) Real world examples coding primit }, "required": false, "excluded": false, - "array": false + "array": true, + "min": 1, + "max": 1 }, - "code": { + "many": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "code", - "url": "http://hl7.org/fhir/StructureDefinition/code" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" }, "required": false, "excluded": false, - "array": false + "array": true, + "min": 42, + "max": 42 }, - "display": { + "one_or_many": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", @@ -188,37 +194,24 @@ exports[`FHIR Schema to Type Schema (snapshot) Real world examples coding primit }, "required": false, "excluded": false, - "array": false + "array": true, + "min": 1 }, - "userSelected": { + "zero_or_many": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "boolean", - "url": "http://hl7.org/fhir/StructureDefinition/boolean" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" }, "required": false, "excluded": false, - "array": false + "array": true } }, "description": "Base StructureDefinition for Coding Type: A reference to a code defined by a terminology system.", "dependencies": [ - { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "boolean", - "url": "http://hl7.org/fhir/StructureDefinition/boolean" - }, - { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "code", - "url": "http://hl7.org/fhir/StructureDefinition/code" - }, { "kind": "complex-type", "package": "hl7.fhir.r4.core", @@ -232,59 +225,21 @@ exports[`FHIR Schema to Type Schema (snapshot) Real world examples coding primit "version": "4.0.1", "name": "string", "url": "http://hl7.org/fhir/StructureDefinition/string" - }, - { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "uri", - "url": "http://hl7.org/fhir/StructureDefinition/uri" - } - ] - } -]" -`; - -exports[`FHIR Schema to Type Schema (snapshot) Real world examples string primitive type 1`] = ` -"[ - { - "identifier": { - "kind": "primitive-type", - "package": "realworld.test", - "version": "1.0.0", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - }, - "description": "Base StructureDefinition for string Type: A sequence of Unicode characters", - "base": { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "Element", - "url": "http://hl7.org/fhir/StructureDefinition/Element" - }, - "dependencies": [ - { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "Element", - "url": "http://hl7.org/fhir/StructureDefinition/Element" } ] } ]" `; -exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotificationTemplate 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) with resource with string 1`] = ` "[ { "identifier": { "kind": "resource", "package": "mypackage", "version": "0.0.0", - "name": "TutorNotificationTemplate", - "url": "http://example.com/aidbox-sms-tutor/TutorNotificationTemplate" + "name": "WithPrimitiveString", + "url": "http://example.io/fhir/WithPrimitiveString" }, "base": { "kind": "resource", @@ -294,7 +249,7 @@ exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, "fields": { - "template": { + "someString": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", @@ -307,6 +262,7 @@ exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification "array": false } }, + "description": "description", "dependencies": [ { "kind": "resource", @@ -327,15 +283,15 @@ exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification ]" `; -exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) with resource with code 1`] = ` "[ { "identifier": { "kind": "resource", "package": "mypackage", "version": "0.0.0", - "name": "TutorNotification", - "url": "http://example.com/aidbox-sms-tutor/TutorNotification" + "name": "WithCode", + "url": "http://example.io/fhir/WithCode" }, "base": { "kind": "resource", @@ -345,161 +301,160 @@ exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, "fields": { - "type": { + "gender": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" + "name": "code", + "url": "http://hl7.org/fhir/StructureDefinition/code" }, - "required": true, + "required": false, "excluded": false, "array": false, "binding": { "kind": "binding", - "package": "mypackage", - "version": "0.0.0", - "name": "TutorNotification.type_binding", - "url": "http://example.com/aidbox-sms-tutor/TutorNotification#type_binding" + "package": "shared", + "version": "1.0.0", + "name": "AdministrativeGender", + "url": "urn:fhir:binding:AdministrativeGender" }, "enum": { "isOpen": false, "values": [ - "phone", - "fax", - "email", - "pager", - "url", - "sms", - "other" + "male", + "female", + "other", + "unknown" ] } + } + }, + "description": "description", + "dependencies": [ + { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "code", + "url": "http://hl7.org/fhir/StructureDefinition/code" }, - "status": { + { + "kind": "resource", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "DomainResource", + "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" + }, + { + "kind": "binding", + "package": "shared", + "version": "1.0.0", + "name": "AdministrativeGender", + "url": "urn:fhir:binding:AdministrativeGender" + } + ] + }, + { + "identifier": { + "kind": "binding", + "package": "shared", + "version": "1.0.0", + "name": "AdministrativeGender", + "url": "urn:fhir:binding:AdministrativeGender" + }, + "valueset": { + "kind": "value-set", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "AdministrativeGender", + "url": "http://hl7.org/fhir/ValueSet/administrative-gender" + }, + "strength": "required", + "enum": { + "isOpen": false, + "values": [ + "male", + "female", + "other", + "unknown" + ] + }, + "dependencies": [ + { + "kind": "value-set", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "AdministrativeGender", + "url": "http://hl7.org/fhir/ValueSet/administrative-gender" + } + ] + } +]" +`; + +exports[`FHIR Schema to Type Schema (snapshot) with resource with codable concept 1`] = ` +"[ + { + "identifier": { + "kind": "resource", + "package": "mypackage", + "version": "0.0.0", + "name": "WithCodableConcept", + "url": "http://example.io/fhir/WithCodableConcept" + }, + "base": { + "kind": "resource", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "DomainResource", + "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" + }, + "fields": { + "maritalStatus": { "type": { - "kind": "primitive-type", + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" + "name": "CodeableConcept", + "url": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" }, - "required": true, + "required": false, "excluded": false, "array": false, "binding": { "kind": "binding", - "package": "mypackage", - "version": "0.0.0", - "name": "TutorNotification.status_binding", - "url": "http://example.com/aidbox-sms-tutor/TutorNotification#status_binding" + "package": "shared", + "version": "1.0.0", + "name": "MaritalStatus", + "url": "urn:fhir:binding:MaritalStatus" }, "enum": { - "isOpen": false, + "isOpen": true, "values": [ - "draft", - "requested", - "received", - "accepted", - "rejected", - "ready", - "cancelled", - "in-progress", - "on-hold", - "failed", - "completed", - "entered-in-error" + "A", + "D", + "I", + "L", + "M", + "P", + "S", + "T", + "U", + "W", + "UNK" ] } - }, - "template": { - "type": { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "Reference", - "url": "http://hl7.org/fhir/StructureDefinition/Reference" - }, - "required": true, - "excluded": false, - "reference": [ - { - "kind": "resource", - "package": "mypackage", - "version": "0.0.0", - "name": "TutorNotificationTemplate", - "url": "http://example.com/aidbox-sms-tutor/TutorNotificationTemplate" - } - ], - "array": false - }, - "message": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - }, - "required": false, - "excluded": false, - "array": false - }, - "sendAfter": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "dateTime", - "url": "http://hl7.org/fhir/StructureDefinition/dateTime" - }, - "required": true, - "excluded": false, - "array": false - }, - "subject": { - "type": { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "Reference", - "url": "http://hl7.org/fhir/StructureDefinition/Reference" - }, - "required": true, - "excluded": false, - "reference": [ - { - "kind": "resource", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "Patient", - "url": "http://hl7.org/fhir/StructureDefinition/Patient" - } - ], - "array": false } }, + "description": "description", "dependencies": [ { - "kind": "binding", - "package": "mypackage", - "version": "0.0.0", - "name": "TutorNotification.status_binding", - "url": "http://example.com/aidbox-sms-tutor/TutorNotification#status_binding" - }, - { - "kind": "binding", - "package": "mypackage", - "version": "0.0.0", - "name": "TutorNotification.type_binding", - "url": "http://example.com/aidbox-sms-tutor/TutorNotification#type_binding" - }, - { - "kind": "primitive-type", + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "dateTime", - "url": "http://hl7.org/fhir/StructureDefinition/dateTime" + "name": "CodeableConcept", + "url": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" }, { "kind": "resource", @@ -509,90 +464,44 @@ exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "Reference", - "url": "http://hl7.org/fhir/StructureDefinition/Reference" - }, - { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - } - ] - }, - { - "identifier": { - "kind": "binding", - "package": "mypackage", - "version": "0.0.0", - "name": "TutorNotification.status_binding", - "url": "http://example.com/aidbox-sms-tutor/TutorNotification#status_binding" - }, - "valueset": { - "kind": "value-set", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "TaskStatus", - "url": "http://hl7.org/fhir/ValueSet/task-status" - }, - "strength": "required", - "enum": { - "isOpen": false, - "values": [ - "draft", - "requested", - "received", - "accepted", - "rejected", - "ready", - "cancelled", - "in-progress", - "on-hold", - "failed", - "completed", - "entered-in-error" - ] - }, - "dependencies": [ - { - "kind": "value-set", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "TaskStatus", - "url": "http://hl7.org/fhir/ValueSet/task-status" + "kind": "binding", + "package": "shared", + "version": "1.0.0", + "name": "MaritalStatus", + "url": "urn:fhir:binding:MaritalStatus" } ] }, { "identifier": { "kind": "binding", - "package": "mypackage", - "version": "0.0.0", - "name": "TutorNotification.type_binding", - "url": "http://example.com/aidbox-sms-tutor/TutorNotification#type_binding" + "package": "shared", + "version": "1.0.0", + "name": "MaritalStatus", + "url": "urn:fhir:binding:MaritalStatus" }, "valueset": { "kind": "value-set", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "ContactPointSystem", - "url": "http://hl7.org/fhir/ValueSet/contact-point-system" + "name": "MaritalStatus", + "url": "http://hl7.org/fhir/ValueSet/marital-status" }, - "strength": "required", + "strength": "extensible", "enum": { - "isOpen": false, + "isOpen": true, "values": [ - "phone", - "fax", - "email", - "pager", - "url", - "sms", - "other" + "A", + "D", + "I", + "L", + "M", + "P", + "S", + "T", + "U", + "W", + "UNK" ] }, "dependencies": [ @@ -600,130 +509,105 @@ exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification "kind": "value-set", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "ContactPointSystem", - "url": "http://hl7.org/fhir/ValueSet/contact-point-system" + "name": "MaritalStatus", + "url": "http://hl7.org/fhir/ValueSet/marital-status" } ] } ]" `; -exports[`FHIR Schema to Type Schema (snapshot) with cardinality 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) with resource with choice 1`] = ` "[ { "identifier": { - "kind": "complex-type", + "kind": "resource", "package": "mypackage", "version": "0.0.0", - "name": "Cardinality", - "url": "http://hl7.org/fhir/StructureDefinition/Cardinality" + "name": "WithChoice", + "url": "http://example.io/fhir/WithChoice" }, "base": { - "kind": "complex-type", + "kind": "resource", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "Element", - "url": "http://hl7.org/fhir/StructureDefinition/Element" + "name": "DomainResource", + "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, "fields": { - "zero_or_one": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - }, - "required": false, - "excluded": false, - "array": true, - "min": 0, - "max": 1 - }, - "one": { + "deceasedDateTime": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" + "name": "dateTime", + "url": "http://hl7.org/fhir/StructureDefinition/dateTime" }, "required": false, "excluded": false, - "array": true, - "min": 1, - "max": 1 + "array": false, + "choiceOf": "deceased" }, - "many": { + "deceasedBoolean": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" + "name": "boolean", + "url": "http://hl7.org/fhir/StructureDefinition/boolean" }, "required": false, "excluded": false, - "array": true, - "min": 42, - "max": 42 + "array": false, + "choiceOf": "deceased" }, - "one_or_many": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - }, + "deceased": { "required": false, "excluded": false, - "array": true, - "min": 1 - }, - "zero_or_many": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - }, - "required": false, - "excluded": false, - "array": true + "array": false, + "choices": [ + "deceasedBoolean", + "deceasedDateTime" + ] } }, - "description": "Base StructureDefinition for Coding Type: A reference to a code defined by a terminology system.", + "description": "description", "dependencies": [ { - "kind": "complex-type", + "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "Element", - "url": "http://hl7.org/fhir/StructureDefinition/Element" + "name": "boolean", + "url": "http://hl7.org/fhir/StructureDefinition/boolean" }, { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" + "name": "dateTime", + "url": "http://hl7.org/fhir/StructureDefinition/dateTime" + }, + { + "kind": "resource", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "DomainResource", + "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" } ] } ]" `; -exports[`FHIR Schema to Type Schema (snapshot) with resource with string 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) with resource with nested type 1`] = ` "[ { "identifier": { "kind": "resource", "package": "mypackage", "version": "0.0.0", - "name": "WithPrimitiveString", - "url": "http://example.io/fhir/WithPrimitiveString" + "name": "WithNestedType", + "url": "http://example.io/fhir/WithNestedType" }, "base": { "kind": "resource", @@ -733,21 +617,60 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with string 1`] = ` "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, "fields": { - "someString": { + "link": { "type": { - "kind": "primitive-type", + "kind": "nested", + "package": "mypackage", + "version": "0.0.0", + "name": "link", + "url": "http://example.io/fhir/WithNestedType#link" + }, + "array": true, + "required": false, + "excluded": false + } + }, + "nested": [ + { + "identifier": { + "kind": "nested", + "package": "mypackage", + "version": "0.0.0", + "name": "link", + "url": "http://example.io/fhir/WithNestedType#link" + }, + "base": { + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" + "name": "BackboneElement", + "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" }, - "required": true, - "excluded": false, - "array": false + "fields": { + "someString": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" + }, + "required": true, + "excluded": false, + "array": false + } + } } - }, - "description": "description", + ], + "description": "some description", "dependencies": [ + { + "kind": "complex-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "BackboneElement", + "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" + }, { "kind": "resource", "package": "hl7.fhir.r4.core", @@ -767,15 +690,15 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with string 1`] = ` ]" `; -exports[`FHIR Schema to Type Schema (snapshot) with resource with code 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) with resource with nested type 2 1`] = ` "[ { "identifier": { "kind": "resource", "package": "mypackage", "version": "0.0.0", - "name": "WithCode", - "url": "http://example.io/fhir/WithCode" + "name": "Root", + "url": "http://example.io/fhir/Root" }, "base": { "kind": "resource", @@ -785,43 +708,89 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with code 1`] = ` "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, "fields": { - "gender": { + "field": { "type": { - "kind": "primitive-type", + "kind": "nested", + "package": "mypackage", + "version": "0.0.0", + "name": "field", + "url": "http://example.io/fhir/Root#field" + }, + "array": true, + "required": false, + "excluded": false + } + }, + "nested": [ + { + "identifier": { + "kind": "nested", + "package": "mypackage", + "version": "0.0.0", + "name": "field", + "url": "http://example.io/fhir/Root#field" + }, + "base": { + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "code", - "url": "http://hl7.org/fhir/StructureDefinition/code" + "name": "BackboneElement", + "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" }, - "required": false, - "excluded": false, - "array": false, - "binding": { - "kind": "binding", - "package": "shared", - "version": "1.0.0", - "name": "AdministrativeGender", - "url": "urn:fhir:binding:AdministrativeGender" + "fields": { + "subfield": { + "type": { + "kind": "nested", + "package": "mypackage", + "version": "0.0.0", + "name": "field.subfield", + "url": "http://example.io/fhir/Root#field.subfield" + }, + "array": true, + "required": false, + "excluded": false + } + } + }, + { + "identifier": { + "kind": "nested", + "package": "mypackage", + "version": "0.0.0", + "name": "field.subfield", + "url": "http://example.io/fhir/Root#field.subfield" }, - "enum": { - "isOpen": false, - "values": [ - "male", - "female", - "other", - "unknown" - ] + "base": { + "kind": "complex-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "BackboneElement", + "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" + }, + "fields": { + "subsubfield": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" + }, + "required": false, + "excluded": false, + "array": false + } } } - }, - "description": "description", + ], + "description": "some description", "dependencies": [ { - "kind": "primitive-type", + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "code", - "url": "http://hl7.org/fhir/StructureDefinition/code" + "name": "BackboneElement", + "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" }, { "kind": "resource", @@ -831,185 +800,178 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with code 1`] = ` "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, { - "kind": "binding", - "package": "shared", - "version": "1.0.0", - "name": "AdministrativeGender", - "url": "urn:fhir:binding:AdministrativeGender" - } - ] - }, - { - "identifier": { - "kind": "binding", - "package": "shared", - "version": "1.0.0", - "name": "AdministrativeGender", - "url": "urn:fhir:binding:AdministrativeGender" - }, - "valueset": { - "kind": "value-set", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "AdministrativeGender", - "url": "http://hl7.org/fhir/ValueSet/administrative-gender" - }, - "strength": "required", - "enum": { - "isOpen": false, - "values": [ - "male", - "female", - "other", - "unknown" - ] - }, - "dependencies": [ - { - "kind": "value-set", + "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "AdministrativeGender", - "url": "http://hl7.org/fhir/ValueSet/administrative-gender" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" } ] } ]" `; -exports[`FHIR Schema to Type Schema (snapshot) with resource with codable concept 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) Real world examples coding primitive type 1`] = ` "[ { "identifier": { - "kind": "resource", - "package": "mypackage", - "version": "0.0.0", - "name": "WithCodableConcept", - "url": "http://example.io/fhir/WithCodableConcept" - }, - "base": { - "kind": "resource", + "kind": "complex-type", + "package": "realworld.test", + "version": "1.0.0", + "name": "Coding", + "url": "http://hl7.org/fhir/StructureDefinition/Coding" + }, + "base": { + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "DomainResource", - "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" + "name": "Element", + "url": "http://hl7.org/fhir/StructureDefinition/Element" }, "fields": { - "maritalStatus": { + "system": { "type": { - "kind": "complex-type", + "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "CodeableConcept", - "url": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" + "name": "uri", + "url": "http://hl7.org/fhir/StructureDefinition/uri" }, "required": false, "excluded": false, - "array": false, - "binding": { - "kind": "binding", - "package": "shared", - "version": "1.0.0", - "name": "MaritalStatus", - "url": "urn:fhir:binding:MaritalStatus" + "array": false + }, + "version": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" }, - "enum": { - "isOpen": true, - "values": [ - "A", - "D", - "I", - "L", - "M", - "P", - "S", - "T", - "U", - "W", - "UNK" - ] - } + "required": false, + "excluded": false, + "array": false + }, + "code": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "code", + "url": "http://hl7.org/fhir/StructureDefinition/code" + }, + "required": false, + "excluded": false, + "array": false + }, + "display": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" + }, + "required": false, + "excluded": false, + "array": false + }, + "userSelected": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "boolean", + "url": "http://hl7.org/fhir/StructureDefinition/boolean" + }, + "required": false, + "excluded": false, + "array": false } }, - "description": "description", + "description": "Base StructureDefinition for Coding Type: A reference to a code defined by a terminology system.", "dependencies": [ + { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "boolean", + "url": "http://hl7.org/fhir/StructureDefinition/boolean" + }, + { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "code", + "url": "http://hl7.org/fhir/StructureDefinition/code" + }, { "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "CodeableConcept", - "url": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" + "name": "Element", + "url": "http://hl7.org/fhir/StructureDefinition/Element" }, { - "kind": "resource", + "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "DomainResource", - "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" }, { - "kind": "binding", - "package": "shared", - "version": "1.0.0", - "name": "MaritalStatus", - "url": "urn:fhir:binding:MaritalStatus" + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "uri", + "url": "http://hl7.org/fhir/StructureDefinition/uri" } ] - }, + } +]" +`; + +exports[`FHIR Schema to Type Schema (snapshot) Real world examples string primitive type 1`] = ` +"[ { "identifier": { - "kind": "binding", - "package": "shared", + "kind": "primitive-type", + "package": "realworld.test", "version": "1.0.0", - "name": "MaritalStatus", - "url": "urn:fhir:binding:MaritalStatus" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" }, - "valueset": { - "kind": "value-set", + "description": "Base StructureDefinition for string Type: A sequence of Unicode characters", + "base": { + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "MaritalStatus", - "url": "http://hl7.org/fhir/ValueSet/marital-status" - }, - "strength": "extensible", - "enum": { - "isOpen": true, - "values": [ - "A", - "D", - "I", - "L", - "M", - "P", - "S", - "T", - "U", - "W", - "UNK" - ] + "name": "Element", + "url": "http://hl7.org/fhir/StructureDefinition/Element" }, "dependencies": [ { - "kind": "value-set", + "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "MaritalStatus", - "url": "http://hl7.org/fhir/ValueSet/marital-status" + "name": "Element", + "url": "http://hl7.org/fhir/StructureDefinition/Element" } ] } ]" `; -exports[`FHIR Schema to Type Schema (snapshot) with resource with choice 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotificationTemplate 1`] = ` "[ { "identifier": { "kind": "resource", "package": "mypackage", "version": "0.0.0", - "name": "WithChoice", - "url": "http://example.io/fhir/WithChoice" + "name": "TutorNotificationTemplate", + "url": "http://example.com/aidbox-sms-tutor/TutorNotificationTemplate" }, "base": { "kind": "resource", @@ -1019,79 +981,48 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with choice 1`] = ` "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, "fields": { - "deceasedDateTime": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "dateTime", - "url": "http://hl7.org/fhir/StructureDefinition/dateTime" - }, - "required": false, - "excluded": false, - "array": false, - "choiceOf": "deceased" - }, - "deceasedBoolean": { + "template": { "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "boolean", - "url": "http://hl7.org/fhir/StructureDefinition/boolean" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" }, - "required": false, - "excluded": false, - "array": false, - "choiceOf": "deceased" - }, - "deceased": { - "required": false, + "required": true, "excluded": false, - "array": false, - "choices": [ - "deceasedBoolean", - "deceasedDateTime" - ] + "array": false } }, - "description": "description", "dependencies": [ { - "kind": "primitive-type", + "kind": "resource", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "boolean", - "url": "http://hl7.org/fhir/StructureDefinition/boolean" + "name": "DomainResource", + "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "dateTime", - "url": "http://hl7.org/fhir/StructureDefinition/dateTime" - }, - { - "kind": "resource", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "DomainResource", - "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" } ] } ]" `; -exports[`FHIR Schema to Type Schema (snapshot) with resource with nested type 1`] = ` +exports[`FHIR Schema to Type Schema (snapshot) Custom resource TutorNotification 1`] = ` "[ { "identifier": { "kind": "resource", "package": "mypackage", "version": "0.0.0", - "name": "WithNestedType", - "url": "http://example.io/fhir/WithNestedType" + "name": "TutorNotification", + "url": "http://example.com/aidbox-sms-tutor/TutorNotification" }, "base": { "kind": "resource", @@ -1101,59 +1032,161 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with nested type 1` "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, "fields": { - "link": { + "type": { "type": { - "kind": "nested", + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" + }, + "required": true, + "excluded": false, + "array": false, + "binding": { + "kind": "binding", "package": "mypackage", "version": "0.0.0", - "name": "link", - "url": "http://example.io/fhir/WithNestedType#link" + "name": "TutorNotification.type_binding", + "url": "http://example.com/aidbox-sms-tutor/TutorNotification#type_binding" }, - "array": true, - "required": false, - "excluded": false - } - }, - "nested": [ - { - "identifier": { - "kind": "nested", + "enum": { + "isOpen": false, + "values": [ + "phone", + "fax", + "email", + "pager", + "url", + "sms", + "other" + ] + } + }, + "status": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" + }, + "required": true, + "excluded": false, + "array": false, + "binding": { + "kind": "binding", "package": "mypackage", "version": "0.0.0", - "name": "link", - "url": "http://example.io/fhir/WithNestedType#link" + "name": "TutorNotification.status_binding", + "url": "http://example.com/aidbox-sms-tutor/TutorNotification#status_binding" }, - "base": { + "enum": { + "isOpen": false, + "values": [ + "draft", + "requested", + "received", + "accepted", + "rejected", + "ready", + "cancelled", + "in-progress", + "on-hold", + "failed", + "completed", + "entered-in-error" + ] + } + }, + "template": { + "type": { "kind": "complex-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "BackboneElement", - "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" + "name": "Reference", + "url": "http://hl7.org/fhir/StructureDefinition/Reference" }, - "fields": { - "someString": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - }, - "required": true, - "excluded": false, - "array": false + "required": true, + "excluded": false, + "reference": [ + { + "kind": "resource", + "package": "mypackage", + "version": "0.0.0", + "name": "TutorNotificationTemplate", + "url": "http://example.com/aidbox-sms-tutor/TutorNotificationTemplate" } - } + ], + "array": false + }, + "message": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "string", + "url": "http://hl7.org/fhir/StructureDefinition/string" + }, + "required": false, + "excluded": false, + "array": false + }, + "sendAfter": { + "type": { + "kind": "primitive-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "dateTime", + "url": "http://hl7.org/fhir/StructureDefinition/dateTime" + }, + "required": true, + "excluded": false, + "array": false + }, + "subject": { + "type": { + "kind": "complex-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "Reference", + "url": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + "required": true, + "excluded": false, + "reference": [ + { + "kind": "resource", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "Patient", + "url": "http://hl7.org/fhir/StructureDefinition/Patient" + } + ], + "array": false } - ], - "description": "some description", + }, "dependencies": [ { - "kind": "complex-type", + "kind": "binding", + "package": "mypackage", + "version": "0.0.0", + "name": "TutorNotification.status_binding", + "url": "http://example.com/aidbox-sms-tutor/TutorNotification#status_binding" + }, + { + "kind": "binding", + "package": "mypackage", + "version": "0.0.0", + "name": "TutorNotification.type_binding", + "url": "http://example.com/aidbox-sms-tutor/TutorNotification#type_binding" + }, + { + "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "BackboneElement", - "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" + "name": "dateTime", + "url": "http://hl7.org/fhir/StructureDefinition/dateTime" }, { "kind": "resource", @@ -1162,6 +1195,13 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with nested type 1` "name": "DomainResource", "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }, + { + "kind": "complex-type", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "Reference", + "url": "http://hl7.org/fhir/StructureDefinition/Reference" + }, { "kind": "primitive-type", "package": "hl7.fhir.r4.core", @@ -1170,125 +1210,85 @@ exports[`FHIR Schema to Type Schema (snapshot) with resource with nested type 1` "url": "http://hl7.org/fhir/StructureDefinition/string" } ] - } -]" -`; - -exports[`FHIR Schema to Type Schema (snapshot) with resource with nested type 2 1`] = ` -"[ + }, { "identifier": { - "kind": "resource", + "kind": "binding", "package": "mypackage", "version": "0.0.0", - "name": "Root", - "url": "http://example.io/fhir/Root" + "name": "TutorNotification.status_binding", + "url": "http://example.com/aidbox-sms-tutor/TutorNotification#status_binding" }, - "base": { - "kind": "resource", + "valueset": { + "kind": "value-set", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "DomainResource", - "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" + "name": "TaskStatus", + "url": "http://hl7.org/fhir/ValueSet/task-status" }, - "fields": { - "field": { - "type": { - "kind": "nested", - "package": "mypackage", - "version": "0.0.0", - "name": "field", - "url": "http://example.io/fhir/Root#field" - }, - "array": true, - "required": false, - "excluded": false - } + "strength": "required", + "enum": { + "isOpen": false, + "values": [ + "draft", + "requested", + "received", + "accepted", + "rejected", + "ready", + "cancelled", + "in-progress", + "on-hold", + "failed", + "completed", + "entered-in-error" + ] }, - "nested": [ - { - "identifier": { - "kind": "nested", - "package": "mypackage", - "version": "0.0.0", - "name": "field", - "url": "http://example.io/fhir/Root#field" - }, - "base": { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "BackboneElement", - "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" - }, - "fields": { - "subfield": { - "type": { - "kind": "nested", - "package": "mypackage", - "version": "0.0.0", - "name": "field.subfield", - "url": "http://example.io/fhir/Root#field.subfield" - }, - "array": true, - "required": false, - "excluded": false - } - } - }, - { - "identifier": { - "kind": "nested", - "package": "mypackage", - "version": "0.0.0", - "name": "field.subfield", - "url": "http://example.io/fhir/Root#field.subfield" - }, - "base": { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "BackboneElement", - "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" - }, - "fields": { - "subsubfield": { - "type": { - "kind": "primitive-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" - }, - "required": false, - "excluded": false, - "array": false - } - } - } - ], - "description": "some description", "dependencies": [ { - "kind": "complex-type", - "package": "hl7.fhir.r4.core", - "version": "4.0.1", - "name": "BackboneElement", - "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement" - }, - { - "kind": "resource", + "kind": "value-set", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "DomainResource", - "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" - }, + "name": "TaskStatus", + "url": "http://hl7.org/fhir/ValueSet/task-status" + } + ] + }, + { + "identifier": { + "kind": "binding", + "package": "mypackage", + "version": "0.0.0", + "name": "TutorNotification.type_binding", + "url": "http://example.com/aidbox-sms-tutor/TutorNotification#type_binding" + }, + "valueset": { + "kind": "value-set", + "package": "hl7.fhir.r4.core", + "version": "4.0.1", + "name": "ContactPointSystem", + "url": "http://hl7.org/fhir/ValueSet/contact-point-system" + }, + "strength": "required", + "enum": { + "isOpen": false, + "values": [ + "phone", + "fax", + "email", + "pager", + "url", + "sms", + "other" + ] + }, + "dependencies": [ { - "kind": "primitive-type", + "kind": "value-set", "package": "hl7.fhir.r4.core", "version": "4.0.1", - "name": "string", - "url": "http://hl7.org/fhir/StructureDefinition/string" + "name": "ContactPointSystem", + "url": "http://hl7.org/fhir/ValueSet/contact-point-system" } ] }