fhirpathpy 2.2.0__tar.gz → 2.2.2__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.2.0 → fhirpathpy-2.2.2}/PKG-INFO +2 -1
  2. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/__init__.py +1 -1
  3. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/__init__.py +12 -6
  4. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/evaluators/__init__.py +23 -13
  5. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/__init__.py +24 -21
  6. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/collections.py +1 -1
  7. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/combining.py +10 -1
  8. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/equality.py +7 -8
  9. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/existence.py +3 -6
  10. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/filtering.py +7 -9
  11. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/logic.py +10 -10
  12. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/math.py +2 -2
  13. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/misc.py +39 -36
  14. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/navigation.py +11 -6
  15. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/strings.py +3 -2
  16. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/types.py +1 -0
  17. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/nodes.py +21 -18
  18. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/util.py +8 -7
  19. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/__init__.py +2 -3
  20. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/pyproject.toml +4 -2
  21. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/LICENSE.md +0 -0
  22. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/README.md +0 -0
  23. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/aggregate.py +0 -0
  24. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/constants.py +0 -0
  25. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/datetime.py +0 -0
  26. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/engine/invocations/subsetting.py +0 -0
  27. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/dstu2/choiceTypePaths.json +0 -0
  28. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/dstu2/path2Type.json +0 -0
  29. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/dstu2/pathsDefinedElsewhere.json +0 -0
  30. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/dstu2/type2Parent.json +0 -0
  31. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r4/choiceTypePaths.json +0 -0
  32. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r4/path2Type.json +0 -0
  33. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r4/pathsDefinedElsewhere.json +0 -0
  34. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r4/type2Parent.json +0 -0
  35. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r5/choiceTypePaths.json +0 -0
  36. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r5/path2Type.json +0 -0
  37. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r5/pathsDefinedElsewhere.json +0 -0
  38. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/r5/type2Parent.json +0 -0
  39. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/stu3/choiceTypePaths.json +0 -0
  40. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/stu3/path2Type.json +0 -0
  41. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/stu3/pathsDefinedElsewhere.json +0 -0
  42. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/models/stu3/type2Parent.json +0 -0
  43. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/ASTPathListener.py +0 -0
  44. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/FHIRPath.g4 +0 -0
  45. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/README.md +0 -0
  46. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/__init__.py +0 -0
  47. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/FHIRPath.interp +0 -0
  48. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/FHIRPath.tokens +0 -0
  49. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/FHIRPathLexer.interp +0 -0
  50. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/FHIRPathLexer.py +0 -0
  51. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/FHIRPathLexer.tokens +0 -0
  52. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/FHIRPathListener.py +0 -0
  53. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/FHIRPathParser.py +0 -0
  54. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/parser/generated/__init__.py +0 -0
  55. {fhirpathpy-2.2.0 → fhirpathpy-2.2.2}/fhirpathpy/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fhirpathpy
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: FHIRPath implementation in Python
5
5
  Keywords: fhir,fhirpath
6
6
  Author-email: "beda.software" <fhirpath@beda.software>
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Topic :: Internet :: WWW/HTTP
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Classifier: Typing :: Typed
@@ -10,7 +10,7 @@ from fhirpathpy.engine.util import arraify, get_data, process_user_invocation_ta
10
10
  from fhirpathpy.parser import parse
11
11
 
12
12
  __title__ = "fhirpathpy"
13
- __version__ = "2.2.0"
13
+ __version__ = "2.2.2"
14
14
  __author__ = "beda.software"
15
15
  __license__ = "MIT"
16
16
  __copyright__ = "Copyright 2026 beda.software"
@@ -1,11 +1,12 @@
1
1
  import json
2
2
  import numbers
3
- import fhirpathpy.engine.util as util
4
- from fhirpathpy.engine.nodes import TypeInfo
3
+
4
+ from fhirpathpy.engine import util
5
5
  from fhirpathpy.engine.evaluators import evaluators
6
6
  from fhirpathpy.engine.invocations import (
7
7
  invocation_registry as base_invocation_registry,
8
8
  )
9
+ from fhirpathpy.engine.nodes import TypeInfo
9
10
 
10
11
 
11
12
  def check_integer_param(val):
@@ -63,6 +64,14 @@ def doInvoke(ctx, fn_name, data, raw_params):
63
64
  if "nullable_input" in invocation and util.is_nullable(data):
64
65
  return []
65
66
 
67
+ if "variadic" in invocation:
68
+ tp = invocation["variadic"]
69
+ raw_params_list = raw_params if isinstance(raw_params, list) else []
70
+ thisValue = ctx["$this"] if "$this" in ctx else ctx["dataRoot"]
71
+ params = [make_param(ctx, thisValue, tp, pr) for pr in raw_params_list]
72
+ res = invocation["fn"](ctx, util.arraify(data), *params)
73
+ return util.arraify(res)
74
+
66
75
  if "arity" not in invocation:
67
76
  if raw_params is None or util.is_empty(raw_params):
68
77
  res = invocation["fn"](ctx, util.arraify(data))
@@ -160,10 +169,7 @@ def make_param(ctx, parentData, node_type, param):
160
169
 
161
170
  if len(res) > 1:
