Error Handling
This guide shows you how to handle errors raised by Fhircraft. You will learn about the exception hierarchy, understand which exceptions each component raises and why, and write effective error-handling code that makes your FHIR application robust and easy to debug.
Exception Hierarchy
Fhircraft organizes its exceptions and warnings into two parallel trees, both importable from fhircraft.exceptions. Exceptions (FhircraftException and its subclasses) signal errors that stop execution and must be handled. Warnings (FhircraftWarning and its subclasses) signal non-critical issues issued via Python's warnings module and do not interrupt execution.
Because each component has its own intermediate base class, you can tune the granularity of your except and warnings.filterwarnings calls: catch the root for a coarse safety net, or target only the specific leaf class you care about.
Exceptions
FhircraftException
├── FhirTypeError # FHIR type checking and conversion
├── MapperException # FHIR Mapping Language engine
│ ├── MapperParsingError
│ ├── MapperValidationError
│ ├── MapperScopeError
│ ├── MapperDigestionError
│ ├── MapperGroupProcessingError
│ ├── MapperRuleProcessingError
│ ├── MapperSourceProcessingError
│ ├── MapperTargetProcessingError
│ ├── MapperExecutionError
│ └── MapperRegistryNotFoundError
├── FhirPathException # FHIRPath evaluation engine
│ ├── FhirPathParsingError
│ ├── FhirPathLexingError
│ ├── FhirPathRuntimeError
│ ├── FhirPathTypeError
│ └── FhirPathOperationError
├── FactoryException # FHIR model factory and builders
│ ├── FactoryDefinitionIndexError
│ ├── FactoryDefinitionResolutionError
│ ├── FactoryBuilderError
│ ├── FactoryTypeResolutionError
│ └── FactoryAssemblerError
├── DefinitionNotFoundError # Structure definition registry
└── PackageException # FHIR package registry
├── PackageNotFoundError
├── PackageResolutionError
└── PackageValidationError
Warnings
FhircraftWarning (also a Warning)
├── FhirPathWarning # FHIRPath evaluation engine
├── FhirValidationWarning # FHIR resource validation
└── FactoryWarning # FHIR model factory
There is one deliberate exception to the exceptions tree: FHIR resource validation errors (constraint and invariant failures on Patient, Observation, etc.) surface as Pydantic's ValidationError, not as a FhircraftException subclass. This is intentional — resource models are standard Pydantic models, so they use the standard Pydantic error mechanism. Non-critical constraint issues that do not block execution emit FhirValidationWarning instead. See FHIR Validation Errors for details.
Technical Documentation
Importing Exceptions
All exceptions and warnings live in fhircraft.exceptions. Import exactly what you need — using the intermediate base class (e.g. FhirPathException) keeps your except clauses readable without catching too broadly:
# Exceptions
from fhircraft.exceptions import FhircraftException, FhirPathException
# Warnings
from fhircraft.exceptions import FhircraftWarning, FhirValidationWarning
Catch specific before broad
When chaining multiple except clauses, always list the most specific exception first. Python checks clauses in order, so a broad except FhircraftException placed before except DefinitionNotFoundError would shadow the specific branch and prevent it from ever matching.
Exception Attributes
Every FhircraftException provides two structured attributes beyond the standard string message. These attributes let you write clean logging and alerting code without parsing the exception string yourself:
| Attribute | Type | Description |
|---|---|---|
message |
str |
The raw error description, without the component prefix |
component |
str |
None| Which library component raised the error ("factory","fhirpath","mapper","packages","registry"`) |
from fhircraft.exceptions import FhircraftException, DefinitionNotFoundError
from fhircraft.fhir.resources import FHIRModelFactory
factory = FHIRModelFactory(fhir_release="R4")
try:
model = factory.build("http://example.org/StructureDefinition/Unknown")
except FhircraftException as e:
print(e.message) # the raw description
print(e.component) # which subsystem raised it
The str() representation always produces [component] message, making it easy to read in log output without needing a log formatter to add context.
Catching Exceptions by Component
FHIRPath Errors
FHIRPath evaluation can fail at two distinct stages: when parsing the expression text, and when evaluating it against data. Separating these cases lets you report helpful messages to users — a parsing failure means the expression string is wrong (a developer mistake or bad input), while a runtime failure means the expression is valid but the data does not match what the expression expects.
from fhircraft.exceptions import (
FhirPathParsingError,
FhirPathLexingError,
FhirPathRuntimeError,
)
from fhircraft.fhir.resources import get_fhir_type
Patient = get_fhir_type("Patient", "R5")
patient = Patient(
name=[
{"given": ["Alice"], "family": "Johnson"},
{"given": ["Aly"], "family": "Johnson", "use": "nickname"},
]
)
# A malformed expression fails at parse time
try:
patient.fhirpath_single("Patient.name.where(use = ") # (1)!
except FhirPathParsingError as e:
print(f"Fix the expression: {e.message}")
# A valid expression that returns multiple values fails at runtime
try:
patient.fhirpath_single("Patient.name") # (2)!
except FhirPathRuntimeError as e:
print(f"Adjust the query: {e.message}")
- The
where(clause is never closed — this fails during lexing/parsing before any data is touched. - The expression is syntactically valid, but
single()expects exactly one result and the patient has two names.
| Exception | When it is raised |
|---|---|
FhirPathParsingError |
Expression cannot be parsed — invalid syntax such as unclosed parentheses or unknown keywords |
FhirPathLexingError |
Expression contains tokens the lexer does not recognize |
FhirPathRuntimeError |
Evaluation fails at runtime — wrong cardinality, failed navigation, invalid single() or update_single() call |
FhirPathTypeError |
Incompatible types used together (e.g. comparing a string to a date without conversion) |
FhirPathOperationError |
A function or operator is called with incompatible or unsupported arguments |
FhirPathWarning
FhirPathWarning inherits from FhircraftWarning, which in turn inherits from Python's built-in Warning. It is issued via Python's warnings module for non-fatal FHIRPath situations. Because of the shared base, you can filter on FhirPathWarning to target only FHIRPath warnings, or on FhircraftWarning to capture warnings from any Fhircraft component at once:
import warnings
from fhircraft.exceptions import FhirPathWarning, FhircraftWarning
# Capture only FHIRPath warnings
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always", FhirPathWarning)
result = patient.fhirpath_values("Patient.name")
for w in caught:
print(f"FHIRPath warning: {w.message}")
# Or suppress all Fhircraft warnings at once
warnings.filterwarnings("ignore", category=FhircraftWarning)
FHIR Validation Errors
When Fhircraft constructs or modifies a FHIR resource, Pydantic validates every field automatically. Hard failures raise pydantic.ValidationError, which bundles all constraint violations into a single exception. Each entry in the errors() list describes one violation.
There are two kinds of failures inside a ValidationError:
- Field type errors — a field received a value of the wrong Python type or an unrecognized code.
- FHIR invariant violations — an invariant from the FHIR specification failed (e.g.
dom-6for missing narrative). These use Pydantic'sPydanticCustomErrorinternally and appear with atypestring prefixedfhir_.
from pydantic import ValidationError
from fhircraft.fhir.resources import get_fhir_type
Observation = get_fhir_type("Observation", "R4")
try:
obs = Observation.model_validate({"valueReference": {"type": "only-type"}}) # (1)!
except ValidationError as e:
for error in e.errors():
if error["type"].startswith("fhir_"): # (2)!
key = error["type"].removeprefix("fhir_")
print(f"FHIR invariant [{key}]: {error['msg']}")
else:
print(f"Field error {error['loc']}: {error['msg']}")
- Any missing required field or mismatched type triggers a
ValidationError. - FHIR constraint violations use the pattern
fhir_<constraint-key>, e.g.fhir_dom-6,fhir_ele-1.
Non-critical validation issues — where a constraint is considered a warning rather than an error — emit FhirValidationWarning via Python's warnings module instead of raising. You can capture these the same way as other Fhircraft warnings:
import warnings
from fhircraft.exceptions import FhirValidationWarning
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always", FhirValidationWarning)
patient = Patient(name=[{"given": ["Alice"]}]) # may emit dom-6 warning
for w in caught:
print(f"Validation warning: {w.message}")
See also
See Configuring Validation Behavior for details on switching between strict, lenient, and skip validation modes, and for disabling specific constraint keys.
Resource factory Errors
The factory raises specific errors depending at which stage the model build fails. In a healthy workflow, DefinitionNotFoundError is the only factory exception you routinely need to catch — the others indicate either a corrupt structure definition or an internal library issue worth logging and escalating.
from pydantic import ValidationError
from fhircraft.exceptions import (
DefinitionNotFoundError,
FactoryBuilderError,
FactoryDefinitionResolutionError,
FactoryTypeResolutionError,
)
from fhircraft.fhir.resources import FHIRModelFactory
factory = FHIRModelFactory(fhir_release="R4")
try:
MyPatient = factory.build("http://example.org/StructureDefinition/MyPatient")
except DefinitionNotFoundError as e:
# The most common case: the definition was never loaded into the registry
print(f"Load the definition first: {e.message}")
except FactoryDefinitionResolutionError as e:
# The definition was found, but snapshot/differential resolution failed
print(f"Definition may be malformed: {e.message}")
except FactoryTypeResolutionError as e:
# A FHIR type referenced inside the definition could not be resolved
print(f"Unsupported or missing type: {e.message}")
except FactoryBuilderError as e:
# General model assembly failure
print(f"Build failed: {e.message}")
| Exception | When it is raised |
|---|---|
DefinitionNotFoundError |
The requested canonical URL is not present in the definition registry |
FactoryDefinitionResolutionError |
Snapshot or differential resolution fails (e.g. broken inheritance chain) |
FactoryDefinitionIndexError |
Navigation of the element index inside the definition fails |
FactoryBuilderError |
A builder step fails during model construction |
FactoryTypeResolutionError |
A FHIR type referenced in the definition cannot be mapped to a Python type |
FactoryAssemblerError |
The final model assembly step fails when combining builder outputs |
FhirTypeError |
A type checking or conversion operation on a FHIR primitive or complex type fails |
Non-critical factory situations — such as falling back to a default when an optional feature is unavailable — emit FactoryWarning rather than raising. Use warnings.filterwarnings to capture or suppress them:
import warnings
from fhircraft.exceptions import FactoryWarning
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always", FactoryWarning)
MyModel = factory.build("http://example.org/StructureDefinition/MyPatient")
for w in caught:
print(f"Factory warning: {w.message}")
DefinitionNotFoundError is also a FileNotFoundError
DefinitionNotFoundError inherits from both FhircraftException and Python's built-in FileNotFoundError. Libraries or frameworks that catch FileNotFoundError at a high level will therefore also catch missing-definition errors without requiring any Fhircraft-specific handling.
Mapper Errors
The FHIR Mapper engine processes StructureMap scripts through several sequential stages: lexing and parsing the mapping script, digesting the group and rule definitions, and executing the transformation. Each stage has its own exception class so you can tell immediately where in the pipeline a failure occurred.
from fhircraft.exceptions import (
MapperParsingError,
MapperDigestionError,
MapperRuleProcessingError,
MapperExecutionError,
MapperRegistryNotFoundError,
)
from fhircraft.fhir.mapper import FHIRStructureMapper
mapper = FHIRStructureMapper()
mapping_script = """
map 'http://example.org/mapping' = 'MyMapping'
uses "http://hl7.org/fhir/StructureDefinition/Patient" as target
group main(source src, target patient: Patient) {
src.name -> patient.name;
}
"""
source_data = {"name": [{"given": ["Alice"], "family": "Johnson"}]}
try:
results = mapper.map(mapping_script, source_data) # (1)!
except MapperParsingError as e:
# Syntax error in the mapping script itself
print(f"Mapping script syntax error: {e.message}")
except MapperDigestionError as e:
# Valid syntax but structurally inconsistent group or rule definitions
print(f"Mapping definition error: {e.message}")
except MapperRegistryNotFoundError as e:
# A referenced StructureMap or StructureDefinition could not be resolved
print(f"Missing dependency: {e.message}")
except MapperExecutionError as e:
# The script parsed fine but failed during the actual data transformation
print(f"Transformation failed: {e.message}")
mapper.map()runs the full pipeline: parse → digest → execute. Any stage may raise its exception type.
| Exception | When it is raised |
|---|---|
MapperParsingError |
The mapping script has invalid FHIR Mapping Language syntax |
MapperValidationError |
Input data fails pre-transformation validation |
MapperScopeError |
A variable or scope reference cannot be resolved during execution |
MapperDigestionError |
Group or rule definitions are structurally invalid after parsing |
MapperGroupProcessingError |
A specific group within the mapping fails to process |
MapperRuleProcessingError |
A single mapping rule fails to process |
MapperSourceProcessingError |
Reading or resolving a source value fails |
MapperTargetProcessingError |
Writing or resolving a target value fails |
MapperExecutionError |
Top-level execution failure not covered by a more specific class |
MapperRegistryNotFoundError |
A required StructureMap or StructureDefinition cannot be found |
Package Registry Errors
When loading FHIR packages from the registry — either the default FHIR NPM registry or a custom endpoint — network issues, missing packages, or malformed archives raise package-specific exceptions.
from fhircraft.exceptions import (
PackageNotFoundError,
PackageResolutionError,
PackageValidationError,
)
from fhircraft.fhir.resources import FHIRModelFactory
factory = FHIRModelFactory(fhir_release="R4")
try:
factory.register_package("hl7.fhir.us.core", "6.1.0") # (1)!
except PackageNotFoundError as e:
# Package name or version does not exist in the registry
print(f"Package not published: {e.message}")
except PackageResolutionError as e:
# Package was found but downloading or extracting it failed
# (network error, corrupted archive, file system permission, etc.)
print(f"Could not load package: {e.message}")
except PackageValidationError as e:
# Package was loaded but its contents failed structural validation
print(f"Package contents are invalid: {e.message}")
register_packagecontacts the FHIR package registry, downloads the specified version, extracts the.tgzarchive, and registers all contained StructureDefinitions.
| Exception | When it is raised |
|---|---|
PackageNotFoundError |
The package name and/or version does not exist in the registry |
PackageResolutionError |
Download or archive extraction failed (network, I/O, or format error) |
PackageValidationError |
Package metadata or contained resource files failed validation |
Integrating with Standard Exception Types
Several Fhircraft exceptions also inherit from standard Python built-ins. This dual inheritance means code that catches standard built-in types (e.g. a generic HTTP framework handler catching FileNotFoundError) will also catch the corresponding Fhircraft exceptions without requiring Fhircraft-specific imports:
| Fhircraft Exception | Also a subclass of | Practical effect |
|---|---|---|
DefinitionNotFoundError |
FileNotFoundError |
Caught by filesystem / resource-not-found handlers |
MapperRegistryNotFoundError |
FileNotFoundError |
Caught by the same generic not-found handlers |
FhirPathRuntimeError |
RuntimeError |
Caught by generic runtime error handlers |
FactoryTypeResolutionError |
LookupError |
Caught by generic lookup/key-error handlers |
FactoryAssemblerError |
LookupError |
Caught by generic lookup/key-error handlers |
FhircraftWarning |
Warning |
Suppressed/filtered by any handler targeting Warning |
FhirPathWarning |
FhircraftWarning, Warning |
Filtered by either FhircraftWarning or Warning |
FhirValidationWarning |
FhircraftWarning, Warning |
Filtered by either FhircraftWarning or Warning |
FactoryWarning |
FhircraftWarning, Warning |
Filtered by either FhircraftWarning or Warning |
# A framework handler that catches FileNotFoundError will also catch this
from fhircraft.exceptions import DefinitionNotFoundError
try:
raise DefinitionNotFoundError("No definition for 'MyPatient'")
except FileNotFoundError as e:
print('Exception caught') # caught without any Fhircraft import needed
#> Exception caught
Common Problems and Solutions
| Problem | Solution |
|---|---|
FhircraftException catch-all hides the root cause |
Narrow the except clause to the component's base class (e.g. FactoryException, FhirPathException) or to the specific leaf exception. Log e.component and e.message separately so the subsystem is always visible in output. |
DefinitionNotFoundError raised at build time |
The required structure definition was not loaded before calling factory.build(). Call factory.register() or factory.register_package() first, then retry the build. |
ValidationError raised but unclear which field failed |
Iterate e.errors() and print each entry's loc, type, and msg keys. Types prefixed with fhir_ identify FHIR invariant violations (e.g. fhir_dom-6); all others are Pydantic field-type errors. |
| FHIR constraint violations appear even on valid-looking data | The resource may be missing optional-but-constrained elements such as narrative text (dom-6). Use disable_constraint('dom-6') or switch to validation_mode='lenient' for that operation. See Configuring Validation Behavior. |
FhirPathParsingError on a seemingly correct expression |
Check for unclosed parentheses, mismatched quotes, or unsupported syntax. Validate the expression against the FHIRPath specification. |
FhirPathRuntimeError from fhirpath_single() |
The expression matched more than one value. Use fhirpath_values() to retrieve all matches, or tighten the expression with a where() filter so only one result is returned. |
Mapper raises MapperRegistryNotFoundError |
A uses declaration in the mapping script references a StructureDefinition that is not loaded. Register the definition or load the relevant package before running the mapper. |
MapperExecutionError with no clear cause |
Enable verbose logging and inspect intermediate scope state. Break the mapping script into smaller groups to isolate the failing rule. |
PackageNotFoundError for a package that exists on the registry |
Verify the package name and version string exactly match the registry entry (names are case-sensitive). Check that the configured registry URL is reachable. |
PackageResolutionError despite the package being found |
A network timeout, proxy restriction, or disk permission issue prevented download or extraction. Confirm internet access, check available disk space, and verify write permissions on the package cache directory. |
Uncertain whether two exceptions overlap in an except chain |
Use issubclass(SpecificError, BroadError) to verify the relationship before writing code that depends on it. Place the most specific clause first to avoid it being shadowed. |