fhirpathpy 2.0.3__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.
Files changed (55) hide show
  1. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/PKG-INFO +1 -1
  2. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/__init__.py +21 -1
  3. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/__init__.py +2 -1
  4. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/evaluators/__init__.py +6 -4
  5. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/misc.py +6 -1
  6. fhirpathpy-2.1.0/fhirpathpy/engine/invocations/navigation.py +81 -0
  7. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/nodes.py +6 -3
  8. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/pyproject.toml +2 -1
  9. fhirpathpy-2.0.3/fhirpathpy/engine/invocations/navigation.py +0 -55
  10. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/LICENSE.md +0 -0
  11. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/README.md +0 -0
  12. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/__init__.py +0 -0
  13. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/aggregate.py +0 -0
  14. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/collections.py +0 -0
  15. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/combining.py +0 -0
  16. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/constants.py +0 -0
  17. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/datetime.py +0 -0
  18. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/equality.py +0 -0
  19. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/existence.py +0 -0
  20. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/filtering.py +0 -0
  21. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/logic.py +0 -0
  22. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/math.py +0 -0
  23. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/strings.py +0 -0
  24. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/subsetting.py +0 -0
  25. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/invocations/types.py +0 -0
  26. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/engine/util.py +0 -0
  27. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/__init__.py +0 -0
  28. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/choiceTypePaths.json +0 -0
  29. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/path2Type.json +0 -0
  30. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/pathsDefinedElsewhere.json +0 -0
  31. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/dstu2/type2Parent.json +0 -0
  32. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/choiceTypePaths.json +0 -0
  33. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/path2Type.json +0 -0
  34. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/pathsDefinedElsewhere.json +0 -0
  35. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r4/type2Parent.json +0 -0
  36. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/choiceTypePaths.json +0 -0
  37. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/path2Type.json +0 -0
  38. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/pathsDefinedElsewhere.json +0 -0
  39. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/r5/type2Parent.json +0 -0
  40. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/choiceTypePaths.json +0 -0
  41. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/path2Type.json +0 -0
  42. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/pathsDefinedElsewhere.json +0 -0
  43. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/models/stu3/type2Parent.json +0 -0
  44. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/ASTPathListener.py +0 -0
  45. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/FHIRPath.g4 +0 -0
  46. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/README.md +0 -0
  47. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/__init__.py +0 -0
  48. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPath.interp +0 -0
  49. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPath.tokens +0 -0
  50. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathLexer.interp +0 -0
  51. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathLexer.py +0 -0
  52. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathLexer.tokens +0 -0
  53. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathListener.py +0 -0
  54. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/FHIRPathParser.py +0 -0
  55. {fhirpathpy-2.0.3 → fhirpathpy-2.1.0}/fhirpathpy/parser/generated/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fhirpathpy
3
- Version: 2.0.3
3
+ Version: 2.1.0
4
4
  Summary: FHIRPath implementation in Python
5
5
  Keywords: fhir,fhirpath
6
6
  Author-email: "beda.software" <fhirpath@beda.software>
@@ -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.3"
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, data, tp, pr))
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
- print("TRACE:[" + label + "]", str(x))
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):
@@ -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
- ignore = ["E501"]
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