162
171
  raise Exception(
163
- "Unexpected collection"
164
- + json.dumps(res)
165
- + "; expected singleton of type "
166
- + node_type
172
+ "Unexpected collection" + json.dumps(res) + "; expected singleton of type " + node_type
167
173
  )
168
174
 
169
175
  if len(res) == 0:
@@ -1,12 +1,11 @@
1
+ import json
2
+ import re
1
3
  from collections import abc
2
4
  from decimal import Decimal
3
5
  from functools import reduce
4
6
 
5
- import re
6
- import json
7
- import fhirpathpy.engine as engine
8
- import fhirpathpy.engine.util as util
9
- import fhirpathpy.engine.nodes as nodes
7
+ from fhirpathpy import engine
8
+ from fhirpathpy.engine import nodes, util
10
9
 
11
10
 
12
11
  def boolean_literal(ctx, parentData, node):
@@ -71,7 +70,7 @@ def alias_op_expression(mapFn):
71
70
  def func(ctx, parentData, node):
72
71
  op = node["terminalNodeText"][0]
73
72
 
74
- if not op in mapFn:
73
+ if op not in mapFn:
75
74
  raise Exception("Do not know how to alias " + op + " by " + json.dumps(mapFn))
76
75
 
77
76
  alias = mapFn[op]
@@ -107,7 +106,7 @@ def external_constant_term(ctx, parent_data, node):
107
106
  varName = identifier(ctx, parent_data, ext_identifier)[0].replace("`", "")
108
107
 
109
108
  if varName not in ctx["vars"]:
110
- raise ValueError(f'Attempting to access an undefined environment variable: {varName}')
109
+ raise ValueError(f"Attempting to access an undefined environment variable: {varName}")
111
110
 
112
111
  value = ctx["vars"][varName]
113
112
 
@@ -174,7 +173,9 @@ def create_reduce_member_invocation(model, key):
174
173
  def func(acc, res):
175
174
  res = nodes.ResourceNode.create_node(res)
176
175
  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
176
+ fullPath = (
177
+ f"{res.propName}.{key}" if res.propName else childPath
178
+ ) # The full path to the node (weill evenutally be) e.g. Patient.name[0].given
178
179
  fullPath = fullPath.replace("_", "")
179
180
 
180
181
  actualTypes = None
@@ -202,9 +203,8 @@ def create_reduce_member_invocation(model, key):
202
203
  toAdd_ = res.data.get(f"_{key}")
203
204
  if key == "extension":
204
205
  childPath = "Extension"
205
- else:
206
- if key == "length":
207
- toAdd = len(res.data)
206
+ elif key == "length":
207
+ toAdd = len(res.data)
208
208
 
