Skip to content

Commit

Permalink
Merge branch 'master' into feature/dsl
Browse files Browse the repository at this point in the history
  • Loading branch information
ir4y committed Aug 8, 2023
2 parents 1f19a9a + 7a391e4 commit b8f5e32
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 194 deletions.
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ antlr4-python3-runtime = "==4.8"
pytest = "==7.1.1"
pytest-cov = "==3.0.0"
freezegun = "==1.2.2"
pyyaml = "==5.4"
pyyaml = "==6.0.1"
pre-commit = "~=2.21.0"
black = "*"
305 changes: 135 additions & 170 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion fhirpathpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from fhirpathpy.dsl_impl import DSL

__title__ = "fhirpathpy"
__version__ = "0.1.2"
__version__ = "0.2.2"
__author__ = "beda.software"
__license__ = "MIT"
__copyright__ = "Copyright 2023 beda.software"
Expand Down
6 changes: 4 additions & 2 deletions fhirpathpy/engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,17 @@ def doInvoke(ctx, fn_name, data, raw_params):


def make_param(ctx, parentData, node_type, param):
ctx["currentData"] = parentData

if node_type == "Expr":

def func(data):
return do_eval(ctx, util.arraify(data), param)
ctx["$this"] = util.arraify(data)
return do_eval(ctx, ctx["$this"], param)

return func

if node_type == "AnyAtRoot":
ctx["$this"] = ctx["$this"] if "$this" in ctx else ctx['dataRoot']
return do_eval(ctx, ctx["dataRoot"], param)

if node_type == "Identifier":
Expand All @@ -116,6 +117,7 @@ def func(data):

raise Exception("Expected identifier node, got " + json.dumps(param))

ctx["$this"] = parentData
res = do_eval(ctx, parentData, param)

if node_type == "Any":
Expand Down
35 changes: 21 additions & 14 deletions fhirpathpy/engine/evaluators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def union_expression(ctx, parentData, node):


def this_invocation(ctx, parentData, node):
return util.arraify(ctx["currentData"])
return util.arraify(ctx["$this"])


def op_expression(ctx, parentData, node):
Expand Down Expand Up @@ -182,26 +182,37 @@ def func(acc, res):
actualTypes = model["choiceTypePaths"][childPath]

toAdd = None
toAdd_ = None

if isinstance(actualTypes, list):
# Use actualTypes to find the field's value
for actualType in actualTypes:
field = key + actualType
if isinstance(res.data, (dict, list)) and field in res.data:
toAdd = res.data[field]
childPath = actualType
break
if isinstance(res.data, (dict, list)):
toAdd = res.data.get(field)
toAdd_ = res.data.get(f"_{field}")
if toAdd is not None or toAdd_ is not None:
childPath = actualType
break
else:
if isinstance(res.data, (dict, list)) and key in res.data:
toAdd = res.data[key]
if isinstance(res.data, (dict, list)):
toAdd = res.data.get(key)
toAdd_ = res.data.get(f"_{key}")
if key == 'extension':
childPath = 'Extension'

if util.is_some(toAdd):
if isinstance(toAdd, list):
mapped = [nodes.ResourceNode.create_node(x, childPath) for x in toAdd]
acc = acc + mapped
else:
acc.append(nodes.ResourceNode.create_node(toAdd, childPath))
return acc
if util.is_some(toAdd_):
if isinstance(toAdd_, list):
mapped = [nodes.ResourceNode.create_node(x, childPath) for x in toAdd_]
acc = acc + mapped
else:
acc.append(nodes.ResourceNode.create_node(toAdd_, childPath))
return acc

return func
Expand Down Expand Up @@ -265,9 +276,7 @@ def polarity_expression(ctx, parentData, node):
rtn = engine.do_eval(ctx, parentData, node["children"][0])

