Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
fa10e21
WIP: Add initial versions of `compile_as_first` and `compile_as_array…
projkov Oct 3, 2025
cbe1c59
Extend `compile_as_first` and `compile_as_array` with support for FHI…
projkov Oct 3, 2025
19dcb7f
Refactor `compile_as_first` and `compile_as_array` to improve resourc…
projkov Oct 3, 2025
036d20b
Ruff fixes
projkov Oct 3, 2025
f056dc9
Add `fhirpy-types-r4b` package dependency to Pipfile and update Pipfi…
projkov Oct 3, 2025
c587402
Refactor `compile_as_first` and `compile_as_array` to use dynamic inp…
projkov Oct 3, 2025
cb2c7d5
Parametrize helper tests for `compile_as_first` and `compile_as_array…
projkov Oct 3, 2025
73c2274
Refactor type annotations for `compile_as_first` and `compile_as_arra…
projkov Oct 3, 2025
6d28a48
Correct type annotations in `compile_as_first` and `compile_as_array`…
projkov Oct 6, 2025
cfaa438
Move `fhirpy-types-r4b` dependency to `[dev-packages]` in Pipfile and…
projkov Oct 6, 2025
4077ea7
Update helper tests to include validation for unsupported resource ty…
projkov Oct 6, 2025
c7d9538
Add more test cases
projkov Oct 14, 2025
b4527e9
Add test case for _test_format_result exception
projkov Oct 14, 2025
1e3c253
Fix typo in exception message assertion in `test_format_result_except…
projkov Oct 14, 2025
dc0c0eb
Refactor exception messages in helper functions and tests for improve…
projkov Oct 14, 2025
896a4de
Refactor `_validate_and_convert_resource` for readability by reorgani…
projkov Oct 14, 2025
e3ed442
Refactor `compile_as_first` and `compile_as_array` to reduce duplicat…
projkov Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ name = "pypi"
[packages]
antlr4-python3-runtime = "~=4.10"
python-dateutil = "~=2.8"
fhirpy-types-r4b = "==0.1.1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to dev deps if it's not direct dependency


[dev-packages]
pytest = "==7.1.1"
Expand Down
148 changes: 147 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 63 additions & 3 deletions fhirpathpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from fhirpathpy.engine.invocations.constants import constants
from fhirpathpy.parser import parse
from typing import Any, Callable, Optional, Type

from fhirpathpy.engine import do_eval
from fhirpathpy.engine.util import arraify, get_data, set_paths, process_user_invocation_table
from fhirpathpy.engine.invocations.constants import constants
from fhirpathpy.engine.nodes import FP_Type, ResourceNode
from fhirpathpy.engine.util import arraify, get_data, process_user_invocation_table, set_paths
from fhirpathpy.parser import parse

__title__ = "fhirpathpy"
__version__ = "2.1.0"
Expand Down Expand Up @@ -124,3 +126,61 @@ def compile(path, model=None, options=None):
For example, you could pass in the result of require("fhirpath/fhir-context/r4")
"""
return set_paths(apply_parsed_path, parsedPath=parse(path), model=model, options=options)


type ResourceType = Any
type ContextType = Optional[dict]


def compile_as_array(
expression: str, input_type: Type, output_type: Type
) -> Callable[[Any], Any]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check output type

path_fn = compile(expression)

def fn(resource: input_type, context: ContextType = None) -> list[output_type]:
return _format_result(
path_fn(_validate_and_convert_resource(resource, input_type), context), output_type, False
)

return fn


def compile_as_first(
expression: str, input_type: Type, output_type: Type
) -> Callable[[Any], Any]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check output type

path_fn = compile(expression)

def fn(resource: input_type, context: ContextType = None) -> output_type:
return _format_result(
path_fn(_validate_and_convert_resource(resource, input_type), context), output_type, True
)

return fn


def _validate_and_convert_resource(resource: ResourceType, input_type: Type) -> dict:
if isinstance(resource, input_type):
if isinstance(resource, dict):
return resource
elif hasattr(resource, "model_dump"):
return resource.model_dump()
else:
raise Exception(f"Don't know how to work with type {type(resource)}")
else:
raise Exception(f"Resource type is {type(resource)}, expected {input_type}")


def _format_result(result: list, output_type: Type, is_first=False) -> Any:
if isinstance(result, list):
if is_first:
if len(result) > 0:
if isinstance(result[0], output_type):
return result[0]
else:
raise Exception(f"Unexpected result type {type(result)}, expected {output_type}")
else:
return None
else:
return result
else:
raise Exception(f"Unexpected result type {type(result)}")
41 changes: 41 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from fhirpathpy import compile_as_first, compile_as_array
import fhirpy_types_r4b as r4b
import pytest

PATIENT_DATA = {
"resourceType": "Patient",
"name": [{"given": ["First", "Middle"], "family": "Last"}],
}

PATIENT_RESOURCE = r4b.Patient(**PATIENT_DATA)

EXPRESSION = "Patient.name.given"

@pytest.mark.parametrize(
("fn", "resource", "path", "input_type", "output_type", "expected"),
[
(compile_as_first, PATIENT_DATA, EXPRESSION, dict, str, "First"),
(compile_as_first, PATIENT_RESOURCE, EXPRESSION, r4b.Patient, str, "First"),
(compile_as_array, PATIENT_DATA, EXPRESSION, dict, list[str], ["First", "Middle"]),
(compile_as_array, PATIENT_RESOURCE, EXPRESSION, r4b.Patient, list[str], ["First", "Middle"]),
],
)
def compile_as_test(fn, resource, path, input_type, output_type, expected):
wrapper_fn = fn(path, input_type, output_type)
assert wrapper_fn(resource) == expected


@pytest.mark.parametrize(
("fn", "resource", "path", "input_type", "output_type", "expected"),
[
(compile_as_first, PATIENT_RESOURCE, EXPRESSION, r4b.Observation, str, "Resource type is <class 'fhirpy_types_r4b.Patient'>, expected <class 'fhirpy_types_r4b.Observation'>"),
(compile_as_array, PATIENT_RESOURCE, EXPRESSION, r4b.Observation, list[str], "Resource type is <class 'fhirpy_types_r4b.Patient'>, expected <class 'fhirpy_types_r4b.Observation'>"),
],
)
def exception_compile_as_test(fn, resource, path, input_type, output_type, expected):
wrapper_fn = fn(path, input_type, output_type)
try:
wrapper_fn(resource)
assert False, "Expected exception not raised"
except Exception as e:
assert str(e) == expected