json-source-edit 0.1.0__tar.gz → 0.2.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.
- {json_source_edit-0.1.0/src/json_source_edit.egg-info → json_source_edit-0.2.0}/PKG-INFO +14 -15
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/README.md +13 -14
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/pyproject.toml +1 -1
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit/__init__.py +2 -1
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit/editor.py +15 -1
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit/operations.py +52 -2
- {json_source_edit-0.1.0 → json_source_edit-0.2.0/src/json_source_edit.egg-info}/PKG-INFO +14 -15
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_operations.py +95 -2
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/LICENSE +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/setup.cfg +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit/algorithm.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit/source_map.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit/validator.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit.egg-info/SOURCES.txt +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit.egg-info/dependency_links.txt +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit.egg-info/requires.txt +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit.egg-info/top_level.txt +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_algorithm.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_edge_cases.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_editor.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_errors.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_format_preservation.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_performance.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_source_map.py +0 -0
- {json_source_edit-0.1.0 → json_source_edit-0.2.0}/tests/test_validator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: json-source-edit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Surgical, byte-preserving JSON editing — change only what you intend to change.
|
|
5
5
|
Author: Jérémie Lumbroso
|
|
6
6
|
License: MIT
|
|
@@ -62,14 +62,12 @@ everything else on the reel undisturbed.
|
|
|
62
62
|
|
|
63
63
|
## Install
|
|
64
64
|
|
|
65
|
-
Not yet on PyPI (see [Status](#status) below). Until then:
|
|
66
|
-
|
|
67
65
|
```bash
|
|
68
|
-
|
|
69
|
-
cd json-source-edit
|
|
70
|
-
pip install -e .
|
|
66
|
+
pip install json-source-edit
|
|
71
67
|
```
|
|
72
68
|
|
|
69
|
+
(Or clone the repo and `pip install -e .` for local development.)
|
|
70
|
+
|
|
73
71
|
## Usage
|
|
74
72
|
|
|
75
73
|
```python
|
|
@@ -121,12 +119,13 @@ it: `get_value`, `get_modifications`, and `preview_diff`.
|
|
|
121
119
|
|---|---|
|
|
122
120
|
| `replace` | Supported — any path, any depth |
|
|
123
121
|
| `remove` | Supported — array elements and object properties, any depth, except the document root itself (structurally undefined: the root has no parent container to apply a comma/whitespace rule against) |
|
|
124
|
-
| `
|
|
122
|
+
| `test` | Supported — not a text edit, an assertion. Evaluated against the document as it stood before this batch's own operations (not a naive sequential reading of RFC 6902 — see the docstring on `operations.Test`), with type-strict comparison (`1` does not test-equal `1.0` or `true`) |
|
|
123
|
+
| `add`, `move`, `copy` | Not implemented — raise `NotImplementedError`. Planned next (see `CHANGELOG.md`) |
|
|
125
124
|
|
|
126
|
-
Multiple `replace`/`remove` calls batch correctly against the same
|
|
127
|
-
document — every path resolves in original-document coordinates
|
|
128
|
-
of what else is in the batch, so edit order doesn't matter and
|
|
129
|
-
can't corrupt another edit's position.
|
|
125
|
+
Multiple `replace`/`remove`/`test` calls batch correctly against the same
|
|
126
|
+
original document — every path resolves in original-document coordinates
|
|
127
|
+
regardless of what else is in the batch, so edit order doesn't matter and
|
|
128
|
+
one deletion can't corrupt another edit's position.
|
|
130
129
|
|
|
131
130
|
## Benchmark (v0)
|
|
132
131
|
|
|
@@ -174,10 +173,10 @@ make a package like this one feasible to write at all.
|
|
|
174
173
|
|
|
175
174
|
## Status
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
Published: [pypi.org/project/json-source-edit](https://pypi.org/project/json-source-edit/).
|
|
177
|
+
`pip install json-source-edit`. 203 tests, 100% line coverage. See
|
|
178
|
+
`CHANGELOG.md` for release history and the Scope table above for what's
|
|
179
|
+
implemented.
|
|
181
180
|
|
|
182
181
|
## License
|
|
183
182
|
|
|
@@ -33,14 +33,12 @@ everything else on the reel undisturbed.
|
|
|
33
33
|
|
|
34
34
|
## Install
|
|
35
35
|
|
|
36
|
-
Not yet on PyPI (see [Status](#status) below). Until then:
|
|
37
|
-
|
|
38
36
|
```bash
|
|
39
|
-
|
|
40
|
-
cd json-source-edit
|
|
41
|
-
pip install -e .
|
|
37
|
+
pip install json-source-edit
|
|
42
38
|
```
|
|
43
39
|
|
|
40
|
+
(Or clone the repo and `pip install -e .` for local development.)
|
|
41
|
+
|
|
44
42
|
## Usage
|
|
45
43
|
|
|
46
44
|
```python
|
|
@@ -92,12 +90,13 @@ it: `get_value`, `get_modifications`, and `preview_diff`.
|
|
|
92
90
|
|---|---|
|
|
93
91
|
| `replace` | Supported — any path, any depth |
|
|
94
92
|
| `remove` | Supported — array elements and object properties, any depth, except the document root itself (structurally undefined: the root has no parent container to apply a comma/whitespace rule against) |
|
|
95
|
-
| `
|
|
93
|
+
| `test` | Supported — not a text edit, an assertion. Evaluated against the document as it stood before this batch's own operations (not a naive sequential reading of RFC 6902 — see the docstring on `operations.Test`), with type-strict comparison (`1` does not test-equal `1.0` or `true`) |
|
|
94
|
+
| `add`, `move`, `copy` | Not implemented — raise `NotImplementedError`. Planned next (see `CHANGELOG.md`) |
|
|
96
95
|
|
|
97
|
-
Multiple `replace`/`remove` calls batch correctly against the same
|
|
98
|
-
document — every path resolves in original-document coordinates
|
|
99
|
-
of what else is in the batch, so edit order doesn't matter and
|
|
100
|
-
can't corrupt another edit's position.
|
|
96
|
+
Multiple `replace`/`remove`/`test` calls batch correctly against the same
|
|
97
|
+
original document — every path resolves in original-document coordinates
|
|
98
|
+
regardless of what else is in the batch, so edit order doesn't matter and
|
|
99
|
+
one deletion can't corrupt another edit's position.
|
|
101
100
|
|
|
102
101
|
## Benchmark (v0)
|
|
103
102
|
|
|
@@ -145,10 +144,10 @@ make a package like this one feasible to write at all.
|
|
|
145
144
|
|
|
146
145
|
## Status
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
Published: [pypi.org/project/json-source-edit](https://pypi.org/project/json-source-edit/).
|
|
148
|
+
`pip install json-source-edit`. 203 tests, 100% line coverage. See
|
|
149
|
+
`CHANGELOG.md` for release history and the Scope table above for what's
|
|
150
|
+
implemented.
|
|
152
151
|
|
|
153
152
|
## License
|
|
154
153
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "json-source-edit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Surgical, byte-preserving JSON editing — change only what you intend to change."
|
|
9
9
|
authors = [{name = "Jérémie Lumbroso"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -5,12 +5,13 @@ byte-for-byte." See ADR-0013 in the parent codetour-cli project
|
|
|
5
5
|
(``docs/adr/0013-format-preserving-json-editing.md``) for the design.
|
|
6
6
|
"""
|
|
7
7
|
from .editor import JSONEditor, Modification
|
|
8
|
-
from .operations import UnsupportedRemovePath
|
|
8
|
+
from .operations import TestOperationFailed, UnsupportedRemovePath
|
|
9
9
|
from .validator import SemanticValidationError
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
12
|
"JSONEditor",
|
|
13
13
|
"Modification",
|
|
14
14
|
"UnsupportedRemovePath",
|
|
15
|
+
"TestOperationFailed",
|
|
15
16
|
"SemanticValidationError",
|
|
16
17
|
]
|
|
@@ -16,7 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
from typing import Any, Dict, List, Optional, Union
|
|
17
17
|
|
|
18
18
|
from .algorithm import apply_edits, coalesce_deletions, strip_orphaned_trailing_commas
|
|
19
|
-
from .operations import Operation, Remove, Replace, resolve_path
|
|
19
|
+
from .operations import Operation, Remove, Replace, Test, resolve_path
|
|
20
20
|
from .source_map import SourceMap
|
|
21
21
|
from .validator import SemanticValidationError, validate_semantic_postconditions
|
|
22
22
|
from .source_map import generate as generate_source_map
|
|
@@ -90,6 +90,17 @@ class JSONEditor:
|
|
|
90
90
|
self.operations.append(Remove(path))
|
|
91
91
|
self.modifications.append(Modification("remove", path, old_value, None))
|
|
92
92
|
|
|
93
|
+
def test(self, path: str, value: Any) -> None:
|
|
94
|
+
"""Record an assertion that ``path`` currently equals ``value``
|
|
95
|
+
(does not apply yet). Evaluated at compile time against the
|
|
96
|
+
pristine, pre-batch parse — see ``operations.Test`` for the full
|
|
97
|
+
semantics and the type-strict comparison rule. Raises no
|
|
98
|
+
``Modification``: a passing test doesn't change the document, and
|
|
99
|
+
``get_modifications()``'s audit trail is specifically about what
|
|
100
|
+
changed.
|
|
101
|
+
"""
|
|
102
|
+
self.operations.append(Test(path, value))
|
|
103
|
+
|
|
93
104
|
# Backwards-compatible aliases matching the ADR's original API.
|
|
94
105
|
def set_value(self, path: str, value: Any) -> None:
|
|
95
106
|
"""Alias for :meth:`replace`."""
|
|
@@ -147,6 +158,9 @@ class JSONEditor:
|
|
|
147
158
|
surgical edit.
|
|
148
159
|
operations.UnsupportedRemovePath: if any recorded ``remove()``
|
|
149
160
|
targets an object-property path (Round 4 §2d).
|
|
161
|
+
operations.TestOperationFailed: if any recorded ``test()``'s
|
|
162
|
+
expected value doesn't match — raised before any edit in
|
|
163
|
+
the batch is applied, so nothing partial ever lands.
|
|
150
164
|
ValueError: if two edits overlap after coalescing.
|
|
151
165
|
"""
|
|
152
166
|
edits = self._compile_edits()
|
|
@@ -37,6 +37,21 @@ class UnsupportedRemovePath(NotImplementedError):
|
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class TestOperationFailed(Exception):
|
|
41
|
+
"""Raised when a ``Test`` operation's expected value doesn't match the
|
|
42
|
+
document — JSON Patch ``test`` semantics: a failing test aborts the
|
|
43
|
+
whole batch. Raised during ``compile()``, which runs entirely before
|
|
44
|
+
``apply_edits()`` ever splices text, so a failing test guarantees
|
|
45
|
+
nothing in the batch — not even edits queued before it — gets applied.
|
|
46
|
+
|
|
47
|
+
``__test__ = False``: same reason as ``Test`` itself — the name starts
|
|
48
|
+
with "Test", and pytest's collector would otherwise try (and fail) to
|
|
49
|
+
treat this exception class as a test class.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
__test__ = False
|
|
53
|
+
|
|
54
|
+
|
|
40
55
|
def calculate_deletion_range(path: str, entry, parsed: Any, original_text: str) -> "tuple[int, int]":
|
|
41
56
|
"""Compute the deletion range for a Remove, for either an array element
|
|
42
57
|
or an object property.
|
|
@@ -191,14 +206,49 @@ class Copy(Operation):
|
|
|
191
206
|
|
|
192
207
|
@dataclass
|
|
193
208
|
class Test(Operation):
|
|
194
|
-
"""Test operation (JSON Patch ``test``)
|
|
209
|
+
"""Test operation (JSON Patch ``test``): assert the value at ``path``
|
|
210
|
+
equals ``value``. Not a text edit — contributes no ``Edit`` objects on
|
|
211
|
+
success; raises ``TestOperationFailed`` on mismatch.
|
|
195
212
|
|
|
196
213
|
Named ``Test`` to match JSON Patch's own vocabulary, not a pytest test
|
|
197
214
|
class; ``__test__ = False`` tells pytest's collector to leave it alone.
|
|
215
|
+
|
|
216
|
+
**Evaluated against the pristine, pre-batch parse** (Round 4 §1's
|
|
217
|
+
coordinate-frame contract — the same rule ``Replace``/``Remove`` follow),
|
|
218
|
+
not against the effect of any earlier operation in the same batch. This
|
|
219
|
+
is a real, deliberate deviation from a naive reading of RFC 6902's
|
|
220
|
+
sequential-application model, where a `test` following a `replace` on
|
|
221
|
+
the same path would see the replace's effect. Batching several
|
|
222
|
+
operations in this package works *because* every operation resolves
|
|
223
|
+
independently against fixed original-document coordinates — extending
|
|
224
|
+
that same guarantee to ``Test`` keeps the whole batch model coherent,
|
|
225
|
+
at the cost of not supporting "test the value my own batch just set."
|
|
226
|
+
Call ``apply()``/``save()`` once per patch if you need that.
|
|
227
|
+
|
|
228
|
+
**Comparison is type-strict**, not Python's own loose ``==``: ``1`` does
|
|
229
|
+
not test-equal ``1.0``, and ``true`` does not test-equal ``1`` — Python's
|
|
230
|
+
``bool`` being an ``int`` subclass would otherwise make
|
|
231
|
+
``test(path, True)`` silently pass against a stored ``1``. A package
|
|
232
|
+
whose whole purpose is preserving a document's exact original
|
|
233
|
+
representation should not blur exactly this distinction in the one
|
|
234
|
+
operation whose entire job is comparison.
|
|
198
235
|
"""
|
|
199
236
|
|
|
200
237
|
__test__ = False
|
|
201
238
|
value: Any
|
|
202
239
|
|
|
203
240
|
def compile(self, source_map: SourceMap, parsed: Any, original_text: str) -> List[Edit]:
|
|
204
|
-
|
|
241
|
+
try:
|
|
242
|
+
actual = resolve_path(parsed, self.path)
|
|
243
|
+
except (KeyError, IndexError, ValueError):
|
|
244
|
+
raise TestOperationFailed(
|
|
245
|
+
f"Test on {self.path!r} failed: path does not exist in the document."
|
|
246
|
+
) from None
|
|
247
|
+
if type(actual) is not type(self.value) or actual != self.value:
|
|
248
|
+
raise TestOperationFailed(
|
|
249
|
+
f"Test on {self.path!r} failed: expected {self.value!r} "
|
|
250
|
+
f"({type(self.value).__name__}), found {actual!r} "
|
|
251
|
+
f"({type(actual).__name__}) in the document as it stood "
|
|
252
|
+
"before this batch's own operations."
|
|
253
|
+
)
|
|
254
|
+
return []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: json-source-edit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Surgical, byte-preserving JSON editing — change only what you intend to change.
|
|
5
5
|
Author: Jérémie Lumbroso
|
|
6
6
|
License: MIT
|
|
@@ -62,14 +62,12 @@ everything else on the reel undisturbed.
|
|
|
62
62
|
|
|
63
63
|
## Install
|
|
64
64
|
|
|
65
|
-
Not yet on PyPI (see [Status](#status) below). Until then:
|
|
66
|
-
|
|
67
65
|
```bash
|
|
68
|
-
|
|
69
|
-
cd json-source-edit
|
|
70
|
-
pip install -e .
|
|
66
|
+
pip install json-source-edit
|
|
71
67
|
```
|
|
72
68
|
|
|
69
|
+
(Or clone the repo and `pip install -e .` for local development.)
|
|
70
|
+
|
|
73
71
|
## Usage
|
|
74
72
|
|
|
75
73
|
```python
|
|
@@ -121,12 +119,13 @@ it: `get_value`, `get_modifications`, and `preview_diff`.
|
|
|
121
119
|
|---|---|
|
|
122
120
|
| `replace` | Supported — any path, any depth |
|
|
123
121
|
| `remove` | Supported — array elements and object properties, any depth, except the document root itself (structurally undefined: the root has no parent container to apply a comma/whitespace rule against) |
|
|
124
|
-
| `
|
|
122
|
+
| `test` | Supported — not a text edit, an assertion. Evaluated against the document as it stood before this batch's own operations (not a naive sequential reading of RFC 6902 — see the docstring on `operations.Test`), with type-strict comparison (`1` does not test-equal `1.0` or `true`) |
|
|
123
|
+
| `add`, `move`, `copy` | Not implemented — raise `NotImplementedError`. Planned next (see `CHANGELOG.md`) |
|
|
125
124
|
|
|
126
|
-
Multiple `replace`/`remove` calls batch correctly against the same
|
|
127
|
-
document — every path resolves in original-document coordinates
|
|
128
|
-
of what else is in the batch, so edit order doesn't matter and
|
|
129
|
-
can't corrupt another edit's position.
|
|
125
|
+
Multiple `replace`/`remove`/`test` calls batch correctly against the same
|
|
126
|
+
original document — every path resolves in original-document coordinates
|
|
127
|
+
regardless of what else is in the batch, so edit order doesn't matter and
|
|
128
|
+
one deletion can't corrupt another edit's position.
|
|
130
129
|
|
|
131
130
|
## Benchmark (v0)
|
|
132
131
|
|
|
@@ -174,10 +173,10 @@ make a package like this one feasible to write at all.
|
|
|
174
173
|
|
|
175
174
|
## Status
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
Published: [pypi.org/project/json-source-edit](https://pypi.org/project/json-source-edit/).
|
|
177
|
+
`pip install json-source-edit`. 203 tests, 100% line coverage. See
|
|
178
|
+
`CHANGELOG.md` for release history and the Scope table above for what's
|
|
179
|
+
implemented.
|
|
181
180
|
|
|
182
181
|
## License
|
|
183
182
|
|
|
@@ -197,14 +197,107 @@ def test_ops_2_6_5_remove_from_a_scalar_parent_still_unsupported():
|
|
|
197
197
|
assert "/scalar" in message
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
# --- Future operations remain stubbed ---
|
|
200
|
+
# --- Future operations remain stubbed (Mission 5: Test is implemented below; Add/Move/Copy are not) ---
|
|
201
201
|
|
|
202
202
|
@pytest.mark.parametrize("op", [
|
|
203
203
|
Add("/x", 1),
|
|
204
204
|
Move("/ignored", "/a", "/b"),
|
|
205
205
|
Copy("/ignored", "/a", "/b"),
|
|
206
|
-
Test("/x", 1),
|
|
207
206
|
])
|
|
208
207
|
def test_future_operations_not_implemented(op):
|
|
209
208
|
with pytest.raises(NotImplementedError):
|
|
210
209
|
op.compile(sm.generate('{"a": 1}'), {"a": 1}, '{"a": 1}')
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# --- Mission 5: Test operation (TEST-2.* — not a text edit, a value assertion) ---
|
|
213
|
+
|
|
214
|
+
from json_source_edit.operations import TestOperationFailed # noqa: E402
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_test_2_1_passing_test_contributes_no_edits():
|
|
218
|
+
text = '{"a": 1, "steps": [1, 2, 3]}'
|
|
219
|
+
assert _compile(Test("/a", 1), text) == []
|
|
220
|
+
assert _apply(Test("/a", 1), text) == text
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.parametrize("path,expected,json_text", [
|
|
224
|
+
("/a", 1, '{"a": 1}'),
|
|
225
|
+
("/name", "hello", '{"name": "hello"}'),
|
|
226
|
+
("/flag", True, '{"flag": true}'),
|
|
227
|
+
("/nothing", None, '{"nothing": null}'),
|
|
228
|
+
("/items/1", 20, '{"items": [10, 20, 30]}'),
|
|
229
|
+
("/nested/deep", "x", '{"nested": {"deep": "x"}}'),
|
|
230
|
+
("", {"a": 1}, '{"a": 1}'), # root path
|
|
231
|
+
])
|
|
232
|
+
def test_test_2_2_passes_for_matching_values_various_types_and_positions(path, expected, json_text):
|
|
233
|
+
assert _compile(Test(path, expected), json_text) == []
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_test_2_3_failing_test_raises_with_actionable_message():
|
|
237
|
+
with pytest.raises(TestOperationFailed) as exc_info:
|
|
238
|
+
_compile(Test("/a", 2), '{"a": 1}')
|
|
239
|
+
message = str(exc_info.value)
|
|
240
|
+
assert "/a" in message and "2" in message and "1" in message
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_test_2_4_missing_path_fails_cleanly_not_a_raw_lookup_error():
|
|
244
|
+
"""RFC 6902: testing a path that doesn't exist is a *failed* test, not
|
|
245
|
+
a malformed-patch error -- resolve_path's KeyError/IndexError/ValueError
|
|
246
|
+
must not leak past Test.compile()."""
|
|
247
|
+
with pytest.raises(TestOperationFailed):
|
|
248
|
+
_compile(Test("/nonexistent", 1), '{"a": 1}')
|
|
249
|
+
with pytest.raises(TestOperationFailed):
|
|
250
|
+
_compile(Test("/items/99", 1), '{"items": [1, 2]}')
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@pytest.mark.parametrize("path,expected,json_text", [
|
|
254
|
+
("/flag", 1, '{"flag": true}'), # bool stored, int expected
|
|
255
|
+
("/count", True, '{"count": 1}'), # int stored, bool expected
|
|
256
|
+
("/n", 1.0, '{"n": 1}'), # int stored, float expected
|
|
257
|
+
("/n", 1, '{"n": 1.0}'), # float stored, int expected
|
|
258
|
+
])
|
|
259
|
+
def test_test_2_5_type_strict_comparison_rejects_bool_int_float_crossover(path, expected, json_text):
|
|
260
|
+
"""Plain Python `==` would pass all four of these (bool is an int
|
|
261
|
+
subclass; 1 == 1.0) -- a package whose whole point is preserving exact
|
|
262
|
+
original representation shouldn't blur that in its comparison op."""
|
|
263
|
+
with pytest.raises(TestOperationFailed):
|
|
264
|
+
_compile(Test(path, expected), json_text)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_test_2_6_failing_test_aborts_the_whole_batch_nothing_partial_applies():
|
|
268
|
+
"""A replace queued *before* a failing test must not land -- compile()
|
|
269
|
+
raises before apply_edits() ever runs, for every op in the batch."""
|
|
270
|
+
from json_source_edit.editor import JSONEditor
|
|
271
|
+
|
|
272
|
+
editor = JSONEditor.from_string('{"a": 1}')
|
|
273
|
+
editor.replace("/a", 999)
|
|
274
|
+
editor.test("/a", 2) # fails against the pristine value, 1
|
|
275
|
+
with pytest.raises(TestOperationFailed):
|
|
276
|
+
editor.apply()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_test_2_7_evaluates_against_pristine_batch_start_not_sequential_effect():
|
|
280
|
+
"""Deliberate deviation from a naive sequential JSON Patch reading: a
|
|
281
|
+
`test` following a `replace` on the same path in the SAME batch sees
|
|
282
|
+
the pristine pre-batch value, not the queued replace's effect -- the
|
|
283
|
+
same coordinate-frame contract every other operation in this package
|
|
284
|
+
follows (Round 4 §1). A test against the value the batch is ABOUT to
|
|
285
|
+
set (999) would fail; against the ORIGINAL value (1) it passes."""
|
|
286
|
+
from json_source_edit.editor import JSONEditor
|
|
287
|
+
|
|
288
|
+
editor = JSONEditor.from_string('{"a": 1}')
|
|
289
|
+
editor.replace("/a", 999)
|
|
290
|
+
editor.test("/a", 1) # pristine value, not editor.replace's queued 999
|
|
291
|
+
result = editor.apply()
|
|
292
|
+
assert json.loads(result)["a"] == 999 # the replace still lands: test passed
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_test_2_8_editor_test_records_no_modification():
|
|
296
|
+
"""A passing test doesn't change the document -- get_modifications()'s
|
|
297
|
+
audit trail is specifically about what changed, so Test shouldn't
|
|
298
|
+
appear there."""
|
|
299
|
+
from json_source_edit.editor import JSONEditor
|
|
300
|
+
|
|
301
|
+
editor = JSONEditor.from_string('{"a": 1}')
|
|
302
|
+
editor.test("/a", 1)
|
|
303
|
+
assert editor.get_modifications() == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit.egg-info/requires.txt
RENAMED
|
File without changes
|
{json_source_edit-0.1.0 → json_source_edit-0.2.0}/src/json_source_edit.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|