jsonata-python 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl

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.
jsonata/cli/__main__.py CHANGED
@@ -34,7 +34,7 @@ def get_options(argv: Optional[list[str]] = None) -> argparse.ArgumentParser:
34
34
  """
35
35
  parser = argparse.ArgumentParser(prog="jsonata", description="Pure Python JSONata CLI")
36
36
  parser.add_argument(
37
- "-v", "--version", action='version', version='%(prog)s 0.6.1')
37
+ "-v", "--version", action='version', version='%(prog)s 0.6.3')
38
38
 
39
39
  parser.add_argument(
40
40
  "-e", "--expression", metavar="<file>",
jsonata/datetimeutils.py CHANGED
@@ -524,7 +524,7 @@ class DateTimeUtils:
524
524
  while pos < len(picture):
525
525
  if picture[pos] == '[':
526
526
  # check it's not a doubled [[
527
- if picture[pos + 1] == '[':
527
+ if pos + 1 < len(picture) and picture[pos + 1] == '[':
528
528
  # literal [
529
529
  format.add_literal(picture, start, pos)
530
530
  format.parts.append(DateTimeUtils.SpecPart("literal", value="["))
@@ -637,14 +637,14 @@ class DateTimeUtils:
637
637
 
638
638
  offset_millis = (60 * offset_hours + offset_minutes) * 60 * 1000
639
639
  date_time = datetime.datetime.fromtimestamp((millis + offset_millis) / 1000.0, datetime.timezone.utc)
640
- result = ""
640
+ parts = []
641
641
  for part in format_spec.parts:
642
642
  if part.type == "literal":
643
- result += part.value
643
+ parts.append(part.value)
644
644
  else:
645
- result += DateTimeUtils._format_component(date_time, part, offset_hours, offset_minutes)
645
+ parts.append(DateTimeUtils._format_component(date_time, part, offset_hours, offset_minutes))
646
646
 
647
- return result
647
+ return ''.join(parts)
648
648
 
649
649
  @staticmethod
650
650
  def _format_component(date: datetime.datetime, marker_spec: SpecPart, offset_hours: int,
jsonata/functions.py CHANGED
@@ -474,11 +474,7 @@ class Functions:
474
474
  pads = size - str_len
475
475
  if pads <= 0:
476
476
  return string
477
- padding = ""
478
- i = 0
479
- while i < pads + 1:
480
- padding += pad_str
481
- i += 1
477
+ padding = pad_str * (pads // len(pad_str) + 1)
482
478
  return Functions.substr(padding, 0, pads) + string
483
479
 
484
480
  # Source: Jsonata4Java PadFunction
@@ -501,11 +497,7 @@ class Functions:
501
497
  pads = size - str_len
502
498
  if pads <= 0:
503
499
  return string
504
- padding = ""
505
- i = 0
506
- while i < pads + 1:
507
- padding += pad_str
508
- i += 1
500
+ padding = pad_str * (pads // len(pad_str) + 1)
509
501
  return string + Functions.substr(padding, 0, pads)
510
502
 
511
503
  @dataclass
@@ -767,23 +759,23 @@ class Functions:
767
759
  raise jexception.JException("Fourth argument of replace function must evaluate to a positive number", 0)
768
760
 
769
761
  def string_replacer(match):
770
- result = ''
762
+ parts = []
771
763
  position = 0
772
764
  repl = str(replacement)
773
765
  while position < len(repl):
774
766
  index = repl.find('$', position)
775
767
  if index == -1:
776
- result += repl[position:]
768
+ parts.append(repl[position:])
777
769
  break
778
- result += repl[position:index]
770
+ parts.append(repl[position:index])
779
771
  position = index + 1
780
772
  if position < len(repl):
781
773
  dollar_val = repl[position]
782
774
  if dollar_val == '$':
783
- result += '$'
775
+ parts.append('$')
784
776
  position += 1
785
777
  elif dollar_val == '0':
786
- result += match.group(0)
778
+ parts.append(match.group(0))
787
779
  position += 1
788
780
  else:
789
781
  max_digits = len(str(len(match.groups())))
@@ -791,15 +783,15 @@ class Functions:
791
783
  if group_num.isdigit():
792
784
  group_index = int(group_num)
793
785
  if 0 < group_index <= len(match.groups()):
794
- result += match.group(group_index) or ''
786
+ parts.append(match.group(group_index) or '')
795
787
  position += len(group_num)
796
788
  else:
797
- result += '$'
789
+ parts.append('$')
798
790
  else:
799
- result += '$'
791
+ parts.append('$')
800
792
  else:
801
- result += '$'
802
- return result
793
+ parts.append('$')
794
+ return ''.join(parts)
803
795
 
804
796
  if callable(replacement):
805
797
  replacer = lambda m: replacement(m.groupdict())
@@ -810,23 +802,23 @@ class Functions:
810
802
 
811
803
  if isinstance(pattern, str):
812
804
  # Use string methods for literal string patterns
813
- result = ''
805
+ parts = []
814
806
  position = 0
815
807
  count = 0
816
808
  while True:
817
809
  if limit is not None and count >= limit:
818
- result += string[position:]
810
+ parts.append(string[position:])
819
811
  break
820
812
  index = string.find(pattern, position)
821
813
  if index == -1:
822
- result += string[position:]
814
+ parts.append(string[position:])
823
815
  break
824
- result += string[position:index]
816
+ parts.append(string[position:index])
825
817
  match = re.match(re.escape(pattern), string[index:])
826
- result += replacer(match)
818
+ parts.append(replacer(match))
827
819
  position = index + len(pattern)
828
820
  count += 1
829
- return result
821
+ return ''.join(parts)
830
822
  else:
831
823
  # Use regex for pattern objects
832
824
  if limit is None:
@@ -1844,7 +1836,8 @@ class Functions:
1844
1836
  #
1845
1837
  @staticmethod
1846
1838
  def error(message: Optional[str]) -> NoReturn:
1847
- raise jexception.JException("D3137", -1, message if message is not None else "$error() function evaluated")
1839
+ raise jexception.JException("D3137", -1,
1840
+ message if message is not None else "$error() function evaluated")
1848
1841
 
1849
1842
  #
1850
1843
  #
@@ -1859,8 +1852,8 @@ class Functions:
1859
1852
  raise jexception.JException("T0410", -1)
1860
1853
 
1861
1854
  if not condition:
1862
- raise jexception.JException("D3141", -1, "$assert() statement failed")
1863
- # message: message || "$assert() statement failed"
1855
+ raise jexception.JException("D3141", -1,
1856
+ message if message is not None else "$assert() statement failed")
1864
1857
 
1865
1858
  #
1866
1859
  #
jsonata/jexception.py CHANGED
@@ -105,6 +105,9 @@ class JException(RuntimeError):
105
105
 
106
106
  formatted = message
107
107
 
108
+ if formatted == "{{{message}}}":
109
+ return str(arg1)
110
+
108
111
  # Replace any {{var}} with format "{}"
109
112
  formatted = re.sub("\\{\\{\\w+\\}\\}", "{}", formatted)
110
113
 
jsonata/jsonata.py CHANGED
@@ -469,7 +469,7 @@ class Jsonata:
469
469
  result.tuple_stream = True
470
470
  step_env = environment
471
471
  if tuple_bindings is None:
472
- tuple_bindings = [{"@": item} for item in input if item is not None]
472
+ tuple_bindings = [{"@": item} for item in input]
473
473
 
474
474
  for tuple_binding in tuple_bindings:
475
475
  step_env = self.create_frame_from_tuple(environment, tuple_binding)
@@ -512,15 +512,21 @@ class Jsonata:
512
512
  results = utils.Utils.create_sequence()
513
513
  if isinstance(input, utils.Utils.JList) and input.tuple_stream:
514
514
  results.tuple_stream = True
515
- if not (isinstance(input, list)):
515
+ if input is None:
516
+ # undefined input yields undefined output; skip filtering entirely
517
+ input = utils.Utils.create_sequence()
518
+ elif not (isinstance(input, list)):
516
519
  input = utils.Utils.create_sequence(input)
517
520
  if predicate.type == "number":
518
521
  index = int(predicate.value) # round it down - was Math.floor
519
522
  if index < 0:
520
523
  # count in from end of array
521
524
  index = len(input) + index
522
- item = input[index] if index < len(input) else None
523
- if item is not None:
525
+ if 0 <= index < len(input):
526
+ item = input[index]
527
+ # Preserve JSON null at this index (vs. out-of-bounds, which is undefined)
528
+ if item is None:
529
+ item = utils.Utils.NULL_VALUE
524
530
  if isinstance(item, list):
525
531
  results = item
526
532
  else:
@@ -684,9 +690,6 @@ class Jsonata:
684
690
  if isinstance(value, list):
685
691
  value = self.flatten(value, None)
686
692
  results = functions.Functions.append(results, value)
687
- elif isinstance(value, dict):
688
- # Call recursively do decompose the map
689
- results.extend(self.evaluate_wildcard(expr, value))
690
693
  else:
691
694
  results.append(value)
692
695
 
@@ -1475,7 +1478,14 @@ class Jsonata:
1475
1478
  elif isinstance(proc, Jsonata.JLambda):
1476
1479
  result = proc.call(input, validated_args)
1477
1480
  elif isinstance(proc, re.Pattern):
1478
- result = [s for s in validated_args if proc.search(s) is not None]
1481
+ _res = []
1482
+ for s in validated_args:
1483
+ if isinstance(s, str):
1484
+ _res.append(Jsonata._regex_closure(proc.finditer(s)))
1485
+ if len(_res) == 1:
1486
+ result = _res[0]
1487
+ else:
1488
+ result = _res
1479
1489
  else:
1480
1490
  print("Proc not found " + str(proc))
1481
1491
  raise jexception.JException("T1006", 0)
@@ -1489,6 +1499,19 @@ class Jsonata:
1489
1499
  raise err
1490
1500
  return result
1491
1501
 
1502
+ @staticmethod
1503
+ def _regex_closure(iterator):
1504
+ m = next(iterator, None)
1505
+ if m is None:
1506
+ return None
1507
+ return {
1508
+ "match": m.group(),
1509
+ "start": m.start(),
1510
+ "end": m.end(),
1511
+ "groups": [m.group()],
1512
+ "next": Jsonata.JLambda(lambda: Jsonata._regex_closure(iterator))
1513
+ }
1514
+
1492
1515
  #
1493
1516
  # Evaluate lambda against input data
1494
1517
  # @param {Object} expr - JSONata expression
@@ -1898,6 +1921,7 @@ class Jsonata:
1898
1921
 
1899
1922
  self.input = None
1900
1923
  self.validate_input = True
1924
+ self.output_convert_nulls = True
1901
1925
 
1902
1926
  # Note: now and millis are implemented in Functions
1903
1927
  # environment.bind("now", defineFunction(function(picture, timezone) {
@@ -1934,6 +1958,24 @@ class Jsonata:
1934
1958
  def set_validate_input(self, validate_input: bool) -> None:
1935
1959
  self.validate_input = validate_input
1936
1960
 
1961
+ #
1962
+ # Checks whether output NULL_VALUE conversion is enabled
1963
+ #
1964
+ def is_output_convert_nulls(self) -> bool:
1965
+ return self.output_convert_nulls
1966
+
1967
+ #
1968
+ # Enable or disable output NULL_VALUE conversion. Enabled by default, which
1969
+ # returns both "JSONata null" and "JSONata undefined" as Python None.
1970
+ #
1971
+ # When disabled, output values may contain Utils.NULL_VALUE indicating
1972
+ # "JSONata null" while Python None indicates "JSONata undefined".
1973
+ # Manually calling Utils.convert_nulls(result) on a raw result will yield
1974
+ # the converted result.
1975
+ #
1976
+ def set_output_convert_nulls(self, output_convert_nulls: bool) -> None:
1977
+ self.output_convert_nulls = output_convert_nulls
1978
+
1937
1979
  def evaluate(self, input: Optional[Any], bindings: Optional[Frame] = None) -> Optional[Any]:
1938
1980
  # throw if the expression compiled with syntax errors
1939
1981
  if self.errors is not None:
@@ -1970,7 +2012,8 @@ class Jsonata:
1970
2012
  # if (typeof callback === "function") {
1971
2013
  # callback(null, it)
1972
2014
  # }
1973
- it = utils.Utils.convert_nulls(it)
2015
+ if self.output_convert_nulls:
2016
+ it = utils.Utils.convert_nulls(it)
1974
2017
  return it
1975
2018
  except Exception as err:
1976
2019
  # insert error message into structure
jsonata/parser.py CHANGED
@@ -1047,7 +1047,8 @@ class Parser:
1047
1047
  rest = self.process_ast(expr.rhs)
1048
1048
  if (rest.type == "function" and rest.procedure.type == "path" and len(
1049
1049
  rest.procedure.steps) == 1 and rest.procedure.steps[0].type == "name" and
1050
- result.steps[-1].type == "function"):
1050
+ result.steps[-1].type == "function" and
1051
+ isinstance(rest.procedure.steps[0].value, Parser.Symbol)):
1051
1052
  # next function in chain of functions - will override a thenable
1052
1053
  result.steps[-1].next_function = rest.procedure.steps[0].value
1053
1054
  if rest.type == "path":
jsonata/signature.py CHANGED
@@ -308,6 +308,7 @@ class Signature:
308
308
  arg = args[arg_index] if arg_index < len(args) else None
309
309
  validated_args.append(arg)
310
310
  arg_index += 1
311
+ index += 1
311
312
  return validated_args
312
313
  self.throw_validation_error(args, supplied_sig, self.function_name)
313
314
 
jsonata/tokenizer.py CHANGED
@@ -32,6 +32,8 @@ from typing import Any, Optional
32
32
 
33
33
  from jsonata import jexception, utils
34
34
 
35
+ _NUMBER_PATTERN = re.compile(r"^-?(0|([1-9][0-9]*))(\.[0-9]+)?([Ee][-+]?[0-9]+)?")
36
+
35
37
 
36
38
  class Tokenizer:
37
39
  operators = {
@@ -135,7 +137,10 @@ class Tokenizer:
135
137
  if pattern == "":
136
138
  raise jexception.JException("S0301", self.position)
137
139
  self.position += 1
138
- current_char = self.path[self.position]
140
+ if self.position < self.length:
141
+ current_char = self.path[self.position]
142
+ else:
143
+ current_char = None
139
144
  # flags
140
145
  start = self.position
141
146
  while current_char == 'i' or current_char == 'm':
@@ -264,8 +269,7 @@ class Tokenizer:
264
269
  self.position += 1
265
270
  raise jexception.JException("S0101", self.position)
266
271
  # test for numbers
267
- numregex = re.compile("^-?(0|([1-9][0-9]*))(\\.[0-9]+)?([Ee][-+]?[0-9]+)?")
268
- match_ = numregex.search(self.path[self.position:])
272
+ match_ = _NUMBER_PATTERN.search(self.path[self.position:])
269
273
  if match_ is not None:
270
274
  num = float(match_.group(0))
271
275
  if not math.isnan(num) and math.isfinite(num):
jsonata/utils.py CHANGED
@@ -74,11 +74,7 @@ class Utils:
74
74
  @staticmethod
75
75
  def create_sequence(el: Optional[Any] = NONE) -> list:
76
76
  if el is not Utils.NONE:
77
- if isinstance(el, list) and len(el) == 1:
78
- sequence = Utils.JList(el)
79
- else:
80
- # This case does NOT exist in Javascript! Why?
81
- sequence = Utils.JList([el])
77
+ sequence = Utils.JList([el])
82
78
  else:
83
79
  sequence = Utils.JList()
84
80
  sequence.sequence = True
@@ -106,9 +102,10 @@ class Utils:
106
102
  if not Utils.is_deep_equal(lhs[key], rhs[key]):
107
103
  return False
108
104
  return True
109
- if lhs == rhs and type(lhs) == type(rhs):
105
+ if lhs == rhs:
106
+ if isinstance(lhs, bool) != isinstance(rhs, bool):
107
+ return False
110
108
  return True
111
-
112
109
  return False
113
110
 
114
111
  class JList(list):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonata-python
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Pure Python implementation of JSONata
5
5
  Project-URL: Homepage, https://github.com/rayokota/jsonata-python
6
6
  Project-URL: Bug Reports, https://github.com/rayokota/jsonata-python/issues
@@ -212,7 +212,7 @@ Keywords: json,jsonata
212
212
  Classifier: Development Status :: 4 - Beta
213
213
  Classifier: License :: OSI Approved :: Apache Software License
214
214
  Classifier: Programming Language :: Python
215
- Requires-Python: >=3.9
215
+ Requires-Python: >=3.10
216
216
  Description-Content-Type: text/markdown
217
217
 
218
218
  # jsonata-python
@@ -0,0 +1,18 @@
1
+ jsonata/__init__.py,sha256=4r8USHj4SoBy_TD8dLxt9HJIgpLfXApFrZBipi4rgr0,388
2
+ jsonata/constants.py,sha256=WtdH_l_s5KD-SiOJ4GR2az7WpVgKB2HguXUnyfy4tvs,3017
3
+ jsonata/datetimeutils.py,sha256=lCTGih5Qn4HZlqG2qcCniREaOt_2Dvxai5rpFq-hKj4,48609
4
+ jsonata/functions.py,sha256=VoVVyK-9SS6CrseChFJkERqUxm7mPzcoIeALegwVztI,75709
5
+ jsonata/jexception.py,sha256=AjyPmFyE7FecV--FLYHfcZFu-SkG1XKAEdvUQ1Ojuqo,12956
6
+ jsonata/jsonata.py,sha256=folX5QxiuXUZEO-KWGWRIkSUQmJgA92MdzIWIMaZcWw,85763
7
+ jsonata/parser.py,sha256=VfWmD79PA_zp7uXUjbVY1E9CEwA60EG1EV2vEKue__M,55444
8
+ jsonata/signature.py,sha256=6iQIgFSCH9Wv7Zhuu5U8PT-LVTtxY_qL8QMsGn5wGH4,20221
9
+ jsonata/timebox.py,sha256=bnevNR_ONvKUiIZCJZEWsRiR0gCWTGOwn5RCY7dKqYc,2861
10
+ jsonata/tokenizer.py,sha256=M5vEaYrMEr6UQwakDLz7dAAIXeJ7ZBPlqw9gYWTbcZM,12550
11
+ jsonata/utils.py,sha256=kn2qjrp7et2NPT3T6XtF9g6Y5lIHWEHgA42hHXQz8rs,5834
12
+ jsonata/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ jsonata/cli/__main__.py,sha256=H-fQAcuYLMH-dSy5t8_idF0DjMivjk3XNWBibsbPuiY,7121
14
+ jsonata_python-0.6.3.dist-info/METADATA,sha256=EDbZIoarzAvc114WL_3525KBm2JakadKu7uj-I6hak8,17338
15
+ jsonata_python-0.6.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
16
+ jsonata_python-0.6.3.dist-info/entry_points.txt,sha256=a-W2yyT3IPhcC1q12BhKPcWnjNi3NXTZ3ZxoI9_UK88,54
17
+ jsonata_python-0.6.3.dist-info/licenses/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
18
+ jsonata_python-0.6.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.28.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,18 +0,0 @@
1
- jsonata/__init__.py,sha256=4r8USHj4SoBy_TD8dLxt9HJIgpLfXApFrZBipi4rgr0,388
2
- jsonata/constants.py,sha256=WtdH_l_s5KD-SiOJ4GR2az7WpVgKB2HguXUnyfy4tvs,3017
3
- jsonata/datetimeutils.py,sha256=IzU6y-vwyhwobHyw9rsImBye-RfWDV7K8Vk0Xs-yiG0,48567
4
- jsonata/functions.py,sha256=it09GOcMOJ2XDlvh_yhf-bgABaHMTrUFhzXa0kUsrNk,75729
5
- jsonata/jexception.py,sha256=6Jz7WMsIiNlQ7-1Hq8RKiE2HxcHq2PDekw0qsSe3lqo,12885
6
- jsonata/jsonata.py,sha256=YmLrus5eBOdcGIzV6nEzsFOkBRJvosE-3bjpdQZVISg,84248
7
- jsonata/parser.py,sha256=dzZ6PeM5l3scTHq4DzCXt4FSRO49yzYEk7XqspCcHNU,55359
8
- jsonata/signature.py,sha256=j7eNKUuGx_9vCt5Qv8BPM7iV5vH26U0Kx8zrkDcctME,20194
9
- jsonata/timebox.py,sha256=bnevNR_ONvKUiIZCJZEWsRiR0gCWTGOwn5RCY7dKqYc,2861
10
- jsonata/tokenizer.py,sha256=6noGxO1L_n2V-Uva5oV044Xx0p08Gm7-XXSfLEG4rR0,12429
11
- jsonata/utils.py,sha256=WDs-z5JRJ5pUEpmAwUY3xgDFxEIsvTqZXp8UX6cNSpQ,5952
12
- jsonata/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- jsonata/cli/__main__.py,sha256=x1cz-w1NYBb_UNJuDnwqmGaz1SdfHwpMjKXIz5GQG-A,7121
14
- jsonata_python-0.6.1.dist-info/METADATA,sha256=lNCEnZXUk7pzV8kP9unZq0AVkNoB2ZngEPbdfiaoUO8,17337
15
- jsonata_python-0.6.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
- jsonata_python-0.6.1.dist-info/entry_points.txt,sha256=a-W2yyT3IPhcC1q12BhKPcWnjNi3NXTZ3ZxoI9_UK88,54
17
- jsonata_python-0.6.1.dist-info/licenses/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
18
- jsonata_python-0.6.1.dist-info/RECORD,,