python-jsonpath 2.0.1__py3-none-any.whl → 2.1.0__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.
jsonpath/__about__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "2.0.1"
4
+ __version__ = "2.1.0"
jsonpath/env.py CHANGED
@@ -3,18 +3,18 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  try:
6
- import regex # noqa: F401
6
+ import regex # type: ignore # noqa: F401
7
7
 
8
8
  REGEX_AVAILABLE = True
9
9
  except ImportError:
10
- REGEX_AVAILABLE = False
10
+ REGEX_AVAILABLE = False # type: ignore
11
11
 
12
12
  try:
13
- import iregexp_check # noqa: F401
13
+ import iregexp_check # type: ignore # noqa: F401
14
14
 
15
15
  IREGEXP_AVAILABLE = True
16
16
  except ImportError:
17
- IREGEXP_AVAILABLE = False
17
+ IREGEXP_AVAILABLE = False # type: ignore
18
18
 
19
19
  from decimal import Decimal
20
20
  from operator import getitem
jsonpath/lex.py CHANGED
@@ -113,7 +113,7 @@ class Lexer:
113
113
  )
114
114
 
115
115
  # /pattern/ or /pattern/flags
116
- self.re_pattern = r"/(?P<G_RE>.+?)/(?P<G_RE_FLAGS>[aims]*)"
116
+ self.re_pattern = r"/(?P<G_RE>(?:(?!(?<!\\)/).)*)/(?P<G_RE_FLAGS>[aims]*)"
117
117
 
118
118
  # func(
119
119
  self.function_pattern = r"(?P<G_FUNC>[a-z][a-z_0-9]+)(?P<G_FUNC_PAREN>\()"
jsonpath/patch.py CHANGED
@@ -71,7 +71,7 @@ class OpAdd(Op):
71
71
  if target == "-":
72
72
  parent.append(self.value)
73
73
  else:
74
- index = self.path._index(target) # noqa: SLF001
74
+ index = self.path._index(target) # type: ignore # noqa: SLF001
75
75
  if index == len(parent):
76
76
  parent.append(self.value)
77
77
  else:
@@ -303,12 +303,12 @@ class OpCopy(Op):
303
303
  self, data: Union[MutableSequence[object], MutableMapping[str, object]]
304
304
  ) -> Union[MutableSequence[object], MutableMapping[str, object]]:
305
305
  """Apply this patch operation to _data_."""
306
- source_parent, source_obj = self.source.resolve_parent(data)
306
+ _, source_obj = self.source.resolve_parent(data)
307
307
 
308
308
  if source_obj is UNDEFINED:
309
309
  raise JSONPatchError("source object does not exist")
310
310
 
311
- dest_parent, dest_obj = self.dest.resolve_parent(data)
311
+ dest_parent, _ = self.dest.resolve_parent(data)
312
312
 
313
313
  if dest_parent is None:
314
314
  # Copy source to root
@@ -639,7 +639,7 @@ class JSONPatch:
639
639
 
640
640
  If _data_ is a string or file-like object, it will be loaded with
641
641
  _json.loads_. Otherwise _data_ should be a JSON-like data structure and
642
- will be modified in place.
642
+ will be modified in place, even if a patch operation fails.
643
643
 
644
644
  When modifying _data_ in place, we return modified data too. This is
645
645
  to allow for replacing _data's_ root element, which is allowed by some
@@ -674,6 +674,37 @@ class JSONPatch:
674
674
 
675
675
  return _data
676
676
 
677
+ def atomic(
678
+ self,
679
+ data: Union[List[Any], Dict[str, Any]],
680
+ ) -> object:
681
+ """Apply this patch to _data_ atomically.
682
+
683
+ Unlike `apply()`, if any patch operation fails, _data_ remains
684
+ unchanged.
685
+
686
+ Arguments:
687
+ data: A Python object representing JSON-like data.
688
+
689
+ Returns:
690
+ Patched _data_.
691
+
692
+ Raises:
693
+ JSONPatchError: When a patch operation fails.
694
+ JSONPatchTestFailure: When a _test_ operation does not pass.
695
+ `JSONPatchTestFailure` is a subclass of `JSONPatchError`.
696
+ """
697
+ data_ = copy.deepcopy(data)
698
+ self.apply(data_) # This could raise a JSONPatchError.
699
+ data.clear()
700
+
701
+ if isinstance(data, dict):
702
+ data.update(data_)
703
+ else:
704
+ data.extend(data_)
705
+
706
+ return data
707
+
677
708
  def asdicts(self) -> List[Dict[str, object]]:
