Skip to content

Base FHIR Models

These classes provide base for constructing Fhircraft-compatible Pydantic FHIR models.

FHIRBaseModel

Path: fhircraft.fhir.resources.base.FHIRBaseModel

Bases: BaseModel, FHIRPathMixin

Base class for representation of FHIR resources as Pydantic objects.

Expands the Pydantic BaseModel class with FHIR-specific methods.

Methods:

Name Description
model_post_init

Initialize model and set up parent tracking.

model_dump_xml

Serialize the FHIR resource to XML format according to FHIR specification.

model_construct

Constructs a model without running validation, with an option to set default values for fields that have them defined.

model_validate

Override model_validate to provide default kwargs for FHIR resources.

model_validate_json

Override model_validate_json to provide default kwargs for FHIR resources.

model_validate_xml

Deserialize FHIR XML data into a model instance.

model_copy

Override model_copy to reset parent context on copied instance.

model_construct_with_slices

Constructs a model with sliced elements by creating empty slice instances based on the specified number of slice copies.

get_sliced_elements

Get the sliced elements from the model fields and their extension fields.

clean_unusued_slice_instances

Cleans up unused or incomplete slice instances within the given FHIR resource by iterating through the

model_post_init

model_post_init(context: Any) -> None

Initialize model and set up parent tracking.

Source code in fhircraft/fhir/resources/base.py
def model_post_init(self, context: Any) -> None:
    """Initialize model and set up parent tracking."""
    # After construction, propagate context to all nested fields
    self._set_resource_context()

model_dump_xml

model_dump_xml(*, indent: int | None = None, ensure_ascii: bool = True, include: IncEx | None = None, exclude: IncEx | None = None, exclude_unset: bool = False, exclude_none: bool = False, exclude_defaults: bool = False) -> str

Serialize the FHIR resource to XML format according to FHIR specification.

Parameters:

Name Type Description Default
indent int | None

Indentation to use in the XML output. If None is passed, the output will be compact.

None
ensure_ascii bool

Whether to escape non-ASCII characters.

True
include IncEx | None

Fields to include in the output

None
exclude IncEx | None

Fields to exclude from the output

None
exclude_unset bool

Whether to exclude fields that were not explicitly set

False
exclude_none bool

Whether to exclude fields with None values

False
exclude_defaults bool

Whether to exclude fields with default values

False

Returns:

Type Description
str

A string containing the XML representation of the FHIR resource

Source code in fhircraft/fhir/resources/base.py
def model_dump_xml(
    self,
    *,
    indent: int | None = None,
    ensure_ascii: bool = True,
    include: IncEx | None = None,
    exclude: IncEx | None = None,
    exclude_unset: bool = False,
    exclude_none: bool = False,
    exclude_defaults: bool = False,
) -> str:
    """
    Serialize the FHIR resource to XML format according to FHIR specification.

    Args:
        indent: Indentation to use in the XML output. If None is passed, the output will be compact.
        ensure_ascii: Whether to escape non-ASCII characters.
        include: Fields to include in the output
        exclude: Fields to exclude from the output
        exclude_unset: Whether to exclude fields that were not explicitly set
        exclude_none: Whether to exclude fields with None values
        exclude_defaults: Whether to exclude fields with default values

    Returns:
        A string containing the XML representation of the FHIR resource
    """
    # Register the FHIR namespace with empty prefix (default namespace)
    from xml.etree.ElementTree import register_namespace

    register_namespace("", "http://hl7.org/fhir")

    # Determine the root element name BEFORE filtering (so exclude_defaults doesn't affect it)
    if hasattr(self, "resourceType"):
        root_name = self.resourceType
    else:
        root_name = self.__class__.__name__

    # Get the data as a dictionary with filtering options
    data = self.model_dump(
        by_alias=True,
        include=include,
        exclude=exclude,
        exclude_unset=exclude_unset,
        exclude_none=exclude_none,
        exclude_defaults=exclude_defaults,
    )

    # Create the root element with FHIR namespace using Clark notation
    # This creates the element in the namespace but serializes with xmlns attribute
    root = ET_Element(f"{{http://hl7.org/fhir}}{root_name}")

    # Build the XML tree
    self._build_xml_element(root, data, root_name)

    # Convert to string with encoding option
    encoding = "unicode" if ensure_ascii else "unicode"
    xml_str = tostring(root, encoding=encoding)

    # Pretty print if requested
    if indent is not None:
        try:
            dom = minidom.parseString(xml_str)
            xml_str = dom.toprettyxml(indent="  " * indent)
            # Remove extra blank lines and XML declaration if not needed
            lines = [line for line in xml_str.split("\n") if line.strip()]
            # Keep XML declaration
            xml_str = "\n".join(lines)
        except Exception:
            # If pretty printing fails, return the raw XML
            pass

    return xml_str

