fhirpathpy 2.0.2__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/PKG-INFO +1 -1
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/__init__.py +21 -1
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/__init__.py +2 -1
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/evaluators/__init__.py +6 -4
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/misc.py +6 -1
- fhirpathpy-2.1.0/fhirpathpy/engine/invocations/navigation.py +81 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/nodes.py +7 -4
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/pyproject.toml +2 -1
- fhirpathpy-2.0.2/fhirpathpy/engine/invocations/navigation.py +0 -55
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/LICENSE.md +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/README.md +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/__init__.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/aggregate.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/collections.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/combining.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/constants.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/datetime.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/equality.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/existence.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/filtering.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/logic.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/math.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/strings.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/subsetting.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/types.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/engine/util.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/__init__.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/choiceTypePaths.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/path2Type.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/pathsDefinedElsewhere.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/type2Parent.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/choiceTypePaths.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/path2Type.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/pathsDefinedElsewhere.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/type2Parent.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/choiceTypePaths.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/path2Type.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/pathsDefinedElsewhere.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/type2Parent.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/choiceTypePaths.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/path2Type.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/pathsDefinedElsewhere.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/type2Parent.json +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/ASTPathListener.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/FHIRPath.g4 +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/README.md +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/__init__.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPath.interp +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPath.tokens +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathLexer.interp +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathLexer.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathLexer.tokens +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathListener.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathParser.py +0 -0
- {fhirpathpy-2.0.2 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/__init__.py +0 -0
|
@@ -5,7 +5,7 @@ from fhirpathpy.engine.util import arraify, get_data, set_paths, process_user_in
|
|
|
5
5
|
from fhirpathpy.engine.nodes import FP_Type, ResourceNode
|
|
6
6
|
|
|
7
7
|
__title__ = "fhirpathpy"
|
|
8
|
-
__version__ = "2.0
|
|
8
|
+
__version__ = "2.1.0"
|
|
9
9
|
__author__ = "beda.software"
|
|
10
10
|
__license__ = "MIT"
|
|
11
11
|
__copyright__ = "Copyright 2025 beda.software"
|
|
@@ -35,11 +35,31 @@ def apply_parsed_path(resource, parsedPath, context=None, model=None, options=No
|
|
|
35
35
|
(options or {}).get("userInvocationTable", {})
|
|
36
36
|
),
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
# Add trace callback if provided in options
|
|
40
|
+
if options and "traceFn" in options:
|
|
41
|
+
ctx["traceFn"] = options["traceFn"]
|
|
42
|
+
|
|
38
43
|
node = do_eval(ctx, dataRoot, parsedPath["children"][0])
|
|
39
44
|
|
|
40
45
|
# Resolve any internal "ResourceNode" instances. Continue to let FP_Type
|
|
41
46
|
# subclasses through.
|
|
42
47
|
|
|
48
|
+
if options and options.get("returnRawData", False):
|
|
49
|
+
if isinstance(node, list):
|
|
50
|
+
res = []
|
|
51
|
+
# Filter out intenal representation of primitive extensions
|
|
52
|
+
# even in this raw data mode (as they are not a part of the output)
|
|
53
|
+
for item in node:
|
|
54
|
+
if isinstance(item, ResourceNode):
|
|
55
|
+
if isinstance(item.data, dict):
|
|
56
|
+
keys = list(item.data.keys())
|
|
57
|
+
if keys == ["extension"]:
|
|
58
|
+
continue
|
|
59
|
+
res.append(item)
|
|
60
|
+
return res
|
|
61
|
+
return node
|
|
62
|
+
|
|
43
63
|
def visit(node):
|
|
44
64
|
data = get_data(node)
|
|
45
65
|
|
|
@@ -83,10 +83,11 @@ def doInvoke(ctx, fn_name, data, raw_params):
|
|
|
83
83
|
params = []
|
|
84
84
|
argTypes = invocation["arity"][paramsNumber]
|
|
85
85
|
|
|
86
|
+
thisValue = ctx["$this"] if "$this" in ctx else ctx["dataRoot"]
|
|
86
87
|
for i in range(0, paramsNumber):
|
|
87
88
|
tp = argTypes[i]
|
|
88
89
|
pr = raw_params[i]
|
|
89
|
-
params.append(make_param(ctx,
|
|
90
|
+
params.append(make_param(ctx, thisValue, tp, pr))
|
|
90
91
|
|
|
91
92
|
params.insert(0, data)
|
|
92
93
|
params.insert(0, ctx)
|
|
@@ -174,6 +174,8 @@ def create_reduce_member_invocation(model, key):
|
|
|
174
174
|
def func(acc, res):
|
|
175
175
|
res = nodes.ResourceNode.create_node(res)
|
|
176
176
|
childPath = f"{res.path}.{key}" if res.path else f"_.{key}"
|
|
177
|
+
fullPath = f"{res.propName}.{key}" if res.propName else childPath # The full path to the node (weill evenutally be) e.g. Patient.name[0].given
|
|
178
|
+
fullPath = fullPath.replace("_", "")
|
|
177
179
|
|
|
178
180
|
actualTypes = None
|
|
179
181
|
toAdd = None
|
|
@@ -212,16 +214,16 @@ def create_reduce_member_invocation(model, key):
|
|
|
212
214
|
|
|
213
215
|
if util.is_some(toAdd):
|
|
214
216
|
if isinstance(toAdd, list):
|
|
215
|
-
mapped = [nodes.ResourceNode.create_node(x, childPath) for x in toAdd]
|
|
217
|
+
mapped = [nodes.ResourceNode.create_node(x, childPath, propName=f"{fullPath}[{i}]", index=i) for i, x in enumerate(toAdd)]
|
|
216
218
|
acc = acc + mapped
|
|
217
219
|
else:
|
|
218
|
-
acc.append(nodes.ResourceNode.create_node(toAdd, childPath))
|
|
220
|
+
acc.append(nodes.ResourceNode.create_node(toAdd, childPath, propName=fullPath))
|
|
219
221
|
if util.is_some(toAdd_):
|
|
220
222
|
if isinstance(toAdd_, list):
|
|
221
|
-
mapped = [nodes.ResourceNode.create_node(x, childPath) for x in toAdd_]
|
|
223
|
+
mapped = [nodes.ResourceNode.create_node(x, childPath, propName=f"{fullPath}[{i}]", index=i) for i, x in enumerate(toAdd_)]
|
|
222
224
|
acc = acc + mapped
|
|
223
225
|
else:
|
|
224
|
-
acc.append(nodes.ResourceNode.create_node(toAdd_, childPath))
|
|
226
|
+
acc.append(nodes.ResourceNode.create_node(toAdd_, childPath, propName=fullPath))
|
|
225
227
|
return acc
|
|
226
228
|
|
|
227
229
|
return func
|
|
@@ -21,7 +21,12 @@ def iif_macro(ctx, data, cond, ok, fail=None):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def trace_fn(ctx, x, label=""):
|
|
24
|
-
|
|
24
|
+
# Check if a custom trace callback is provided in the context
|
|
25
|
+
if "traceFn" in ctx and callable(ctx["traceFn"]):
|
|
26
|
+
ctx["traceFn"](label, x)
|
|
27
|
+
else:
|
|
28
|
+
# Fall back to console output if no callback is provided
|
|
29
|
+
print("TRACE:[" + label + "]", str(x))
|
|
25
30
|
return x
|
|
26
31
|
|
|
27
32
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from collections import abc
|
|
2
|
+
from functools import reduce
|
|
3
|
+
import fhirpathpy.engine.util as util
|
|
4
|
+
import fhirpathpy.engine.nodes as nodes
|
|
5
|
+
|
|
6
|
+
create_node = nodes.ResourceNode.create_node
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_reduce_children(ctx, exclude_primitive_extensions):
|
|
10
|
+
model = ctx["model"]
|
|
11
|
+
|
|
12
|
+
def func(acc, res):
|
|
13
|
+
data = util.get_data(res)
|
|
14
|
+
res = create_node(res)
|
|
15
|
+
|
|
16
|
+
if isinstance(data, list):
|
|
17
|
+
data = dict((i, data[i]) for i in range(0, len(data)))
|
|
18
|
+
|
|
19
|
+
if isinstance(data, abc.Mapping):
|
|
20
|
+
for prop in data.keys():
|
|
21
|
+
value = data[prop]
|
|
22
|
+
childPath = ""
|
|
23
|
+
|
|
24
|
+
# extensions shouldn't filter through here, yet they should for descendants?
|
|
25
|
+
# unless this item is the node that is being processed (primitive extension)
|
|
26
|
+
# though if you filter it, descendants will not work too
|
|
27
|
+
if prop.startswith("_") and exclude_primitive_extensions:
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
if res.path is not None:
|
|
31
|
+
childPath = res.path + "." + prop
|
|
32
|
+
|
|
33
|
+
fullPath = f"{res.propName}.{prop}" if res.propName else childPath # The full path to the node (weill evenutally be) e.g. Patient.name[0].given
|
|
34
|
+
fullPath = fullPath.replace("_", "")
|
|
35
|
+
|
|
36
|
+
if prop == "extension":
|
|
37
|
+
childPath = "Extension"
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
isinstance(model, dict)
|
|
41
|
+
and "pathsDefinedElsewhere" in model
|
|
42
|
+
and childPath in model["pathsDefinedElsewhere"]
|
|
43
|
+
):
|
|
44
|
+
childPath = model["pathsDefinedElsewhere"][childPath]
|
|
45
|
+
|
|
46
|
+
childPath = (
|
|
47
|
+
model["path2Type"].get(childPath, childPath)
|
|
48
|
+
if isinstance(model, dict) and "path2Type" in model
|
|
49
|
+
else childPath
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# If the prop tolower ends with the type tolower
|
|
53
|
+
if prop.lower().endswith(childPath.lower()) and len(prop) > len(childPath):
|
|
54
|
+
# Check if the path is actually in the choice types
|
|
55
|
+
altPropName = res.path + "." + prop[:-len(childPath)]
|
|
56
|
+
actualTypes = model["choiceTypePaths"].get(altPropName, [])
|
|
57
|
+
if len(actualTypes) > 0:
|
|
58
|
+
# If it is, we can use it
|
|
59
|
+
fullPath = f"{res.propName}.{prop[:-len(childPath)]}"
|
|
60
|
+
|
|
61
|
+
if isinstance(value, list):
|
|
62
|
+
mapped = [create_node(n, childPath, propName=f"{fullPath}[{i}]", index=i) for i, n in enumerate(value)]
|
|
63
|
+
acc = acc + mapped
|
|
64
|
+
else:
|
|
65
|
+
acc.append(create_node(value, childPath, propName=fullPath))
|
|
66
|
+
return acc
|
|
67
|
+
|
|
68
|
+
return func
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def children(ctx, coll):
|
|
72
|
+
return reduce(create_reduce_children(ctx, True), coll, [])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def descendants(ctx, coll):
|
|
76
|
+
res = []
|
|
77
|
+
ch = reduce(create_reduce_children(ctx, False), coll, [])
|
|
78
|
+
while len(ch) > 0:
|
|
79
|
+
res = res + ch
|
|
80
|
+
ch = reduce(create_reduce_children(ctx, False), ch, [])
|
|
81
|
+
return res
|
|
@@ -8,6 +8,7 @@ import math
|
|
|
8
8
|
import json
|
|
9
9
|
import re
|
|
10
10
|
import time
|
|
11
|
+
from typing import Optional
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
timeRE = (
|
|
@@ -821,7 +822,7 @@ class ResourceNode:
|
|
|
821
822
|
* @param _data additional data stored in a property named with "_" prepended.
|
|
822
823
|
"""
|
|
823
824
|
|
|
824
|
-
def __init__(self, data, path, _data=None):
|
|
825
|
+
def __init__(self, data, path, _data=None, propName=None, index=None):
|
|
825
826
|
"""
|
|
826
827
|
If data is a resource (maybe a contained resource) reset the path
|
|
827
828
|
information to the resource type.
|
|
@@ -832,6 +833,8 @@ class ResourceNode:
|
|
|
832
833
|
self.path = path
|
|
833
834
|
self.data = data
|
|
834
835
|
self._data = _data
|
|
836
|
+
self.propName: Optional[str] = propName
|
|
837
|
+
self.index: Optional[int] = index
|
|
835
838
|
|
|
836
839
|
def __eq__(self, value):
|
|
837
840
|
if isinstance(value, ResourceNode):
|
|
@@ -839,7 +842,7 @@ class ResourceNode:
|
|
|
839
842
|
return self.data == value
|
|
840
843
|
|
|
841
844
|
def __hash__(self):
|
|
842
|
-
data_hash = hash(json.dumps(self.data, sort_keys=True))
|
|
845
|
+
data_hash = hash(json.dumps(self.data, sort_keys=True, default=str))
|
|
843
846
|
path_hash = hash(self.path)
|
|
844
847
|
return hash((data_hash, path_hash))
|
|
845
848
|
|
|
@@ -864,10 +867,10 @@ class ResourceNode:
|
|
|
864
867
|
return json.dumps(self.data)
|
|
865
868
|
|
|
866
869
|
@staticmethod
|
|
867
|
-
def create_node(data, path=None, _data=None):
|
|
870
|
+
def create_node(data, path=None, _data=None, propName=None, index=None):
|
|
868
871
|
if isinstance(data, ResourceNode):
|
|
869
872
|
return data
|
|
870
|
-
return ResourceNode(data, path, _data)
|
|
873
|
+
return ResourceNode(data, path, _data, propName, index)
|
|
871
874
|
|
|
872
875
|
def convert_data(self):
|
|
873
876
|
data = self.data
|
|
@@ -67,7 +67,8 @@ include = ["app/**/*.py", "tests/**/*.py"]
|
|
|
67
67
|
[tool.ruff.lint]
|
|
68
68
|
select = ["B", "F", "I", "E", "UP", "N", "PL", "PERF"]
|
|
69
69
|
# Black is responsible for E501
|
|
70
|
-
|
|
70
|
+
# N803/N806 is not relevant for us because we use camelCase for historical reasons
|
|
71
|
+
ignore = ["E501", "N803", "N806"]
|
|
71
72
|
unfixable = ["F401"]
|
|
72
73
|
|
|
73
74
|
[tool.autohooks]
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
from collections import abc
|
|
2
|
-
from functools import reduce
|
|
3
|
-
import fhirpathpy.engine.util as util
|
|
4
|
-
import fhirpathpy.engine.nodes as nodes
|
|
5
|
-
|
|
6
|
-
create_node = nodes.ResourceNode.create_node
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def create_reduce_children(ctx):
|
|
10
|
-
model = ctx["model"]
|
|
11
|
-
|
|
12
|
-
def func(acc, res):
|
|
13
|
-
data = util.get_data(res)
|
|
14
|
-
res = create_node(res)
|
|
15
|
-
|
|
16
|
-
if isinstance(data, list):
|
|
17
|
-
data = dict((i, data[i]) for i in range(0, len(data)))
|
|
18
|
-
|
|
19
|
-
if isinstance(data, abc.Mapping):
|
|
20
|
-
for prop in data.keys():
|
|
21
|
-
value = data[prop]
|
|
22
|
-
childPath = ""
|
|
23
|
-
|
|
24
|
-
if res.path is not None:
|
|
25
|
-
childPath = res.path + "." + prop
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
isinstance(model, dict)
|
|
29
|
-
and "pathsDefinedElsewhere" in model
|
|
30
|
-
and childPath in model["pathsDefinedElsewhere"]
|
|
31
|
-
):
|
|
32
|
-
childPath = model["pathsDefinedElsewhere"][childPath]
|
|
33
|
-
|
|
34
|
-
if isinstance(value, list):
|
|
35
|
-
mapped = [create_node(n, childPath) for n in value]
|
|
36
|
-
acc = acc + mapped
|
|
37
|
-
else:
|
|
38
|
-
acc.append(create_node(value, childPath))
|
|
39
|
-
return acc
|
|
40
|
-
|
|
41
|
-
return func
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def children(ctx, coll):
|
|
45
|
-
return reduce(create_reduce_children(ctx), coll, [])
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def descendants(ctx, coll):
|
|
49
|
-
res = []
|
|
50
|
-
ch = children(ctx, coll)
|
|
51
|
-
while len(ch) > 0:
|
|
52
|
-
res = res + ch
|
|
53
|
-
ch = children(ctx, ch)
|
|
54
|
-
|
|
55
|
-
return res
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|