678
709
  """Return a list of this patch's operations as dictionaries."""
679
710
  return [op.asdict() for op in self.ops]
@@ -690,7 +721,7 @@ def apply(
690
721
 
691
722
  If _data_ is a string or file-like object, it will be loaded with
692
723
  _json.loads_. Otherwise _data_ should be a JSON-like data structure and
693
- will be **modified in-place**.
724
+ will be **modified in-place**, even if a patch operation fails.
694
725
 
695
726
  When modifying _data_ in-place, we return modified data too. This is
696
727
  to allow for replacing _data's_ root element, which is allowed by some
@@ -711,10 +742,43 @@ def apply(
711
742
  JSONPatchError: When a patch operation fails.
712
743
  JSONPatchTestFailure: When a _test_ operation does not pass.
713
744
  `JSONPatchTestFailure` is a subclass of `JSONPatchError`.
714
-
715
745
  """
716
746
  return JSONPatch(
717
747
  patch,
718
748
  unicode_escape=unicode_escape,
719
749
  uri_decode=uri_decode,
720
750
  ).apply(data)
751
+
752
+
753
+ def atomic(
754
+ patch: Union[str, IOBase, Iterable[Mapping[str, object]], None],
755
+ data: Union[List[Any], Dict[str, Any]],
756
+ *,
757
+ unicode_escape: bool = True,
758
+ uri_decode: bool = False,
759
+ ) -> object:
760
+ """Apply patch operations from _patch_ to _data_ atomically.
761
+
762
+ Unlike `apply()`, if any patch operation fails, _data_ remains unchanged.
763
+
764
+ Arguments:
765
+ patch: A JSON Patch formatted document or equivalent Python objects.
766
+ data: A Python object representing JSON-like data.
767
+ unicode_escape: If `True`, UTF-16 escape sequences will be decoded
768
+ before parsing JSON pointers.
769
+ uri_decode: If `True`, JSON pointers will be unescaped using _urllib_
770
+ before being parsed.
771
+
772
+ Returns:
773
+ Patched _data_.
774
+
775
+ Raises:
776
+ JSONPatchError: When a patch operation fails.
777
+ JSONPatchTestFailure: When a _test_ operation does not pass.
778
+ `JSONPatchTestFailure` is a subclass of `JSONPatchError`.
779
+ """
780
+ return JSONPatch(
781
+ patch,
782
+ unicode_escape=unicode_escape,
783
+ uri_decode=uri_decode,
784
+ ).atomic(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jsonpath
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: JSONPath, JSON Pointer and JSON Patch for Python.
5
5
  Project-URL: Documentation, https://jg-rp.github.io/python-jsonpath/
6
6
  Project-URL: Issues, https://github.com/jg-rp/python-jsonpath/issues
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
22
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
23
24
  Classifier: Programming Language :: Python :: Implementation :: PyPy
24
25
  Requires-Python: >=3.8
@@ -95,7 +96,7 @@ conda install -c conda-forge python-jsonpath
95
96
 
96
97
  ## Related projects
97
98
 
98
- - [JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - A minimal, slightly cleanr Python implementation of RFC 9535. If you're not interested JSONPath sytax beyond that defined in RFC 9535, you might choose [jsonpath-rfc9535](https://pypi.org/project/jsonpath-rfc9535/) over [python-jsonpath](https://pypi.org/project/python-jsonpath/).
99
+ - [JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - A minimal, slightly cleaner Python implementation of RFC 9535. If you're not interested JSONPath sytax beyond that defined in RFC 9535, you might choose [jsonpath-rfc9535](https://pypi.org/project/jsonpath-rfc9535/) over [python-jsonpath](https://pypi.org/project/python-jsonpath/).
99
100
 
100
101
  jsonpath-rfc9535 also includes utilities for verifying and testing the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite). Most notably the nondeterministic behavior of some JSONPath selectors.
101
102
 
@@ -148,7 +149,10 @@ print(jane_score) # 55
148
149
 
149
150
  ### JSON Patch
150
151
 
151
- We also include an [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) compliant implementation of JSON Patch. See JSON Patch [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#patchapplypatch-data) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPatch)
152
+ We also include an [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) compliant implementation of JSON Patch. See JSON Patch [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#patchapplypatch-data) and the [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPatch).
153
+
154
+ > [!WARNING]
155
+ > Objects passed to `patch.apply()` and `JSONPatch.apply()` are modified in place, even if a patch operation fails. Use `patch.atomic()` or `JSONPatch.atomic()` if you need to preserve input data on patch failure.
152
156
 
153
157
  ```python
154
158
  from jsonpath import patch
@@ -163,7 +167,30 @@ patch_operations = [
163
167
  data = {"some": {"other": "thing"}}
164
168
  patch.apply(patch_operations, data)
165
169
  print(data) # {'some': {'other': 'thing', 'foo': {'bar': [1], 'else': 'thing'}}}
170
+ ```
171
+
172
+ Use `patch.atomic()` or `JSONPatch.atomic()` if you need to preserve input data on patch failure.
173
+
174
+ ```python
175
+ import contextlib
176
+
177
+ from jsonpath import JSONPatchError
178
+ from jsonpath import patch
179
+
180
+ patch_operations = [
181
+ {"op": "add", "path": "/some/foo", "value": {"foo": {}}},
182
+ {"op": "add", "path": "/some/foo", "value": {"bar": []}},
183
+ {"op": "copy", "from": "/some/other", "path": "/some/foo/else"},
184
+ {"op": "add", "path": "/some/foo/bar/-", "value": 1},
185
+ {"op": "test", "path": "/some/thing", "value": "baz"}, # Always fails
186
+ ]
187
+
188
+ data = {"some": {"other": "thing"}}
189
+
190
+ with contextlib.suppress(JSONPatchError):
191
+ patch.atomic(patch_operations, data)
166
192
 
193
+ assert data == {"some": {"other": "thing"}}
167
194
  ```
168
195
 
169
196
  ## License
@@ -1,18 +1,18 @@
1
- jsonpath/__about__.py,sha256=-7JogzyaEMlAT9mZnAfz6WvYW2e1688bbokpApou5SM,132
1
+ jsonpath/__about__.py,sha256=sy-PNy4DSvCg0M-xGEYpavLcPSZVTGWGjd3G-qluuLg,132
2
2
  jsonpath/__init__.py,sha256=-a4CE0KwMtXNtOvTIriRZ37EQezZ85EpLz7sj2XpXeQ,11101
3
3
  jsonpath/__main__.py,sha256=6Y5wOE7U-MHymopXOsxofaY30tVZYPGTJO0L4vytoUw,61
4
4
  jsonpath/_data.py,sha256=JEpu5Kg0_kgxYKUilBcHVdTmPxf3-Vc0NgaW6olsqyY,577
5
5
  jsonpath/_types.py,sha256=mxwypalUAI64XdDwXCwIM_1MXCUOTM13y826j-UD8LY,818
6
6
  jsonpath/cli.py,sha256=QNDOzRvGWW9jDNhPpsE9tL1jyS9LS5kSQeMh3q86C6E,10217
7
- jsonpath/env.py,sha256=G0UK9NZvxaLEicdD10hxrd3C_pp-o9FxekkzpZb_O4I,24540
7
+ jsonpath/env.py,sha256=t0TbQJYu2gVz_ktol-kZeEpvZ9JG1NMJRG8HAWcH5Eo,24602
8
8
  jsonpath/exceptions.py,sha256=V7A4A76NjFffa11ePEYgYcV6gG8sdmL9fh3KGEGqPu8,6498
9
9
  jsonpath/filter.py,sha256=W4TyM8um0AQzA_m4Iy-dr1Jd5341YmTP6UWmXfSpia4,22617
10
10
  jsonpath/fluent_api.py,sha256=mfAA2t-xUGOmGVr_1h9lxo3y4FsAvaxOpvOH8jZ4DHU,9117
11
- jsonpath/lex.py,sha256=_64KcaE5Mi3-E5DdYMyX1tHRHtokQWXGKT-ToMb_dfs,12019
11
+ jsonpath/lex.py,sha256=H2M3JjDVw-Ksys1kZ2oGOMKKOrD4MZoIPak1J9ge0WE,12034
12
12
  jsonpath/lru_cache.py,sha256=ohd_ssx2UnBWlLyt16bh2Vg6a_SDA1xHyI_wyOkHLvY,4047
13
13
  jsonpath/match.py,sha256=hPz0OEVJOYJ3MzenJuUzxxHSC0-NNPrLyHXHBRYzOZE,4167
14
14
  jsonpath/parse.py,sha256=QplaB5u2QRl_MyVUDkJ39cPKI6s2oX-ztZ10KUcxNQM,29973
15
- jsonpath/patch.py,sha256=pwlAPFlwHZ-gW8COy60Axk9e1yIVNnQ4ctD0IEILaA4,25593
15
+ jsonpath/patch.py,sha256=V1b1wqkNSG_rE-viUSDzZMlAKYyPHOCYJxJMZpN_pn4,27620
16
16
  jsonpath/path.py,sha256=k4LEmfMhNcJ0ZnEIqOX--G1SoCWhtZZ2S1OzC6kHotA,15661
17
17
  jsonpath/pointer.py,sha256=VEd_cX2_5eR-z755yRatmIS-OLAl_e-9wZhF-t4F1YM,23037
18
18
  jsonpath/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -35,8 +35,8 @@ jsonpath/function_extensions/search.py,sha256=H2Si0js_UG6LACxHfZDPhKK8qqmVtUUhr3
35
35
  jsonpath/function_extensions/starts_with.py,sha256=z-9hkNjQQj_p579CnM6W-E6nc1ucooHHwKwpefO_EBA,676
36
36
  jsonpath/function_extensions/typeof.py,sha256=yCAj9zOqSnam1mfHCGolNHWDmsBOvU3rAhbZDYycx50,1780
37
37
  jsonpath/function_extensions/value.py,sha256=fQMbPUV87Jn1nOwAlBpTeLmLIG5ejH0XQBOM_SR-Us4,721
38
- python_jsonpath-2.0.1.dist-info/METADATA,sha256=fyv3QwOwtVsP_9Nkqv-1Cn8Natr-WHF1jd0n9gYFAts,6529
39
- python_jsonpath-2.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
40
- python_jsonpath-2.0.1.dist-info/entry_points.txt,sha256=xvbWnAebJyOMI_9ugK0xrpFRlwmEsAJD2kNHU0Dvscc,43
41
- python_jsonpath-2.0.1.dist-info/licenses/LICENSE.txt,sha256=u7PksAQGI1QYWcERHeauMseZ4XAzDKUrKW8Z4wbeU1k,1101
42
- python_jsonpath-2.0.1.dist-info/RECORD,,
38
+ python_jsonpath-2.1.0.dist-info/METADATA,sha256=R-GhisgER9oICIxyX_T35Pv4V_z_7paPtjkDeDhH4G8,7525
39
+ python_jsonpath-2.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
40
+ python_jsonpath-2.1.0.dist-info/entry_points.txt,sha256=xvbWnAebJyOMI_9ugK0xrpFRlwmEsAJD2kNHU0Dvscc,43
41
+ python_jsonpath-2.1.0.dist-info/licenses/LICENSE.txt,sha256=u7PksAQGI1QYWcERHeauMseZ4XAzDKUrKW8Z4wbeU1k,1101
42
+ python_jsonpath-2.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any