model_construct classmethod

model_construct(set_defaults=True, *args, **kwargs) -> Self

Constructs a model without running validation, with an option to set default values for fields that have them defined.

Parameters:

Name Type Description Default
set_defaults bool

Optional, if True, sets default values for fields that have them defined (default is True).

True

Returns:

Name Type Description
instance Self

An instance of the model.

Source code in fhircraft/fhir/resources/base.py
@classmethod
def model_construct(cls, set_defaults=True, *args, **kwargs) -> Self:
    """
    Constructs a model without running validation, with an option to set default values for fields that have them defined.

    Args:
        set_defaults (bool): Optional, if `True`, sets default values for fields that have them defined (default is `True`).

    Returns:
        instance (Self): An instance of the model.
    """
    instance = super().model_construct(*args, **kwargs)

    if not set_defaults:
        # Still need to set context even if not setting defaults
        instance._set_resource_context()
        return instance

    # Set default values for fields that have them defined
    for field_name, field in cls.model_fields.items():
        if getattr(instance, field_name, None) is not None:
            continue
        if field.default not in (PydanticUndefined, None):
            setattr(instance, field_name, copy(field.default))
        elif field.default_factory not in (PydanticUndefined, None):
            setattr(instance, field_name, field.default_factory)

    # Set context after all fields are set
    instance._set_resource_context()
    return instance

model_validate classmethod

model_validate(obj, *, strict=None, from_attributes=None, context=None) -> Self

Override model_validate to provide default kwargs for FHIR resources.

Source code in fhircraft/fhir/resources/base.py
@classmethod
def model_validate(
    cls, obj, *, strict=None, from_attributes=None, context=None
) -> Self:
    """Override model_validate to provide default kwargs for FHIR resources."""
    instance = super().model_validate(
        obj, strict=strict, from_attributes=from_attributes, context=context
    )

    # Set up resource context for the root instance if it's a resource
    if hasattr(instance, "resourceType"):
        instance._set_resource_context(
            parent=None, root=instance, resource=instance
        )

    return instance

model_validate_json classmethod

model_validate_json(json_data: str, *, strict: bool = False, context: Any = None, extra: ExtraValues | None = None) -> Self

Override model_validate_json to provide default kwargs for FHIR resources.

Parameters:

Name Type Description Default
json_data str

JSON string to deserialize

required
strict bool

Whether to validate strictly

False
context Any

Additional context for validation

None
extra ExtraValues | None

Extra parameters

None
Source code in fhircraft/fhir/resources/base.py
@classmethod
def model_validate_json(
    cls,
    json_data: str,
    *,
    strict: bool = False,
    context: Any = None,
    extra: ExtraValues | None = None,
) -> Self:
    """
    Override model_validate_json to provide default kwargs for FHIR resources.

    Args:
        json_data: JSON string to deserialize
        strict: Whether to validate strictly
        context: Additional context for validation
        extra: Extra parameters
    """
    instance = super().model_validate_json(
        json_data, strict=strict, context=context
    )

    # Set up resource context for the root instance if it's a resource
    if hasattr(instance, "resourceType"):
        instance._set_resource_context(
            parent=None, root=instance, resource=instance
        )

    return instance

model_validate_xml classmethod

model_validate_xml(xml_data: str, *, strict: bool = None, context: Any = None) -> Self

Deserialize FHIR XML data into a model instance.

Parameters:

Name Type Description Default
xml_data str

XML string to deserialize

required
strict bool

Whether to validate strictly

None
context Any

Additional context for validation

None

Returns:

Type Description
Self

An instance of the model populated from the XML data

Source code in fhircraft/fhir/resources/base.py
@classmethod
def model_validate_xml(
    cls, xml_data: str, *, strict: bool = None, context: Any = None
) -> Self:
    """
    Deserialize FHIR XML data into a model instance.

    Args:
        xml_data: XML string to deserialize
        strict: Whether to validate strictly
        context: Additional context for validation

    Returns:
        An instance of the model populated from the XML data
    """
    from xml.etree.ElementTree import fromstring

    # Parse the XML
    root = fromstring(xml_data)

    # Convert XML to dictionary, passing model class for type checking
    data = cls._xml_element_to_dict(root, model_class=cls)

    # Use existing model_validate with the dictionary
    return cls.model_validate(data, strict=strict, context=context)