209
209
  childPath = (
210
210
  model["path2Type"].get(childPath, childPath)
@@ -214,13 +214,23 @@ def create_reduce_member_invocation(model, key):
214
214
 
215
215
  if util.is_some(toAdd):
216
216
  if isinstance(toAdd, list):
217
- mapped = [nodes.ResourceNode.create_node(x, childPath, propName=f"{fullPath}[{i}]", index=i) for i, x in enumerate(toAdd)]
217
+ mapped = [
218
+ nodes.ResourceNode.create_node(
219
+ x, childPath, propName=f"{fullPath}[{i}]", index=i
220
+ )
221
+ for i, x in enumerate(toAdd)
222
+ ]
218
223
  acc = acc + mapped
219
224
  else:
220
225
  acc.append(nodes.ResourceNode.create_node(toAdd, childPath, propName=fullPath))
221
226
  if util.is_some(toAdd_):
222
227
  if isinstance(toAdd_, list):
223
- mapped = [nodes.ResourceNode.create_node(x, childPath, propName=f"{fullPath}[{i}]", index=i) for i, x in enumerate(toAdd_)]
228
+ mapped = [
229
+ nodes.ResourceNode.create_node(
230
+ x, childPath, propName=f"{fullPath}[{i}]", index=i
231
+ )
232
+ for i, x in enumerate(toAdd_)
233
+ ]
224
234
  acc = acc + mapped
225
235
  else:
226
236
  acc.append(nodes.ResourceNode.create_node(toAdd_, childPath, propName=fullPath))
@@ -1,19 +1,21 @@
1
1
  from decimal import Decimal
2
2
 
3
- import fhirpathpy.engine.invocations.collections as collections
4
- import fhirpathpy.engine.invocations.existence as existence
5
- import fhirpathpy.engine.invocations.filtering as filtering
6
- import fhirpathpy.engine.invocations.subsetting as subsetting
7
- import fhirpathpy.engine.invocations.strings as strings
8
- import fhirpathpy.engine.invocations.navigation as navigation
9
- import fhirpathpy.engine.invocations.combining as combining
10
- import fhirpathpy.engine.invocations.math as math
11
- import fhirpathpy.engine.invocations.misc as misc
12
- import fhirpathpy.engine.invocations.equality as equality
13
- import fhirpathpy.engine.invocations.logic as logic
14
- import fhirpathpy.engine.invocations.datetime as datetime
15
- import fhirpathpy.engine.invocations.types as types
16
- import fhirpathpy.engine.invocations.aggregate as aggregate
3
+ from fhirpathpy.engine.invocations import (
4
+ aggregate,
5
+ collections,
6
+ combining,
7
+ datetime,
8
+ equality,
9
+ existence,
10
+ filtering,
11
+ logic,
12
+ math,
13
+ misc,
14
+ navigation,
15
+ strings,
16
+ subsetting,
17
+ types,
18
+ )
17
19
  from fhirpathpy.engine.nodes import FP_DateTime, FP_Quantity, FP_Time
18
20
 
19
21
  invocation_registry = {
@@ -21,8 +23,8 @@ invocation_registry = {
21
23
  "not": {"fn": existence.not_fn},
22
24
  "exists": {"fn": existence.exists_macro, "arity": {0: [], 1: ["Expr"]}},
23
25
  "all": {"fn": existence.all_macro, "arity": {1: ["Expr"]}},
24
- "union": {"fn": combining.union_op, "arity": {1: ["AnyAtRoot"]}},
25
- "exclude": {"fn": combining.exclude_fn, "arity": {1: ["AnyAtRoot"]}},
26
+ "union": {"fn": combining.union_op, "arity": {1: ["AnyAtRoot"]}},
27
+ "exclude": {"fn": combining.exclude_fn, "arity": {1: ["AnyAtRoot"]}},
26
28
  "allTrue": {"fn": existence.all_true_fn},
27
29
  "anyTrue": {"fn": existence.any_true_fn},
28
30
  "allFalse": {"fn": existence.all_false_fn},
@@ -48,6 +50,7 @@ invocation_registry = {
48
50
  "skip": {"fn": filtering.skip_fn, "arity": {1: ["Integer"]}},
49
51
  "intersect": {"fn": subsetting.intersect_fn, "arity": {1: ["AnyAtRoot"]}},
50
52
  "combine": {"fn": combining.combine_fn, "arity": {1: ["AnyAtRoot"]}},
53
+ "coalesce": {"fn": combining.coalesce_fn, "variadic": "Expr"},
51
54
  "iif": {"fn": misc.iif_macro, "arity": {2: ["Expr", "Expr"], 3: ["Expr", "Expr", "Expr"]}},
52
55
  "trace": {"fn": misc.trace_fn, "arity": {0: [], 1: ["String"]}},
53
56
  "toInteger": {"fn": misc.to_integer},
@@ -109,8 +112,8 @@ invocation_registry = {
109
112
  ">=": {"fn": equality.gte, "arity": {2: ["Any", "Any"]}, "nullable": True},
110
113
  "containsOp": {"fn": collections.contains, "arity": {2: ["Any", "Any"]}},
111
114
  "inOp": {"fn": collections.inn, "arity": {2: ["Any", "Any"]}},
112
- "isOp": {"fn": types.is_fn, "arity": {2: ["Any", "TypeSpecifier"]}},
113
- "asOp": {"fn": types.as_fn, "arity": {2: ["Any", "TypeSpecifier"]}},
115
+ "isOp": {"fn": types.is_fn, "arity": {2: ["Any", "TypeSpecifier"]}},
116
+ "asOp": {"fn": types.as_fn, "arity": {2: ["Any", "TypeSpecifier"]}},
114
117
  "&": {"fn": math.amp, "arity": {2: ["String", "String"]}},
115
118
  "+": {"fn": math.plus, "arity": {2: ["Any", "Any"]}, "nullable": True},
116
119
  "-": {"fn": math.minus, "arity": {2: ["Any", "Any"]}, "nullable": True},
@@ -127,10 +130,10 @@ invocation_registry = {
127
130
  "min": {"fn": aggregate.min_fn},
128
131
  "max": {"fn": aggregate.max_fn},
129
132
  "aggregate": {"fn": aggregate.aggregate_macro, "arity": {1: ["Expr"], 2: ["Expr", "Any"]}},
130
- "convertsToBoolean": {"fn": misc.create_converts_to_fn(misc.to_boolean, 'bool')},
131
- "convertsToInteger": {"fn": misc.create_converts_to_fn(misc.to_integer, 'int')},
133
+ "convertsToBoolean": {"fn": misc.create_converts_to_fn(misc.to_boolean, "bool")},
134
+ "convertsToInteger": {"fn": misc.create_converts_to_fn(misc.to_integer, "int")},
132
135
  "convertsToDecimal": {"fn": misc.create_converts_to_fn(misc.to_decimal, Decimal)},
133
- "convertsToString": {"fn": misc.create_converts_to_fn(misc.to_string, 'str')},
136
+ "convertsToString": {"fn": misc.create_converts_to_fn(misc.to_string, "str")},
134
137
  "convertsToDate": {"fn": misc.create_converts_to_fn(misc.to_date, FP_DateTime)},
135
138
  "convertsToDateTime": {"fn": misc.create_converts_to_fn(misc.to_date_time, FP_DateTime)},
136
139
  "convertsToTime": {"fn": misc.create_converts_to_fn(misc.to_time, FP_Time)},
@@ -1,4 +1,4 @@
1
- """
1
+ """
2
2
  This file holds code to hande the FHIRPath Math functions.
3
3
  """
4
4
 
@@ -1,4 +1,5 @@
1
- import fhirpathpy.engine.invocations.existence as existence
1
+ from fhirpathpy.engine import util
2
+ from fhirpathpy.engine.invocations import existence
2
3
 
3
4
  """
4
5
  This file holds code to hande the FHIRPath Combining functions
@@ -15,3 +16,11 @@ def combine_fn(ctx, coll1, coll2):
15
16
 
16
17
  def exclude_fn(ctx, coll1, coll2):
17
18
  return [element for element in coll1 if element not in coll2]
19
+
20
+
21
+ def coalesce_fn(ctx, data, *exprs):
22
+ for expr in exprs:
23
+ result = expr(data)
24
+ if not util.is_empty(result):
25
+ return result
26
+ return []
@@ -1,8 +1,8 @@
1
+ import json
1
2
  from collections import abc
2
3
  from decimal import Decimal
3
- import json
4
- import fhirpathpy.engine.util as util
5
- import fhirpathpy.engine.nodes as nodes
4
+
5
+ from fhirpathpy.engine import nodes, util
6
6
 
7
7
  """
8
8
  This file holds code to hande the FHIRPath Math functions.
@@ -81,7 +81,7 @@ def equivalence(ctx, x, y):
81
81
  if isinstance(x_val, nodes.FP_Quantity) and isinstance(y_val, nodes.FP_Quantity):
82
82
  return x_val.deep_equal(y_val)
83
83
 
84
- if isinstance(a, (abc.Mapping, list)) and isinstance(b, (abc.Mapping, list)):
84
+ if isinstance(a, abc.Mapping | list) and isinstance(b, abc.Mapping | list):
85
85
 
86
86
  def deep_equal(a, b):
87
87
  if isinstance(a, abc.Mapping) and isinstance(b, abc.Mapping):
@@ -94,7 +94,7 @@ def equivalence(ctx, x, y):
94
94
  )
95
95
  elif isinstance(a, str) and isinstance(b, str):
96
96
  return normalize_string(a) == normalize_string(b)
97
- elif isinstance(a, (int, float)) and isinstance(b, (int, float)):
97
+ elif isinstance(a, int | float) and isinstance(b, int | float):
98
98
  return abs(a - b) < 0.5
99
99
  else:
100
100
  return a == b
@@ -190,12 +190,11 @@ def typecheck(a, b):
190
190
  if lClass != rClass and not areNumbers:
191
191
  d = None
192
192
 
193
- # TODO refactor
194
- if lClass == str and (rClass == nodes.FP_DateTime or rClass == nodes.FP_Time):
193
+ if lClass is str and (rClass in (nodes.FP_DateTime, nodes.FP_Time)):
195
194
  d = nodes.FP_DateTime(a) or nodes.FP_Time(a)
196
195
  if d is not None:
197
196
  rtn = [d, b]
198
- elif rClass == str and (lClass == nodes.FP_DateTime or lClass == nodes.FP_Time):
197
+ elif rClass is str and (lClass in (nodes.FP_DateTime, nodes.FP_Time)):
199
198
  d = nodes.FP_DateTime(b) or nodes.FP_Time(b)
200
199
  if d is not None:
201
200
  rtn = [a, d]
@@ -1,10 +1,7 @@
1
1
  from decimal import Decimal
2
- from fhirpathpy.engine.invocations import misc
3
- from fhirpathpy.engine.invocations.misc import to_boolean
4
- import fhirpathpy.engine.util as util
5
- import fhirpathpy.engine.nodes as nodes
6
- import fhirpathpy.engine.invocations.filtering as filtering
7
2
 
3
+ from fhirpathpy.engine import nodes, util
4
+ from fhirpathpy.engine.invocations import filtering, misc
8
5
 
9
6
  """
10
7
  This file holds code to hande the FHIRPath Existence functions
@@ -54,7 +51,7 @@ def all_macro(ctx, colls, expr):
54
51
 
55
52
  def extract_boolean_value(data):
56
53
  value = util.get_data(data)
57
- if type(value) != bool:
54
+ if type(value) is not bool:
58
55
  raise Exception("Found type '" + type(data) + "' but was expecting bool")
59
56
  return value
60
57
 
@@ -1,8 +1,6 @@
1
1
  from collections import abc
2
- from decimal import Decimal
3
- import numbers
4
- import fhirpathpy.engine.util as util
5
- import fhirpathpy.engine.nodes as nodes
2
+
3
+ from fhirpathpy.engine import nodes, util
6
4
 
7
5
  # Contains the FHIRPath Filtering and Projection functions.
8
6
  # (Section 5.2 of the FHIRPath 1.0.0 specification).
@@ -54,18 +52,18 @@ def repeat_macro(ctx, data, expr):
54
52
  res = []
55
53
  items = data
56
54
 
57
- next = None
55
+ next_item = None
58
56
  lres = None
59
57
 
60
58
  uniq = set()
61
59
 
62
60
  while len(items) != 0:
63
- next = items[0]
61
+ next_item = items[0]
64
62
  items = items[1:]
65
- lres = [l for l in expr(next) if l not in uniq]
63
+ lres = [elem for elem in expr(next_item) if elem not in uniq]
66
64
  if len(lres) > 0:
67
- for l in lres:
68
- uniq.add(l)
65
+ for elem in lres:
66
+ uniq.add(elem)
69
67
  res = res + lres
70
68
  items = items + lres
71
69
 
@@ -1,13 +1,13 @@
1
1
  def or_op(ctx, a, b):
2
2
  if isinstance(b, list):
3
- if a == True:
3
+ if a is True:
4
4
  return True
5
- if a == False:
5
+ if a is False:
6
6
  return []
7
7
  if isinstance(a, list):
8
8
  return []
9
9
  if isinstance(a, list):
10
- if b == True:
10
+ if b is True:
11
11
  return True
12
12
  return []
13
13
 
@@ -16,15 +16,15 @@ def or_op(ctx, a, b):
16
16
 
17
17
  def and_op(ctx, a, b):
18
18
  if isinstance(b, list):
19
- if a == True:
19
+ if a is True:
20
20
  return []
21
- if a == False:
21
+ if a is False:
22
22
  return False
23
23
  if isinstance(a, list):
24
24
  return []
25
25
 
26
26
  if isinstance(a, list):
27
- if b == True:
27
+ if b is True:
28
28
  return []
29
29
  return False
30
30
 
@@ -42,19 +42,19 @@ def xor_op(ctx, a, b):
42
42
 
43
43
  def implies_op(ctx, a, b):
44
44
  if isinstance(b, list):
45
- if a == True:
45
+ if a is True:
46
46
  return []
47
- if a == False:
47
+ if a is False:
48
48
  return True
49
49
  if isinstance(a, list):
50
50
  return []
51
51
 
52
52
  if isinstance(a, list):
53
- if b == True:
53
+ if b is True:
54
54
  return True
55
55
  return []
56
56
 
57
- if a == False:
57
+ if a is False:
58
58
  return True
59
59
 
60
60
  return a and b
@@ -1,7 +1,7 @@
1
1
  from decimal import Decimal
2
+
3
+ from fhirpathpy.engine import nodes, util
2
4
  from fhirpathpy.engine.invocations.equality import remove_duplicate_extension
3
- import fhirpathpy.engine.util as util
4
- import fhirpathpy.engine.nodes as nodes
5
5
 
6
6
  """
7
7
  Adds the math functions to the given FHIRPath engine.
@@ -1,8 +1,7 @@
1
1
  import re
2
2
  from decimal import Decimal
3
3
 
4
- import fhirpathpy.engine.util as util
5
- import fhirpathpy.engine.nodes as nodes
4
+ from fhirpathpy.engine import nodes, util
6
5
 
7
6
  # This file holds code to hande the FHIRPath Existence functions (5.1 in the
8
7
  # specification).
@@ -36,10 +35,10 @@ def to_integer(ctx, coll):
36
35
 
37
36
  value = util.get_data(coll[0])
38
37
 
39
- if value == False:
38
+ if value is False:
40
39
  return 0
41
40
 
42
- if value == True:
41
+ if value is True:
43
42
  return 1
44
43
 
45
44
  if util.is_number(value):
@@ -72,7 +71,7 @@ def to_quantity(ctx, coll, to_unit=None):
72
71
  v = util.val_data_converted(coll[0])
73
72
  quantity_regex_res = None
74
73
 
75
- if isinstance(v, (int, Decimal)):
74
+ if isinstance(v, int | Decimal):
76
75
  result = nodes.FP_Quantity(v, "'1'")
77
76
  elif isinstance(v, nodes.FP_Quantity):
78
77
  result = v
@@ -130,62 +129,63 @@ def to_string(ctx, coll):
130
129
 
131
130
  def to_date_time(ctx, coll):
132
131
  ln = len(coll)
133
- rtn = []
134
132
  if ln > 1:
135
133
  raise Exception("to_date_time called for a collection of length " + str(ln))
136
134
 
137
- if ln == 1:
138
- value = util.get_data(coll[0])
135
+ if ln != 1:
136
+ return []
139
137
 
140
- dateTimeObject = nodes.FP_DateTime(value)
138
+ value = util.get_data(coll[0])
139
+ dateTimeObject = nodes.FP_DateTime(value)
141
140
 
142
- if dateTimeObject:
143
- rtn.append(dateTimeObject)
141
+ if not dateTimeObject:
142
+ return []
144
143
 
145
- return util.get_data(rtn[0])
144
+ return util.get_data(dateTimeObject)
146
145
 
147
146
 
148
147
  def to_time(ctx, coll):
149
148
  ln = len(coll)
150
- rtn = []
151
149
  if ln > 1:
152
150
  raise Exception("to_time called for a collection of length " + str(ln))
153
151
 
154
- if ln == 1:
155
- value = util.get_data(coll[0])
152
+ if ln != 1:
153
+ return []
156
154
 
157
- timeObject = nodes.FP_Time(value)
155
+ value = util.get_data(coll[0])
156
+ timeObject = nodes.FP_Time(value)
158
157
 
159
- if timeObject:
160
- rtn.append(timeObject)
158
+ if not timeObject:
159
+ return []
161
160
 
162
- return util.get_data(rtn[0])
161
+ return util.get_data(timeObject)
163
162
 
164
163
 
165
164
  def to_date(ctx, coll):
166
165
  ln = len(coll)
167
- rtn = []
168
-
169
166
  if ln > 1:
170
167
  raise Exception("to_date called for a collection of length " + str(ln))
171
168
 
172
- if ln == 1:
173
- value = util.get_data(coll[0])
169
+ if ln != 1:
170
+ return []
174
171
 
175
- dateObject = nodes.FP_DateTime(value)
172
+ value = util.get_data(coll[0])
173
+ dateObject = nodes.FP_DateTime(value)
176
174
 
177
- if dateObject:
178
- rtn.append(dateObject)
175
+ if not dateObject:
176
+ return []
179
177
 
180
- return util.get_data(rtn[0])
178
+ return util.get_data(dateObject)
181
179
 
182
180
 
183
181
  def create_converts_to_fn(to_function, _type):
184
182
  if isinstance(_type, str):
183
+
185
184
  def in_function(ctx, coll):
186
185
  if len(coll) != 1:
187
186
  return []
188
187
  return type(to_function(ctx, coll)).__name__ == _type
188
+
189
189
  return in_function
190
190
 
191
191
  def in_function(ctx, coll):
@@ -198,8 +198,8 @@ def create_converts_to_fn(to_function, _type):
198
198
 
199
199
 
200
200
  def to_boolean(ctx, coll):
201
- true_strings = ['true', 't', 'yes', 'y', '1', '1.0']
202
- false_strings = ['false', 'f', 'no', 'n', '0', '0.0']
201
+ true_strings = ["true", "t", "yes", "y", "1", "1.0"]
202
+ false_strings = ["false", "f", "no", "n", "0", "0.0"]
203
203
 
204
204
  if len(coll) != 1:
205
205
  return []
@@ -209,10 +209,10 @@ def to_boolean(ctx, coll):
209
209
 
210
210
  if var_type == "bool":
211
211
  return val
212
- elif var_type == "int" or var_type == "float":
213
- if val == 1 or val == 1.0:
212
+ elif var_type in ("int", "float"):
213
+ if val in (1, 1.0):
214
214
  return True
215
- elif val == 0 or val == 0.0:
215
+ elif val in (0, 0.0):
216
216
  return False
217
217
  elif var_type == "str":
218
218
  lower_case_var = val.lower()
@@ -231,25 +231,28 @@ def boolean_singleton(coll):
231
231
  elif len(coll) == 1:
232
232
  return True
233
233
 
234
+
234
235
  def string_singleton(coll):
235
236
  d = util.get_data(coll[0])
236
237
  if isinstance(d, str):
237
238
  return d
238
239
 
240
+
239
241
  singleton_eval_by_type = {
240
242
  "Boolean": boolean_singleton,
241
243
  "String": string_singleton,
242
244
  }
243
245
 
246
+
244
247
  def singleton(coll, type):
245
248
  if len(coll) > 1:
246
- raise Exception("Unexpected collection {coll}; expected singleton of type {type}".format(coll=coll, type=type))
249
+ raise Exception(f"Unexpected collection {coll}; expected singleton of type {type}")
247
250
  elif len(coll) == 0:
248
251
  return []
249
252
  to_singleton = singleton_eval_by_type[type]
250
253
  if to_singleton:
251
254
  val = to_singleton(coll)
252
- if (val is not None):
255
+ if val is not None:
253
256
  return val
254
- raise Exception("Expected {type}, but got: {coll}".format(type=type.lower(), coll=coll))
255
- raise Exception("Not supported type {}".format(type))
257
+ raise Exception(f"Expected {type.lower()}, but got: {coll}")
258
+ raise Exception(f"Not supported type {type}")
@@ -1,7 +1,7 @@
1
1
  from collections import abc
2
2
  from functools import reduce
3
- import fhirpathpy.engine.util as util
4
- import fhirpathpy.engine.nodes as nodes
3
+
4
+ from fhirpathpy.engine import nodes, util
5
5
 
6
6
  create_node = nodes.ResourceNode.create_node
7
7
 
@@ -30,7 +30,9 @@ def create_reduce_children(ctx, exclude_primitive_extensions):
30
30
  if res.path is not None:
31
31
  childPath = res.path + "." + prop
32
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
33
+ fullPath = (
34
+ f"{res.propName}.{prop}" if res.propName else childPath
35
+ ) # The full path to the node (weill evenutally be) e.g. Patient.name[0].given
34
36
  fullPath = fullPath.replace("_", "")
35
37
 
36
38
  if prop == "extension":
@@ -52,14 +54,17 @@ def create_reduce_children(ctx, exclude_primitive_extensions):
52
54
  # If the prop tolower ends with the type tolower
53
55
  if prop.lower().endswith(childPath.lower()) and len(prop) > len(childPath):
54
56
  # Check if the path is actually in the choice types
55
- altPropName = res.path + "." + prop[:-len(childPath)]
57
+ altPropName = res.path + "." + prop[: -len(childPath)]
56
58
  actualTypes = model["choiceTypePaths"].get(altPropName, [])
57
59
  if len(actualTypes) > 0:
58
60
  # If it is, we can use it
59
- fullPath = f"{res.propName}.{prop[:-len(childPath)]}"
61
+ fullPath = f"{res.propName}.{prop[: -len(childPath)]}"
60
62
 
61
63
  if isinstance(value, list):
62
- mapped = [create_node(n, childPath, propName=f"{fullPath}[{i}]", index=i) for i, n in enumerate(value)]
64
+ mapped = [
65
+ create_node(n, childPath, propName=f"{fullPath}[{i}]", index=i)
66
+ for i, n in enumerate(value)
67
+ ]
63
68
  acc = acc + mapped
64
69
  else:
65
70
  acc.append(create_node(value, childPath, propName=fullPath))
@@ -1,12 +1,13 @@
1
1
  import base64
2
2
  import re
3
- import fhirpathpy.engine.util as util
3
+
4
+ from fhirpathpy.engine import util
4
5
 
5
6
 
6
7
  def ensure_string_singleton(x):
7
8
  if len(x) == 1:
8
9
  d = util.get_data(x[0])
9
- if type(d) == str:
10
+ if isinstance(d, str):
10
11
  return d
11
12
  raise Exception("Expected string, but got " + str(d))
12
13
 
@@ -1,5 +1,6 @@
1
1
  from fhirpathpy.engine.nodes import TypeInfo
2
2
 
3
+
3
4
  def type_fn(ctx, coll):
4
5
  return [TypeInfo.from_value(value).__dict__ for value in coll]
5
6
 
@@ -1,15 +1,13 @@
1
- from collections import abc
2
1
  import copy
3
- from datetime import datetime, timedelta, timezone
4
- from dateutil.relativedelta import relativedelta
5
- from dateutil import parser, tz
6
- from decimal import ROUND_HALF_UP, ROUND_UP, Decimal
7
- import math
8
2
  import json
3
+ import math
9
4
  import re
10
- import time
11
- from typing import Optional
5
+ from collections import abc
6
+ from datetime import datetime, timedelta, timezone
7
+ from decimal import ROUND_HALF_UP, ROUND_UP, Decimal
12
8
 
9
+ from dateutil import parser, tz
10
+ from dateutil.relativedelta import relativedelta
13
11
 
14
12
  timeRE = (
15
13
  r"^T?([0-9]{2})(?::([0-9]{2}))?(?::([0-9]{2}))?(?:\.([0-9]+))?(Z|(\+|-)[0-9]{2}(:[0-9]{2})?)?$"
@@ -258,8 +256,9 @@ class FP_Quantity(FP_Type):
258
256
  fromUnit in FP_Quantity._m_cm_mm_conversion_factor
259
257
  or toUnit in FP_Quantity._m_cm_mm_conversion_factor
260
258
  ):
261
- from_magnitude, to_magnitude = Decimal(from_m_cm_mm_magnitude), Decimal(
262
- to_m_cm_mm_magnitude
259
+ from_magnitude, to_magnitude = (
260
+ Decimal(from_m_cm_mm_magnitude),
261
+ Decimal(to_m_cm_mm_magnitude),
263
262
  )
264
263
  return FP_Quantity(from_magnitude * value / to_magnitude, toUnit)
265
264
 
@@ -343,7 +342,7 @@ class FP_TimeBase(FP_Type):
343
342
  2012-01 = 2011 returns false
344
343
  2012-01 ~ 2012 returns false
345
344
  """
346
- if type(otherDateTime) != type(self):
345
+ if type(otherDateTime) is not type(self):
347
346
  return False
348
347
 
349
348
  thisdt_list = self._getMatchAsList()
@@ -355,7 +354,7 @@ class FP_TimeBase(FP_Type):
355
354
  indices_to_remove = [
356
355
  i
357
356
  for i in range(len(normalized_thisdt_list))
358
- if normalized_thisdt_list[i] == normalized_otherdt_list[i] == None
357
+ if normalized_thisdt_list[i] is None and normalized_otherdt_list[i] is None
359
358
  ]
360
359
 
361
360
  for i in reversed(indices_to_remove):
@@ -413,7 +412,7 @@ class FP_TimeBase(FP_Type):
413
412
  ]
414
413
 
415
414
  def compare(self, otherDateTime):
416
- if type(otherDateTime) != type(self):
415
+ if type(otherDateTime) is not type(self):
417
416
  raise TypeError
418
417
 
419
418
  thisDateTimeList = self._getMatchAsList()
@@ -424,7 +423,7 @@ class FP_TimeBase(FP_Type):
424
423
  indices_to_remove = [
425
424
  i
426
425
  for i in range(len(normalized_thisdt_list))
427
- if normalized_thisdt_list[i] == normalized_otherdt_list[i] == None
426
+ if normalized_thisdt_list[i] is None and normalized_otherdt_list[i] is None
428
427
  ]
429
428
  for i in reversed(indices_to_remove):
430
429
  del normalized_thisdt_list[i]
@@ -598,7 +597,7 @@ class FP_Time(FP_TimeBase):
598
597
  if not re.match(timeRE, dateStr):
599
598
  return None
600
599
 
601
- return super(FP_Time, cls).__new__(cls)
600
+ return super().__new__(cls)
602
601
 
603
602
  def __init__(self, timeStr):
604
603
  self.asStr = timeStr if isinstance(timeStr, str) else None
@@ -647,6 +646,8 @@ class FP_Time(FP_TimeBase):
647
646
  return time_str
648
647
  return self.asStr
649
648
 
649
+ __hash__ = None
650
+
650
651
  def __eq__(self, other):
651
652
  if isinstance(other, str):
652
653
  return self.getTimeMatchStr()
@@ -705,7 +706,7 @@ class FP_DateTime(FP_TimeBase):
705
706
  if not re.match(dateTimeRE, dateStr):
706
707
  return None
707
708
 
708
- return super(FP_DateTime, cls).__new__(cls)
709
+ return super().__new__(cls)
709
710
 
710
711
  def __init__(self, dateStr):
711
712
  self.asStr = dateStr if isinstance(dateStr, str) else None
@@ -735,6 +736,8 @@ class FP_DateTime(FP_TimeBase):
735
736
  return iso_str
736
737
  return self.asStr
737
738
 
739
+ __hash__ = None
740
+
738
741
  def __eq__(self, other):
739
742
  if isinstance(other, str):
740
743
  return self.getDateTimeMatchStr()
@@ -833,8 +836,8 @@ class ResourceNode:
833
836
  self.path = path
834
837
  self.data = data
835
838
  self._data = _data
836
- self.propName: Optional[str] = propName
837
- self.index: Optional[int] = index
839
+ self.propName: str | None = propName
840
+ self.index: int | None = index
838
841
 
839
842
  def __eq__(self, value):
840
843
  if isinstance(value, ResourceNode):
@@ -1,8 +1,9 @@
1
- from decimal import Decimal
2
1
  import json
3
2
  from collections import OrderedDict
3
+ from decimal import Decimal
4
4
  from functools import reduce
5
- from fhirpathpy.engine.nodes import ResourceNode, FP_Quantity
5
+
6
+ from fhirpathpy.engine.nodes import FP_Quantity, ResourceNode
6
7
 
7
8
 
8
9
  class set_paths:
@@ -13,9 +14,7 @@ class set_paths:
13
14
  self.options = options
14
15
 
15
16
  def __call__(self, resource, context=None):
16
- return self.func(
17
- resource, self.parsedPath, context or {}, self.model, self.options
18
- )
17
+ return self.func(resource, self.parsedPath, context or {}, self.model, self.options)
19
18
 
20
19
 
21
20
  def get_data(value):
@@ -41,7 +40,7 @@ def parse_value(value):
41
40
 
42
41
 
43
42
  def is_number(value):
44
- return isinstance(value, (int, Decimal, complex)) and not isinstance(value, bool)
43
+ return isinstance(value, int | Decimal | complex) and not isinstance(value, bool)
45
44
 
46
45
 
47
46
  def is_capitalized(x):
@@ -61,7 +60,9 @@ def is_nullable(x):
61
60
 
62
61
 
63
62
  def is_true(x):
64
- return x == True or isinstance(x, list) and len(x) == 1 and x[0] == True
63
+ # Use == not is: mid-pipeline values are often ResourceNode wrappers; __eq__
64
+ # compares .data, while `is True` does not (see fhirpathpy 2.2.1 regression).
65
+ return x == True or isinstance(x, list) and len(x) == 1 and x[0] == True # noqa: E712
65
66
 
66
67
 
67
68
  def arraify(x, instead_none=None):
@@ -1,8 +1,7 @@
1
- import os
2
- from collections import defaultdict
3
1
  import json
4
-
2
+ import os
5
3
  import pathlib
4
+ from collections import defaultdict
6
5
 
7
6
  current_dir = pathlib.Path(__file__).parent.resolve()
8
7
 
@@ -30,6 +30,7 @@ classifiers = [
30
30
  "Programming Language :: Python :: 3.11",
31
31
  "Programming Language :: Python :: 3.12",
32
32
  "Programming Language :: Python :: 3.13",
33
+ "Programming Language :: Python :: 3.14",
33
34
  "Topic :: Internet :: WWW/HTTP",
34
35
  "Topic :: Software Development :: Libraries :: Python Modules",
35
36
  "Typing :: Typed",
@@ -50,11 +51,12 @@ Changelog = "https://github.com/beda-software/fhirpath-py/blob/master/CHANGELOG.
50
51
  target-version = "py310"
51
52
  line-length = 100
52
53
  include = ["fhirpathpy/**/*.py", "tests/**/*.py"]
54
+ exclude = ["fhirpathpy/parser/**/*.py"]
53
55
 
54
56
  [tool.ruff.lint]
55
57
  select = ["B", "F", "I", "E", "UP", "N", "PL", "PERF"]
56
- # N803/N806 is not relevant for us because we use camelCase for historical reasons
57
- ignore = ["E501", "N803", "N806"]
58
+ # N8* is not relevant for us because of legacy code
59
+ ignore = ["E501", "N801", "N802", "N805", "N815", "N816", "N803", "N806", "PLR0911", "PLR0912", "PLR0915", "PLR2004", "PLW0211"]
58
60
  unfixable = ["F401"]
59
61
 
60
62
  [tool.mypy]
File without changes
File without changes