MaLaC-HD library / API guide¶
Besides the malac-hd command-line tool, MaLaC-HD
can be used directly from Python. This is useful when you want to generate
mapping code as part of a build step, run transformations inside a larger
application, or register your own model packages programmatically.
The workflow mirrors the two CLI modes:
- Convert a mapping (FML, or a FHIR StructureMap/ConceptMap resource) into standalone Python source code.
- Transform input data by running that generated code.
The examples below use sample mappings under tests/, so they run as-is from a
source checkout of the repository. Those fixtures are not part of the
installed PyPI package — when running against an installed malac-hd, substitute
your own mapping and input files for the tests/... paths.
Note on versions. MaLaC-HD ships a generator per supported FHIR version. Import the
r4orr5generator to match your mapping; the two expose the same API. The examples user5; substituter4where noted.
Table of contents¶
- Converting a mapping to Python
- Running a transformation
- Convert and transform in one step
- Converting a StructureMap resource
- Registering model packages
- Error handling
- Utilities
- CLI to API cheatsheet
Converting a mapping to Python¶
convert() turns a parsed mapping into a string of standalone Python source
code. Parse the FML map with the matching generator, then call convert():
import os
from malac.hd.core.fhir.r5.generator import fml
# for an R4 mapping: from malac.hd.core.fhir.r4.generator import fml
map_path = "tests/fml/r5/source_cardinality/sourceCardinality2.map"
structure_map = fml.parse(map_path, silence=True)
py_code = structure_map.convert(silent=True, source=os.path.abspath(map_path))
# py_code is a complete, self-contained Python module as a string
print(len(py_code), "characters of generated Python")
parse(path, silence=True) reads the FML file (silence=True suppresses the
echo of the parsed map). convert() accepts:
| argument | default | meaning |
|---|---|---|
silent |
True |
when False, prints the generated Python to stdout while converting |
source |
"." |
absolute path to the mapping, embedded in the generated code as its origin (used for error messages); pass os.path.abspath(map_path) |
standalone |
True |
emit a runnable module (with if __name__ == "__main__") rather than a fragment |
To persist the generated module, just write the string to a file:
This is the library equivalent of malac-hd -m map.5.map -co mapping.py.
Running a transformation¶
The generated code exposes a transform(source_path, target_path) function.
Load the code as a module and call it. You can load from the in-memory string
(no file needed):
import importlib.util
spec = importlib.util.spec_from_loader("mapping", loader=None)
module = importlib.util.module_from_spec(spec)
exec(py_code, module.__dict__) # py_code from convert() above
module.transform(
"tests/fml/r5/source_cardinality/patient_1name.xml", # source
"out.xml", # target
)
...or, if you saved the module to mapping.py and it is on sys.path:
The format is detected from the file extension of both paths: .xml reads
and writes FHIR XML, .json reads and writes FHIR JSON (JSON is available where
the source/target model supports it). The resource type is detected from the
document itself, so no type argument is needed.
This is the library equivalent of python mapping.py -s patient.xml -t out.xml.
Reading and writing streams¶
transform() takes two further optional arguments, source_type and
target_type ("xml" or "json"), and accepts "-" in place of either path
to read from stdin / write to stdout:
The format cannot be sniffed from a stream, so the matching *_type is
required whenever the path is "-" — otherwise transform() raises
ValueError. For real file paths the two arguments are optional and override
the extension-based detection:
# a .txt file that actually holds FHIR JSON
module.transform("payload.txt", "out.xml", source_type="json")
Passing source_type="json" for a mapping whose model is XML-only (CDA, for
example) also raises ValueError. Progress messages go to stderr, so a
transform writing to stdout emits only the payload — which is what lets
generated scripts be chained in a shell pipeline:
cat patient.xml | python step1.py -s - -t - -st xml -tt xml | python step2.py -s - -t out.xml -st xml
This is the library equivalent of python mapping.py -s - -t - -st xml -tt xml.
Convert and transform in one step¶
For quick testing you can chain both phases without ever touching disk for the generated code:
import importlib.util, os
from malac.hd.core.fhir.r5.generator import fml
def compile_map(map_path):
structure_map = fml.parse(map_path, silence=True)
py_code = structure_map.convert(silent=True, source=os.path.abspath(map_path))
spec = importlib.util.spec_from_loader("mapping", loader=None)
module = importlib.util.module_from_spec(spec)
exec(py_code, module.__dict__)
return module
mapping = compile_map("tests/fml/r5/source_cardinality/sourceCardinality2.map")
mapping.transform("tests/fml/r5/source_cardinality/patient_1name.xml", "out.xml")
This mirrors malac-hd -m map.5.map -ti patient.xml -to out.xml (convert, then
immediately transform).
Converting a StructureMap resource instead of FML¶
The same generators can consume a FHIR StructureMap resource directly
instead of FML. malac.hd.list_m_modules maps a mapping file's extension to the
generator module that handles it:
import malac.hd
print(malac.hd.list_m_modules[".5.fhir.xml"]) # 'fhir.r5.generator.structuremap'
print(malac.hd.list_m_modules[".map"]) # ['fhir.r5...fml', 'fhir.r4...fml']
Parse the StructureMap with that generator and call convert() exactly as
above. For an XML resource:
from malac.hd.core.fhir.r5.generator import structuremap
structure_map = structuremap.parse("map.5.fhir.xml", silence=True)
py_code = structure_map.convert(silent=True, source="map.5.fhir.xml")
When an extension maps to a list of candidate generators (e.g. plain .map
or .xml, which could be r4 or r5), the CLI tries each and keeps the
best-matching one. In library code, pick the generator for the version you know
you have, or replicate that selection logic (see malac/hd/__main__.py).
That choice is about which generator parses your mapping. Which FHIR model the
generated code is built against is decided separately, by
malac.hd.core.fhir.base.generator.model.select_fhir_module: the target model
wins, then the source model, and a mapping between two non-FHIR models falls
back to the FHIR version of the generator in use. The generator exposes the
result as fhir_module, next to i_is_fhir / o_is_fhir for the separate
question of whether a given side of the mapping is FHIR at all. Mappings that
cross FHIR versions (an R4-to-R5 map, say) therefore compile against one model
consistently, without the caller having to arrange it.
Registering model packages¶
Mappings reference their source/target models through uses URLs. MaLaC-HD
resolves those URLs against malac.hd.list_i_o_modules. The built-in FHIR (and
optional CDA) models are registered automatically; to add your own model
packages (for example project-specific logical models), register them before
converting so the uses URLs resolve:
import malac.hd
# module names and/or paths to .py files, each exposing `list_i_o_modules`
malac.hd.load_model_packages(["my_models_plugin", "/path/to/other_models.py"])
# or from the MALAC_HD_MODELS environment variable (comma- or os.pathsep-separated)
malac.hd.load_model_packages_from_env()
A model package is any importable Python module exposing a list_i_o_modules
dict that maps canonical uses-URL prefixes to the module holding the model
classes — the same contract malac.models.fhir fulfils. See
Model package plugins in the README for
the plugin contract and CLI (-mod/--models) equivalents.
load_model_packages() raises if a requested package cannot be imported or
does not expose list_i_o_modules — an explicitly requested package must load.
It returns the list of loaded modules.
Error handling¶
Every error MaLaC-HD raises derives from malac.hd.exceptions.MalacHDError, so a
caller can catch the whole family without also catching KeyboardInterrupt /
SystemExit:
from malac.hd.exceptions import MalacHDError
from malac.hd.core.fhir.r5.generator import fml
try:
py_code = fml.parse(map_path, silence=True).convert(silent=True, source=map_path)
except MalacHDError as err:
print(f"Could not compile the mapping: {err}")
Catch a specific subclass to react to one kind of problem:
| Exception | Raised when |
|---|---|
MappingClassificationError |
the mapping's rule language / FHIR version can't be determined |
ModuleResolutionError |
a uses URL has no registered source/target model |
MappingImportError |
an imported mapping/resource file can't be found |
GroupResolutionError |
a StructureMap group can't be resolved or is invalid |
UnsupportedMappingError |
the mapping uses a construct MaLaC-HD can't compile |
FHIRPathError |
a FHIRPath expression can't be compiled |
ConceptMapError |
a ConceptMap can't be converted |
All of these subclass MappingError, which is itself a MalacHDError.
The malac-hd command-line tool catches these and prints a clean
Error: <message> to stderr with a non-zero exit code, instead of a Python
traceback.
Utilities¶
CLI to API cheatsheet¶
| CLI | Library |
|---|---|
malac-hd -m map.5.map -co out.py |
code = fml.parse("map.5.map", silence=True).convert(...) then write it |
python out.py -s in.xml -t out.xml |
module.transform("in.xml", "out.xml") |
python out.py -s - -t - -st xml -tt xml |
module.transform("-", "-", source_type="xml", target_type="xml") |
malac-hd -m map.5.map -ti in.xml -to out.xml |
convert + transform in one step |
malac-hd ... -mod my_plugin.py |
malac.hd.load_model_packages(["my_plugin.py"]) |
MALAC_HD_MODELS=... malac-hd ... |
malac.hd.load_model_packages_from_env() |
For the full CLI reference, see the README.