model_copy

model_copy(*, update: dict[str, Any] | None = None, deep: bool = False) -> Self

Override model_copy to reset parent context on copied instance.

Parameters:

Name Type Description Default
update dict[str, Any] | None

Optional dict of field updates to apply to the copy

None
deep bool

Whether to perform a deep copy

False

Returns:

Type Description
Self

A copied instance with reset parent context

Source code in fhircraft/fhir/resources/base.py
def model_copy(
    self, *, update: dict[str, Any] | None = None, deep: bool = False
) -> Self:
    """
    Override model_copy to reset parent context on copied instance.

    Args:
        update: Optional dict of field updates to apply to the copy
        deep: Whether to perform a deep copy

    Returns:
        A copied instance with reset parent context
    """
    # Avoid calling __deepcopy__ since model_copy(deep=True) calls it without memo
    # Instead, let Pydantic do the copy, then reset context
    copied: Self = BaseModel.model_copy(self, update=update, deep=deep)  # type: ignore
    # Reset context - copied instance should be a new root
    copied._set_resource_context()
    return copied

model_construct_with_slices classmethod

model_construct_with_slices(slice_copies: int = 9) -> object

Constructs a model with sliced elements by creating empty slice instances based on the specified number of slice copies. The method iterates over the sliced elements of the class, generates slice resources, and sets them in the resource collection.

Parameters:

Name Type Description Default
slice_copies int

Optional, an integer specifying the number of copies for each slice (default is 9).

9

Returns:

Name Type Description
instance Self

An instance of the model with the sliced elements constructed.

Source code in fhircraft/fhir/resources/base.py
@classmethod
def model_construct_with_slices(cls, slice_copies: int = 9) -> object:
    """
    Constructs a model with sliced elements by creating empty slice instances based on the specified number of slice copies.
    The method iterates over the sliced elements of the class, generates slice resources, and sets them in the resource collection.

    Args:
        slice_copies (int): Optional, an integer specifying the number of copies for each slice (default is 9).

    Returns:
        instance (Self): An instance of the model with the sliced elements constructed.
    """
    from fhircraft.fhir.path import fhirpath

    instance = super().model_construct()
    for element, slices in cls.get_sliced_elements().items():
        slice_resources = []
        for slice in slices:
            # Add empty slice instances
            slice_resources.extend(
                [
                    slice.model_construct_with_slices()
                    for _ in range(min(slice.max_cardinality, slice_copies))
                ]
            )
        # Set the whole list of slices in the resource
        collection = fhirpath.parse(element).__evaluate_wrapped(
            instance, create=True
        )
        [item.set_literal(slice_resources) for item in collection]
    return instance

get_sliced_elements classmethod

get_sliced_elements() -> dict[str, list[type[FHIRSliceModel]]]

Get the sliced elements from the model fields and their extension fields. Sliced elements are filtered based on being instances of FHIRSliceModel.

Returns:

Name Type Description
slices dict

A dictionary with field names as keys and corresponding sliced elements as values.

Source code in fhircraft/fhir/resources/base.py
@classmethod
def get_sliced_elements(cls) -> dict[str, list[type["FHIRSliceModel"]]]:
    """
    Get the sliced elements from the model fields and their extension fields.
    Sliced elements are filtered based on being instances of `FHIRSliceModel`.

    Returns:
        slices (dict): A dictionary with field names as keys and corresponding sliced elements as values.
    """
    # Get model elements' extension fields
    extensions = {
        f"{field_name}.extension": next(
            (
                arg.model_fields.get("extension")
                for arg in get_all_models_from_field(field)
                if arg.model_fields.get("extension")
            ),
            None,
        )
        for field_name, field in cls.model_fields.items()
        if field_name != "extension"
    }
    fields = {
        **cls.model_fields,
        **extensions,
    }
    # Compile the sliced elements in the model
    return {
        field_name: slices
        for field_name, field in fields.items()
        if field
        and bool(
            slices := list(
                get_all_models_from_field(field, issubclass_of=FHIRSliceModel)
            )
        )
    }

clean_unusued_slice_instances classmethod

clean_unusued_slice_instances(resource)

Cleans up unused or incomplete slice instances within the given FHIR resource by iterating through the sliced elements of the class, identifying valid elements, and updating the resource with only the valid slices.