if len(rtn) != 1: # not yet in spec, but per Bryn Rhodes
raise Exception(
"Unary " + sign + " can only be applied to an individual number."
)
raise Exception("Unary " + sign + " can only be applied to an individual number.")

if not util.is_number(rtn[0]):
raise Exception("Unary " + sign + " can only be applied to a number.")
Expand Down Expand Up @@ -301,9 +310,7 @@ def polarity_expression(ctx, parentData, node):
# expressions
"PolarityExpression": polarity_expression,
"IndexerExpression": indexer_expression,
"MembershipExpression": alias_op_expression(
{"contains": "containsOp", "in": "inOp"}
),
"MembershipExpression": alias_op_expression({"contains": "containsOp", "in": "inOp"}),
"TermExpression": term_expression,
"UnionExpression": union_expression,
"InvocationExpression": invocation_expression,
Expand Down
5 changes: 3 additions & 2 deletions fhirpathpy/engine/invocations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"count": {"fn": existence.count_fn},
"repeat": {"fn": filtering.repeat_macro, "arity": {1: ["Expr"]}},
"where": {"fn": filtering.where_macro, "arity": {1: ["Expr"]}},
"extension": {"fn": filtering.extension, "arity": {1: ["String"]}},
"select": {"fn": filtering.select_macro, "arity": {1: ["Expr"]}},
"single": {"fn": filtering.single_fn},
"first": {"fn": filtering.first_fn},
Expand All @@ -42,8 +43,8 @@
"toInteger": {"fn": misc.to_integer},
"toDecimal": {"fn": misc.to_decimal},
"toString": {"fn": misc.to_string},
# toDateTime: {fn: misc.toDateTime},
# toTime: {fn: misc.toTime},
"toDateTime": {"fn": misc.to_date_time},
"toTime": {"fn": misc.to_time},
"indexOf": {"fn": strings.index_of, "arity": {1: ["String"]}, "nullable_input": True},
"substring": {
"fn": strings.substring,
Expand Down
6 changes: 4 additions & 2 deletions fhirpathpy/engine/invocations/equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ def datetime_equality(ctx, x, y):
datetime_x = x[0]
datetime_y = y[0]
if type(datetime_x) not in DATETIME_NODES_LIST:
datetime_x = nodes.FP_DateTime(datetime_x) or nodes.FP_Time(datetime_x)
v_x = util.get_data(datetime_x)
datetime_x = nodes.FP_DateTime(v_x) or nodes.FP_Time(v_x)
if type(datetime_y) not in DATETIME_NODES_LIST:
datetime_y = nodes.FP_DateTime(datetime_y) or nodes.FP_Time(datetime_y)
v_y = util.get_data(datetime_y)
datetime_y = nodes.FP_DateTime(v_y) or nodes.FP_Time(v_y)
return datetime_x.equals(datetime_y)


Expand Down
12 changes: 12 additions & 0 deletions fhirpathpy/engine/invocations/filtering.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numbers
import fhirpathpy.engine.util as util
import fhirpathpy.engine.nodes as nodes

# Contains the FHIRPath Filtering and Projection functions.
# (Section 5.2 of the FHIRPath 1.0.0 specification).
Expand Down Expand Up @@ -115,3 +116,14 @@ def check_fhir_type(ctx, x, tp):

def of_type_fn(ctx, coll, tp):
return list(filter(lambda x: check_fhir_type(ctx, util.get_data(x), tp), coll))


def extension(ctx, data, url):
res = []
for d in data:
element = util.get_data(d)
if isinstance(element, dict):
exts = [e for e in element.get("extension", []) if e["url"] == url]
if len(exts) > 0:
res.append(nodes.ResourceNode.create_node(exts[0], "Extension"))
return res
4 changes: 2 additions & 2 deletions fhirpathpy/engine/invocations/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def to_date_time(ctx, coll):
dateTimeObject = nodes.FP_DateTime(value)

if dateTimeObject:
rtn[0] = dateTimeObject
rtn.append(dateTimeObject)

return rtn

Expand All @@ -113,6 +113,6 @@ def to_time(ctx, coll):
timeObject = nodes.FP_Time(value)

if timeObject:
rtn[0] = timeObject
rtn.append(timeObject)

return rtn
108 changes: 108 additions & 0 deletions tests/cases/extensions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
tests:
# https://www.hl7.org/fhir/fhirpath.html#types
- 'group: Extension and id for primitive types':

- desc: '** id for primitive type'
expression: Functions.attrtrue.id = 'someid'
result:
- true

- desc: '** expression with extension for primitive type 1'
inputfile: patient-example.json
expression: Patient.birthDate.extension.where(url = '').empty()
result:
- true

- desc: '** expression with extension for primitive type 2'
inputfile: patient-example.json
expression: >-
Patient.birthDate.extension
.where(url = 'http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.valueDateTime.toDateTime() = @1974-12-25T14:35:45-05:00
result:
- true

- desc: '** expression with extension for primitive type 3'
inputfile: patient-example.json
model: r4
expression: >-
Patient.birthDate.extension
.where(url = 'http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.value = @1974-12-25T14:35:45-05:00
result:
- true

# https://www.hl7.org/fhir/fhirpath.html#functions
- 'group: Additional functions':
- desc: 'extension(url : string) : collection'

# If the url is empty ({ }), the result is empty.
- desc: '** empty url'
inputfile: patient-example.json
expression: Patient.birthDate.extension('').empty()
result:
- true

# If the input collection is empty ({ }), the result is empty.
- desc: '** empty input collection'
inputfile: patient-example.json
expression: >-
Patient.birthDate1
.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime').empty()
result:
- true

- desc: '** expression with extension() for primitive type (without using FHIR model data)'
inputfile: patient-example.json
expression: >-
Patient.birthDate.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.valueDateTime.toDateTime() = @1974-12-25T14:35:45-05:00
result:
- true

- desc: '** expression with extension() for primitive type (without using FHIR model data) when only extension is present'
inputfile: patient-example-2.json
expression: >-
Patient.communication.preferred.extension('test').exists()
result:
- true

- desc: '** expression with extension() for primitive type (using FHIR model data) when only extension is present'
inputfile: patient-example-2.json
model: r4
expression: >-
Patient.communication.preferred.extension('test').value.id
result:
- testing

- desc: '** expression with extension() for primitive type (using FHIR model data)'
inputfile: patient-example.json
model: r4
expression: >-
Patient.birthDate.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.value = @1974-12-25T14:35:45-05:00
result:
- true

- desc: '** value of extension of extension (using FHIR model data)'
model: r4
expression: Functions.attrtrue.extension('url1').extension('url2').value = 'someuri'
result:
- true

- desc: '** id of extension of extension'
expression: Functions.attrtrue.extension('url1').extension('url2').id = 'someid2'
result:
- true

subject:
resourceType: Functions
attrtrue: true
_attrtrue:
id: someid
extension:
- url: url1
extension:
- url: url2
id: someid2
valueUri: someuri
1 change: 1 addition & 0 deletions tests/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def save_to_resources(resources, resource_filename):

save_to_resources(resources, "observation-example.json")
save_to_resources(resources, "patient-example.json")
save_to_resources(resources, "patient-example-2.json")
save_to_resources(resources, "quantity-example.json")
save_to_resources(resources, "questionnaire-example.json")
save_to_resources(resources, "valueset-example-expansion.json")
26 changes: 26 additions & 0 deletions tests/resources/patient-example-2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"resourceType": "Patient",
"communication": [
{
"language": {
"coding": [
{
"system": "urn:ietf:bcp:47",
"code": "nl",
"display": "Dutch"
}
]
},
"_preferred": {
"extension": [
{
"url": "test",
"_valueString": {
"id": "testing"
}
}
]
}
}
]
}
Loading

0 comments on commit b8f5e32

Please sign in to comment.