j-perm 0.2.1.1__tar.gz → 0.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.
- {j_perm-0.2.1.1 → j_perm-0.2.2}/PKG-INFO +1 -1
- {j_perm-0.2.1.1 → j_perm-0.2.2}/pyproject.toml +1 -1
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/constructs/ref.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/engine.py +2 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/normalizer.py +1 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_assert.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_assert_d.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_exec.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_if.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/copy.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/copy_d.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/delete.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/distinct.py +2 -3
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/foreach.py +1 -2
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/set.py +2 -3
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/update.py +1 -2
- j_perm-0.2.2/src/j_perm/pointers.py +107 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/subst.py +1 -3
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/PKG-INFO +1 -1
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/SOURCES.txt +2 -3
- j_perm-0.2.1.1/src/j_perm/utils/__init__.py +0 -0
- j_perm-0.2.1.1/src/j_perm/utils/pointers.py +0 -108
- {j_perm-0.2.1.1 → j_perm-0.2.2}/README.md +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/setup.cfg +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/__init__.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/__init__.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_bool.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_float.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_int.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_str.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/constructs/__init__.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/constructs/eval.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/funcs/__init__.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/funcs/subtract.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/op_handler.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/__init__.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/replace_root.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/schema/__init__.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/__init__.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/_assert.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/assign_or_append.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/delete.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/special_resolver.py +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/dependency_links.txt +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/requires.txt +0 -0
- {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
from typing import Mapping, Any
|
|
5
5
|
|
|
6
6
|
from ..special_resolver import _MISSING, SpecialRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@SpecialRegistry.register("$ref")
|
|
@@ -14,7 +13,7 @@ def of_ref(node: Mapping[str, Any], src: Mapping[str, Any], engine: "ActionEngin
|
|
|
14
13
|
|
|
15
14
|
dflt = node.get("$default", _MISSING)
|
|
16
15
|
try:
|
|
17
|
-
return copy.deepcopy(maybe_slice(ptr, src))
|
|
16
|
+
return copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, src))
|
|
18
17
|
except Exception:
|
|
19
18
|
if dflt is not _MISSING:
|
|
20
19
|
return copy.deepcopy(dflt)
|
|
@@ -6,6 +6,7 @@ from typing import Any, List, Mapping, TypeAlias, MutableMapping, Union
|
|
|
6
6
|
|
|
7
7
|
from .normalizer import Normalizer
|
|
8
8
|
from .op_handler import Handlers
|
|
9
|
+
from .pointers import PointerManager
|
|
9
10
|
from .special_resolver import SpecialResolver
|
|
10
11
|
from .subst import TemplateSubstitutor
|
|
11
12
|
|
|
@@ -48,6 +49,7 @@ class ActionEngine:
|
|
|
48
49
|
special: SpecialResolver = field(default_factory=SpecialResolver)
|
|
49
50
|
substitutor: TemplateSubstitutor = field(default_factory=TemplateSubstitutor)
|
|
50
51
|
normalizer: Normalizer = field(default_factory=Normalizer)
|
|
52
|
+
pointer_manager: PointerManager = field(default_factory=PointerManager)
|
|
51
53
|
|
|
52
54
|
def apply_actions(
|
|
53
55
|
self,
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_get
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("assert")
|
|
@@ -17,7 +16,7 @@ def op_assert(
|
|
|
17
16
|
path = engine.substitutor.substitute(step["path"], src)
|
|
18
17
|
|
|
19
18
|
try:
|
|
20
|
-
current =
|
|
19
|
+
current = engine.pointer_manager.get_pointer(src, path)
|
|
21
20
|
except Exception:
|
|
22
21
|
raise AssertionError(f"'{path}' does not exist in source")
|
|
23
22
|
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_get
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("assertD")
|
|
@@ -17,7 +16,7 @@ def op_assert_d(
|
|
|
17
16
|
path = engine.substitutor.substitute(step["path"], dest)
|
|
18
17
|
|
|
19
18
|
try:
|
|
20
|
-
current =
|
|
19
|
+
current = engine.pointer_manager.get_pointer(dest, path)
|
|
21
20
|
except Exception:
|
|
22
21
|
raise AssertionError(f"'{path}' does not exist in destination")
|
|
23
22
|
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import maybe_slice
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("exec")
|
|
@@ -26,7 +25,7 @@ def op_exec(
|
|
|
26
25
|
if has_from:
|
|
27
26
|
actions_ptr = engine.substitutor.substitute(step["from"], src)
|
|
28
27
|
try:
|
|
29
|
-
actions = maybe_slice(actions_ptr, src)
|
|
28
|
+
actions = engine.pointer_manager.maybe_slice(actions_ptr, src)
|
|
30
29
|
except Exception:
|
|
31
30
|
if "default" in step:
|
|
32
31
|
actions = engine.special.resolve(step["default"], src, engine)
|
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
from typing import MutableMapping, Any, Mapping
|
|
5
5
|
|
|
6
6
|
from ..op_handler import OpRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@OpRegistry.register("if")
|
|
@@ -18,7 +17,7 @@ def op_if(
|
|
|
18
17
|
if "path" in step:
|
|
19
18
|
try:
|
|
20
19
|
ptr = engine.substitutor.substitute(step["path"], src)
|
|
21
|
-
current = maybe_slice(ptr, dest)
|
|
20
|
+
current = engine.pointer_manager.maybe_slice(ptr, dest)
|
|
22
21
|
missing = False
|
|
23
22
|
except Exception:
|
|
24
23
|
current = None
|
|
@@ -5,7 +5,6 @@ from typing import MutableMapping, Any, Mapping
|
|
|
5
5
|
|
|
6
6
|
from .set import op_set
|
|
7
7
|
from ..op_handler import OpRegistry
|
|
8
|
-
from ..utils.pointers import maybe_slice
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@OpRegistry.register("copy")
|
|
@@ -24,7 +23,7 @@ def op_copy(
|
|
|
24
23
|
ignore = bool(step.get("ignore_missing", False))
|
|
25
24
|
|
|
26
25
|
try:
|
|
27
|
-
value = copy.deepcopy(maybe_slice(ptr, src))
|
|
26
|
+
value = copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, src))
|
|
28
27
|
except Exception:
|
|
29
28
|
if "default" in step:
|
|
30
29
|
value = copy.deepcopy(step["default"])
|
|
@@ -5,7 +5,6 @@ from typing import MutableMapping, Any, Mapping
|
|
|
5
5
|
|
|
6
6
|
from .set import op_set
|
|
7
7
|
from ..op_handler import OpRegistry
|
|
8
|
-
from ..utils.pointers import maybe_slice
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@OpRegistry.register("copyD")
|
|
@@ -23,7 +22,7 @@ def op_copy_d(
|
|
|
23
22
|
ignore = bool(step.get("ignore_missing", False))
|
|
24
23
|
|
|
25
24
|
try:
|
|
26
|
-
value = copy.deepcopy(maybe_slice(ptr, dest))
|
|
25
|
+
value = copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, dest))
|
|
27
26
|
except Exception:
|
|
28
27
|
if "default" in step:
|
|
29
28
|
value = copy.deepcopy(step["default"])
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_ensure_parent
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("delete")
|
|
@@ -18,7 +17,7 @@ def op_delete(
|
|
|
18
17
|
ignore = bool(step.get("ignore_missing", True))
|
|
19
18
|
|
|
20
19
|
try:
|
|
21
|
-
parent, leaf =
|
|
20
|
+
parent, leaf = engine.pointer_manager.ensure_parent(dest, path, create=False)
|
|
22
21
|
|
|
23
22
|
if leaf == "-":
|
|
24
23
|
raise ValueError("'-' not allowed in delete")
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_get
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("distinct")
|
|
@@ -15,7 +14,7 @@ def op_distinct(
|
|
|
15
14
|
) -> MutableMapping[str, Any]:
|
|
16
15
|
"""Remove duplicates from a list at the given path, preserving order."""
|
|
17
16
|
path = engine.substitutor.substitute(step["path"], src)
|
|
18
|
-
lst =
|
|
17
|
+
lst = engine.pointer_manager.get_pointer(dest, path)
|
|
19
18
|
|
|
20
19
|
if not isinstance(lst, list):
|
|
21
20
|
raise TypeError(f"{path} is not a list (distinct)")
|
|
@@ -27,7 +26,7 @@ def op_distinct(
|
|
|
27
26
|
unique = []
|
|
28
27
|
for item in lst:
|
|
29
28
|
if key is not None:
|
|
30
|
-
filter_item =
|
|
29
|
+
filter_item = engine.pointer_manager.get_pointer(item, key_path)
|
|
31
30
|
else:
|
|
32
31
|
filter_item = item
|
|
33
32
|
|
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
from typing import MutableMapping, Any, Mapping
|
|
5
5
|
|
|
6
6
|
from ..op_handler import OpRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@OpRegistry.register("foreach")
|
|
@@ -21,7 +20,7 @@ def op_foreach(
|
|
|
21
20
|
skip_empty = bool(step.get("skip_empty", True))
|
|
22
21
|
|
|
23
22
|
try:
|
|
24
|
-
arr = maybe_slice(arr_ptr, src)
|
|
23
|
+
arr = engine.pointer_manager.maybe_slice(arr_ptr, src)
|
|
25
24
|
except Exception:
|
|
26
25
|
arr = default
|
|
27
26
|
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Any, Mapping, MutableMapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_ensure_parent
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("set")
|
|
@@ -22,12 +21,12 @@ def op_set(
|
|
|
22
21
|
if isinstance(value, (str, list, Mapping)):
|
|
23
22
|
value = engine.substitutor.substitute(value, src)
|
|
24
23
|
|
|
25
|
-
parent, leaf =
|
|
24
|
+
parent, leaf = engine.pointer_manager.ensure_parent(dest, path, create=create)
|
|
26
25
|
|
|
27
26
|
if leaf == "-":
|
|
28
27
|
if not isinstance(parent, list):
|
|
29
28
|
if create:
|
|
30
|
-
grand, last =
|
|
29
|
+
grand, last = engine.pointer_manager.ensure_parent(dest, path.rsplit("/", 1)[0], create=True)
|
|
31
30
|
if not isinstance(grand[last], list):
|
|
32
31
|
if grand[last] == {}:
|
|
33
32
|
grand[last] = []
|
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
from typing import MutableMapping, Any, Mapping
|
|
5
5
|
|
|
6
6
|
from ..op_handler import OpRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice, jptr_ensure_parent
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@OpRegistry.register("update")
|
|
@@ -22,7 +21,7 @@ def op_update(
|
|
|
22
21
|
if "from" in step:
|
|
23
22
|
ptr = engine.substitutor.substitute(step["from"], src)
|
|
24
23
|
try:
|
|
25
|
-
update_value = copy.deepcopy(maybe_slice(ptr, src))
|
|
24
|
+
update_value = copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, src))
|
|
26
25
|
except Exception:
|
|
27
26
|
if "default" in step:
|
|
28
27
|
update_value = copy.deepcopy(step["default"])
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Mapping, MutableMapping, List, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PointerManager:
|
|
8
|
+
_SLICE_RE = re.compile(r"(.+)\[(-?\d*):(-?\d*)]$")
|
|
9
|
+
|
|
10
|
+
def _decode(self, tok: str) -> str:
|
|
11
|
+
"""Decode a single JSON Pointer token, handling RFC6901-style escapes."""
|
|
12
|
+
return (
|
|
13
|
+
tok.replace("~0", "~")
|
|
14
|
+
.replace("~1", "/")
|
|
15
|
+
.replace("~2", "$")
|
|
16
|
+
.replace("~3", ".")
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def get_pointer(self, doc: Any, ptr: str) -> Any:
|
|
20
|
+
"""Read value by JSON Pointer, supporting root and '..' segments."""
|
|
21
|
+
if ptr in ("", "/", "."):
|
|
22
|
+
return doc
|
|
23
|
+
|
|
24
|
+
tokens = ptr.lstrip("/").split("/")
|
|
25
|
+
cur: Any = doc
|
|
26
|
+
parents: List[Tuple[Any, Any]] = []
|
|
27
|
+
|
|
28
|
+
for raw_tok in tokens:
|
|
29
|
+
if raw_tok == "..":
|
|
30
|
+
if parents:
|
|
31
|
+
cur, _ = parents.pop()
|
|
32
|
+
else:
|
|
33
|
+
cur = doc
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
key = self._decode(raw_tok)
|
|
37
|
+
|
|
38
|
+
if isinstance(cur, (list, tuple)):
|
|
39
|
+
idx = int(key)
|
|
40
|
+
parents.append((cur, idx))
|
|
41
|
+
cur = cur[idx]
|
|
42
|
+
else:
|
|
43
|
+
parents.append((cur, key))
|
|
44
|
+
cur = cur[key]
|
|
45
|
+
|
|
46
|
+
return cur
|
|
47
|
+
|
|
48
|
+
def maybe_slice(self, ptr: str, src: Mapping[str, Any]) -> Any:
|
|
49
|
+
"""Resolve a pointer and optional Python-style slice suffix '[start:end]' for arrays."""
|
|
50
|
+
m = self._SLICE_RE.match(ptr)
|
|
51
|
+
if m:
|
|
52
|
+
base, s, e = m.groups()
|
|
53
|
+
seq = self.get_pointer(src, base)
|
|
54
|
+
if not isinstance(seq, (list, tuple)):
|
|
55
|
+
raise TypeError(f"{base} is not a list (slice requested)")
|
|
56
|
+
|
|
57
|
+
start = int(s) if s else None
|
|
58
|
+
end = int(e) if e else None
|
|
59
|
+
return seq[start:end]
|
|
60
|
+
|
|
61
|
+
return self.get_pointer(src, ptr)
|
|
62
|
+
|
|
63
|
+
def ensure_parent(
|
|
64
|
+
self,
|
|
65
|
+
doc: MutableMapping[str, Any],
|
|
66
|
+
ptr: str,
|
|
67
|
+
*,
|
|
68
|
+
create: bool = False,
|
|
69
|
+
):
|
|
70
|
+
"""Return (container, leaf_key) for ptr, optionally creating intermediate nodes."""
|
|
71
|
+
raw_parts = ptr.lstrip("/").split("/")
|
|
72
|
+
parts: List[str] = []
|
|
73
|
+
|
|
74
|
+
for raw in raw_parts:
|
|
75
|
+
if raw == "..":
|
|
76
|
+
if parts:
|
|
77
|
+
parts.pop()
|
|
78
|
+
continue
|
|
79
|
+
parts.append(raw)
|
|
80
|
+
|
|
81
|
+
if not parts:
|
|
82
|
+
return doc, ""
|
|
83
|
+
|
|
84
|
+
cur: Any = doc
|
|
85
|
+
|
|
86
|
+
for raw in parts[:-1]:
|
|
87
|
+
token = self._decode(raw)
|
|
88
|
+
|
|
89
|
+
if isinstance(cur, list):
|
|
90
|
+
idx = int(token)
|
|
91
|
+
if idx >= len(cur):
|
|
92
|
+
if create:
|
|
93
|
+
while idx >= len(cur):
|
|
94
|
+
cur.append({})
|
|
95
|
+
else:
|
|
96
|
+
raise IndexError(f"{ptr}: index {idx} out of range")
|
|
97
|
+
cur = cur[idx]
|
|
98
|
+
else:
|
|
99
|
+
if token not in cur:
|
|
100
|
+
if create:
|
|
101
|
+
cur[token] = {}
|
|
102
|
+
else:
|
|
103
|
+
raise KeyError(f"{ptr}: missing key '{token}'")
|
|
104
|
+
cur = cur[token]
|
|
105
|
+
|
|
106
|
+
leaf = self._decode(parts[-1])
|
|
107
|
+
return cur, leaf
|
|
@@ -8,8 +8,6 @@ from typing import Any, Callable, Mapping
|
|
|
8
8
|
import jmespath
|
|
9
9
|
from jmespath import functions as _jp_funcs
|
|
10
10
|
|
|
11
|
-
from j_perm.utils.pointers import maybe_slice
|
|
12
|
-
|
|
13
11
|
# =============================================================================
|
|
14
12
|
# Types
|
|
15
13
|
# =============================================================================
|
|
@@ -295,6 +293,6 @@ class TemplateSubstitutor:
|
|
|
295
293
|
# 4) JSON Pointer fallback
|
|
296
294
|
pointer = "/" + expr.lstrip("/")
|
|
297
295
|
try:
|
|
298
|
-
return maybe_slice(pointer, data) # type: ignore[arg-type]
|
|
296
|
+
return engine.pointer_manager.maybe_slice(pointer, data) # type: ignore[arg-type]
|
|
299
297
|
except Exception:
|
|
300
298
|
return None
|
|
@@ -4,6 +4,7 @@ src/j_perm/__init__.py
|
|
|
4
4
|
src/j_perm/engine.py
|
|
5
5
|
src/j_perm/normalizer.py
|
|
6
6
|
src/j_perm/op_handler.py
|
|
7
|
+
src/j_perm/pointers.py
|
|
7
8
|
src/j_perm/special_resolver.py
|
|
8
9
|
src/j_perm/subst.py
|
|
9
10
|
src/j_perm.egg-info/PKG-INFO
|
|
@@ -38,6 +39,4 @@ src/j_perm/schema/__init__.py
|
|
|
38
39
|
src/j_perm/shorthands/__init__.py
|
|
39
40
|
src/j_perm/shorthands/_assert.py
|
|
40
41
|
src/j_perm/shorthands/assign_or_append.py
|
|
41
|
-
src/j_perm/shorthands/delete.py
|
|
42
|
-
src/j_perm/utils/__init__.py
|
|
43
|
-
src/j_perm/utils/pointers.py
|
|
42
|
+
src/j_perm/shorthands/delete.py
|
|
File without changes
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from typing import Any, Mapping, MutableMapping, List, Tuple
|
|
5
|
-
|
|
6
|
-
_SLICE_RE = re.compile(r"(.+)\[(-?\d*):(-?\d*)]$")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _decode(tok: str) -> str:
|
|
10
|
-
"""Decode a single JSON Pointer token, handling RFC6901-style escapes."""
|
|
11
|
-
return (
|
|
12
|
-
tok.replace("~0", "~")
|
|
13
|
-
.replace("~1", "/")
|
|
14
|
-
.replace("~2", "$")
|
|
15
|
-
.replace("~3", ".")
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def jptr_get(doc: Any, ptr: str) -> Any:
|
|
20
|
-
"""Read value by JSON Pointer, supporting root and '..' segments."""
|
|
21
|
-
if ptr in ("", "/", "."):
|
|
22
|
-
return doc
|
|
23
|
-
|
|
24
|
-
tokens = ptr.lstrip("/").split("/")
|
|
25
|
-
cur: Any = doc
|
|
26
|
-
parents: List[Tuple[Any, Any]] = []
|
|
27
|
-
|
|
28
|
-
for raw_tok in tokens:
|
|
29
|
-
if raw_tok == "..":
|
|
30
|
-
if parents:
|
|
31
|
-
cur, _ = parents.pop()
|
|
32
|
-
else:
|
|
33
|
-
cur = doc
|
|
34
|
-
continue
|
|
35
|
-
|
|
36
|
-
key = _decode(raw_tok)
|
|
37
|
-
|
|
38
|
-
if isinstance(cur, (list, tuple)):
|
|
39
|
-
idx = int(key)
|
|
40
|
-
parents.append((cur, idx))
|
|
41
|
-
cur = cur[idx]
|
|
42
|
-
else:
|
|
43
|
-
parents.append((cur, key))
|
|
44
|
-
cur = cur[key]
|
|
45
|
-
|
|
46
|
-
return cur
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def maybe_slice(ptr: str, src: Mapping[str, Any]) -> Any:
|
|
50
|
-
"""Resolve a pointer and optional Python-style slice suffix '[start:end]' for arrays."""
|
|
51
|
-
m = _SLICE_RE.match(ptr)
|
|
52
|
-
if m:
|
|
53
|
-
base, s, e = m.groups()
|
|
54
|
-
seq = jptr_get(src, base)
|
|
55
|
-
if not isinstance(seq, (list, tuple)):
|
|
56
|
-
raise TypeError(f"{base} is not a list (slice requested)")
|
|
57
|
-
|
|
58
|
-
start = int(s) if s else None
|
|
59
|
-
end = int(e) if e else None
|
|
60
|
-
return seq[start:end]
|
|
61
|
-
|
|
62
|
-
return jptr_get(src, ptr)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def jptr_ensure_parent(
|
|
66
|
-
doc: MutableMapping[str, Any],
|
|
67
|
-
ptr: str,
|
|
68
|
-
*,
|
|
69
|
-
create: bool = False,
|
|
70
|
-
):
|
|
71
|
-
"""Return (container, leaf_key) for ptr, optionally creating intermediate nodes."""
|
|
72
|
-
raw_parts = ptr.lstrip("/").split("/")
|
|
73
|
-
parts: List[str] = []
|
|
74
|
-
|
|
75
|
-
for raw in raw_parts:
|
|
76
|
-
if raw == "..":
|
|
77
|
-
if parts:
|
|
78
|
-
parts.pop()
|
|
79
|
-
continue
|
|
80
|
-
parts.append(raw)
|
|
81
|
-
|
|
82
|
-
if not parts:
|
|
83
|
-
return doc, ""
|
|
84
|
-
|
|
85
|
-
cur: Any = doc
|
|
86
|
-
|
|
87
|
-
for raw in parts[:-1]:
|
|
88
|
-
token = _decode(raw)
|
|
89
|
-
|
|
90
|
-
if isinstance(cur, list):
|
|
91
|
-
idx = int(token)
|
|
92
|
-
if idx >= len(cur):
|
|
93
|
-
if create:
|
|
94
|
-
while idx >= len(cur):
|
|
95
|
-
cur.append({})
|
|
96
|
-
else:
|
|
97
|
-
raise IndexError(f"{ptr}: index {idx} out of range")
|
|
98
|
-
cur = cur[idx]
|
|
99
|
-
else:
|
|
100
|
-
if token not in cur:
|
|
101
|
-
if create:
|
|
102
|
-
cur[token] = {}
|
|
103
|
-
else:
|
|
104
|
-
raise KeyError(f"{ptr}: missing key '{token}'")
|
|
105
|
-
cur = cur[token]
|
|
106
|
-
|
|
107
|
-
leaf = _decode(parts[-1])
|
|
108
|
-
return cur, leaf
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|