Source code in fhircraft/fhir/resources/base.py
@classmethod
def clean_unusued_slice_instances(cls, resource):
    """
    Cleans up unused or incomplete slice instances within the given FHIR resource by iterating through the
    sliced elements of the class, identifying valid elements, and updating the resource with only the valid slices.
    """
    from fhircraft.fhir.path import fhirpath

    # Remove unused/incomplete slices
    for element, slices in cls.get_sliced_elements().items():
        valid_elements = [
            col.value
            for col in fhirpath.parse(element).__evaluate_wrapped(
                resource, create=True
            )
            if col.value is not None
        ]
        new_valid_elements = []
        if not valid_elements:
            continue
        for slice in slices:
            # Get all the elements that conform to this slice's definition
            sliced_entries = [
                entry for entry in valid_elements if isinstance(entry, slice)
            ]
            for entry in sliced_entries:
                if slice.get_sliced_elements():
                    entry = slice.clean_unusued_slice_instances(entry)
                if (entry.is_FHIR_complete and entry.has_been_modified) or (
                    entry.is_FHIR_complete
                    and not entry.has_been_modified
                    and slice.min_cardinality > 0
                ):
                    if entry not in new_valid_elements:
                        new_valid_elements.append(entry)
        # Set the new list with only the valid slices
        collection = fhirpath.parse(element).__evaluate_wrapped(
            resource, create=True
        )
        [col.set_literal(new_valid_elements) for col in collection]
    return resource

FHIRList

Path: fhircraft.fhir.resources.base.FHIRList

FHIRList(items=None, parent=None, root=None, resource=None)

Bases: list

Custom list wrapper that maintains parent context on mutations.

This list automatically propagates _parent, _root_resource, _resource, and _index context to FHIRBaseModel items when they are added via append, extend, insert, or setitem.

Methods:

Name Description
append

Append item and propagate context.

extend

Extend list and propagate context to new items.

insert

Insert item and propagate context.

Source code in fhircraft/fhir/resources/base.py
def __init__(self, items=None, parent=None, root=None, resource=None):
    """Initialize FHIRList with items and context."""
    super().__init__(items or [])
    self._parent = parent
    self._root = root
    self._resource = resource
    self._propagate_context()

append

append(item)

Append item and propagate context.

Source code in fhircraft/fhir/resources/base.py
def append(self, item):
    """Append item and propagate context."""
    super().append(item)
    if isinstance(item, FHIRBaseModel):
        # Index is the last position
        index = len(self) - 1
        item._set_resource_context(
            parent=self._parent,
            root=self._root,
            resource=self._resource,
            index=index,
        )

extend

extend(items)

Extend list and propagate context to new items.

Source code in fhircraft/fhir/resources/base.py
def extend(self, items):
    """Extend list and propagate context to new items."""
    start_index = len(self)
    super().extend(items)
    # Only propagate to newly added items
    for offset, item in enumerate(items):
        if isinstance(item, FHIRBaseModel):
            item._set_resource_context(
                parent=self._parent,
                root=self._root,
                resource=self._resource,
                index=start_index + offset,
            )

insert

insert(index, item)

Insert item and propagate context.

Source code in fhircraft/fhir/resources/base.py
def insert(self, index, item):
    """Insert item and propagate context."""
    super().insert(index, item)
    if isinstance(item, FHIRBaseModel):
        item._set_resource_context(
            parent=self._parent,
            root=self._root,
            resource=self._resource,
            index=index,
        )
    # Re-index all items after insertion point
    for i in range(index + 1, len(self)):
        if isinstance(self[i], FHIRBaseModel):
            object.__setattr__(self[i], "_index", i)

FHIRSliceModel

Path: fhircraft.fhir.resources.base.FHIRSliceModel

Bases: FHIRBaseModel

Base class for representation of FHIR profiled slices as Pydantic objects.

Expands the FHIRBaseModel class with slice-specific methods.

Attributes:

Name Type Description
is_FHIR_complete

Validates if the FHIR model is complete by attempting to validate the model dump.

has_been_modified

Checks if the FHIRSliceModel instance has been modified by comparing it with a new instance constructed with slices.

is_FHIR_complete property

is_FHIR_complete

Validates if the FHIR model is complete by attempting to validate the model dump. Returns True if the model is complete, False otherwise.

has_been_modified property

has_been_modified

Checks if the FHIRSliceModel instance has been modified by comparing it with a new instance constructed with slices. Returns True if the instance has been modified, False otherwise.