python-jsonpath 2.0.1__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 (42) hide show
  1. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/PKG-INFO +30 -3
  2. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/README.md +28 -2
  3. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/__about__.py +1 -1
  4. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/env.py +4 -4
  5. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/lex.py +1 -1
  6. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/patch.py +70 -6
  7. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/pyproject.toml +5 -4
  8. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/.gitignore +0 -0
  9. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/LICENSE.txt +0 -0
  10. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/__init__.py +0 -0
  11. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/__main__.py +0 -0
  12. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/_data.py +0 -0
  13. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/_types.py +0 -0
  14. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/cli.py +0 -0
  15. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/exceptions.py +0 -0
  16. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/filter.py +0 -0
  17. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/fluent_api.py +0 -0
  18. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/__init__.py +0 -0
  19. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/_pattern.py +0 -0
  20. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/arguments.py +0 -0
  21. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/count.py +0 -0
  22. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/filter_function.py +0 -0
  23. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/is_instance.py +0 -0
  24. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/keys.py +0 -0
  25. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/length.py +0 -0
  26. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/match.py +0 -0
  27. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/search.py +0 -0
  28. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/starts_with.py +0 -0
  29. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/typeof.py +0 -0
  30. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/function_extensions/value.py +0 -0
  31. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/lru_cache.py +0 -0
  32. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/match.py +0 -0
  33. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/parse.py +0 -0
  34. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/path.py +0 -0
  35. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/pointer.py +0 -0
  36. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/py.typed +0 -0
  37. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/segments.py +0 -0
  38. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/selectors.py +0 -0
  39. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/serialize.py +0 -0
  40. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/stream.py +0 -0
  41. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/token.py +0 -0
  42. {python_jsonpath-2.0.1 → python_jsonpath-2.1.0}/jsonpath/unescape.py +0 -0
@@ -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
@@ -66,7 +66,7 @@ conda install -c conda-forge python-jsonpath
66
66
 
67
67
  ## Related projects
68
68
 
69
- - [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/).
69
+ - [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/).
70
70
 
71
71
  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.
72
72
 
@@ -119,7 +119,10 @@ print(jane_score) # 55
119
119
 
120
120
  ### JSON Patch
121
121
 
122
- 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)
122
+ 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).
123
+
124
+ > [!WARNING]
125
+ > 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.
123
126
 
124
127
  ```python
125
128
  from jsonpath import patch
@@ -134,7 +137,30 @@ patch_operations = [
134
137
  data = {"some": {"other": "thing"}}
135
138
  patch.apply(patch_operations, data)
136
139
  print(data) # {'some': {'other': 'thing', 'foo': {'bar': [1], 'else': 'thing'}}}
140
+ ```
141
+
142
+ Use `patch.atomic()` or `JSONPatch.atomic()` if you need to preserve input data on patch failure.
143
+
144
+ ```python
145
+ import contextlib
146
+
147
+ from jsonpath import JSONPatchError
148
+ from jsonpath import patch
149
+
150
+ patch_operations = [
151
+ {"op": "add", "path": "/some/foo", "value": {"foo": {}}},
152
+ {"op": "add", "path": "/some/foo", "value": {"bar": []}},
153
+ {"op": "copy", "from": "/some/other", "path": "/some/foo/else"},
154
+ {"op": "add", "path": "/some/foo/bar/-", "value": 1},
155
+ {"op": "test", "path": "/some/thing", "value": "baz"}, # Always fails
156
+ ]
157
+
158
+ data = {"some": {"other": "thing"}}
159
+
160
+ with contextlib.suppress(JSONPatchError):
161
+ patch.atomic(patch_operations, data)
137
162
 
163
+ assert data == {"some": {"other": "thing"}}
138
164
  ```
139
165
 
140
166
  ## License
@@ -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"
@@ -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
@@ -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>\()"
@@ -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)
@@ -21,6 +21,7 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
24
25
  "Programming Language :: Python :: Implementation :: CPython",
25
26
  "Programming Language :: Python :: Implementation :: PyPy",
26
27
  ]
@@ -76,7 +77,7 @@ lint = "ruff check ."
76
77
  typing = "mypy"
77
78
 
78
79
  [[tool.hatch.envs.test.matrix]]
79
- python = ["38", "39", "310", "311", "312", "pypy38", "pypy39"]
80
+ python = ["38", "39", "310", "311", "312", "313", "314", "pypy38", "pypy39"]
80
81
 
81
82
  [tool.hatch.envs.test.scripts]
82
83
  test = "pytest {args}"
@@ -106,7 +107,7 @@ exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
106
107
  [tool.mypy]
107
108
  files = ["jsonpath", "tests"]
108
109
  exclude = ["tests/nts", "tests/cts"]
109
- python_version = "3.11"
110
+ python_version = "3.10"
110
111
  disallow_subclassing_any = true
111
112
  disallow_untyped_calls = true
112
113
  disallow_untyped_defs = true
@@ -151,8 +152,8 @@ exclude = [
151
152
  line-length = 88
152
153
 
153
154
 
154
- # Assume Python 3.10.
155
- target-version = "py310"
155
+ # Assume Python 3.8.
156
+ target-version = "py38"
156
157
 
157
158
  [tool.ruff.lint]
158
159
  select = [