j-perm 0.1.3__py3-none-any.whl → 0.2.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.
@@ -0,0 +1,34 @@
1
+ j_perm/__init__.py,sha256=RUpTLecqmkXgyy91s8DHNMEStW4almMYGHTJMmLR5NY,714
2
+ j_perm/engine.py,sha256=xKSokxnkuVUyGZcsC1F88xzBw3vLpPPPUD1iHYKuKsI,5562
3
+ j_perm/op_handler.py,sha256=h28thz_18DwZTb9IZtTd6kfsZj7hN4UsmU4uf3aMYpo,1649
4
+ j_perm/special_resolver.py,sha256=khRVWXrE3Vch1gbxsfFwj9JQ37pZ-_ojMeh-G2Vv7bs,4032
5
+ j_perm/subst.py,sha256=oW21Gs2nTEQLcOC_xMKzeuZFtfNvnB2QZAIWl7U4TSg,9802
6
+ j_perm/casters/__init__.py,sha256=mD49oMEyj6oDfSKXEJ8DbnmqkSypfEEg3VMI_rfaK-4,114
7
+ j_perm/casters/_bool.py,sha256=qRsT7IcFBApD09elKUyUUaZmfePSIsOsKQHQHsgnHsw,230
8
+ j_perm/casters/_float.py,sha256=C4r-h6zbkjmQnoEdE2RDMhSkW9oGrvxbQngJZ2T7_tU,153
9
+ j_perm/casters/_int.py,sha256=f1BKqsULiG6Puov1qcGS_inSnv0hZtQ0deQEMP0v0i4,145
10
+ j_perm/casters/_str.py,sha256=v1_BD1_hPbOtvO5CKYAlNEGbMhpR56eKCnV2a2Q4kGU,145
11
+ j_perm/constructs/__init__.py,sha256=TTUPSMyvj8HSfPikrFqXaFsechc9aOIbjtVjy4TOolE,50
12
+ j_perm/constructs/eval.py,sha256=ZckFMcerRWnu-51WAEdm35kuO7t_EzqvtiCAgVWDsjQ,457
13
+ j_perm/constructs/ref.py,sha256=eUrRCanKAEWZ98jOAHNhjs5UhXNAm17NWd4Cbus_cyY,654
14
+ j_perm/funcs/__init__.py,sha256=8yIafxhCH5B7jXrsautQEtl4Qt8RcUK7IWNniORj9ow,31
15
+ j_perm/funcs/subtract.py,sha256=0UW3McKL7VDPVj0UwfvZlrSXGs1uQ6yIdLz5rPqa7bY,293
16
+ j_perm/ops/__init__.py,sha256=G4j6MGnEVnHSeJIBwrpV9oMk7s_djlEaOmLRW_4IEEo,329
17
+ j_perm/ops/_assert.py,sha256=l07DjqsMbvoTDzLbVVcMciblYw8O4D6gBwPt3yFKPhs,758
18
+ j_perm/ops/_exec.py,sha256=-esjfF2te1NvkclrJJ9lGU7YdwY_15vuxMihG08HhQM,1934
19
+ j_perm/ops/_if.py,sha256=DNYp0j3-3YYMIR_CcDM3Ai0CpW2Qt4SYtKxk-9onu8c,1590
20
+ j_perm/ops/copy.py,sha256=fV2rE92JyTRqo91GjXwWucklDR1T1ZcFHHbxG1F8S9g,1110
21
+ j_perm/ops/copy_d.py,sha256=2SGHoq4vaD79diJos_oO3pvZvC_XrQorxhXAgZ5l09E,1017
22
+ j_perm/ops/delete.py,sha256=7SzLE9W_dlmcLOVzKjOzIFLSAHf9nGlagbhbwIH_PmI,916
23
+ j_perm/ops/distinct.py,sha256=RoVIIRNm7C9iAjcPZHpoMfgKw6pGx7nuLkYwruhHeYY,1036
24
+ j_perm/ops/foreach.py,sha256=6tEQ9PyiWBvrm0eANg18p5hV-OvIiL2O1rdqWOC18tQ,1425
25
+ j_perm/ops/replace_root.py,sha256=FFyc-95n3kTzcOXrnjwDdvwOnKNitXKb_1F25MMnXd8,445
26
+ j_perm/ops/set.py,sha256=-kFFgHxeq65K5vLzDe6PhUoLT-ksoZYUVL1Jxf8bRA0,1882
27
+ j_perm/ops/update.py,sha256=gvJTh3vS4zD-lpaqN23w5OO3hTtkSdu8RPy0V8r8Rwg,2471
28
+ j_perm/schema/__init__.py,sha256=mhEbGdi1MgLeEm1IL8-tkTtbIdrdlvQi22sBr7vCUqg,3392
29
+ j_perm/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ j_perm/utils/pointers.py,sha256=X1HSCag4t-qq5zTvBMRAlBKXwpHVNNZYJrdJRtyEr4g,2788
31
+ j_perm-0.2.0.dist-info/METADATA,sha256=DuPKOquojX3jKmrER86guVZXtha52te5M2xo3oN3Azc,16228
32
+ j_perm-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
33
+ j_perm-0.2.0.dist-info/top_level.txt,sha256=yveBxREqVn9DliNwmen1QWoxR0uta7bU5giS1CCffRI,7
34
+ j_perm-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
j_perm/jmes_ext.py DELETED
@@ -1,17 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import jmespath
4
- from jmespath import functions as _jp_funcs
5
-
6
-
7
- class _UserFunctions(_jp_funcs.Functions):
8
- """Container for custom JMESPath functions used by the DSL."""
9
-
10
- @_jp_funcs.signature({'types': ['number']}, {'types': ['number']})
11
- def _func_subtract(self, a: float, b: float) -> float:
12
- """JMESPath function that subtracts two numbers (a - b)."""
13
- return a - b
14
-
15
-
16
- USER_FUNCTIONS = _UserFunctions()
17
- JP_OPTIONS = jmespath.Options(custom_functions=USER_FUNCTIONS)
j_perm/registry.py DELETED
@@ -1,30 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Callable, Dict, MutableMapping, Mapping, TypeAlias
4
-
5
- Handler: TypeAlias = Callable[
6
- [dict, MutableMapping[str, Any], Mapping[str, Any]],
7
- MutableMapping[str, Any],
8
- ]
9
-
10
- _OP_HANDLERS: Dict[str, Handler] = {}
11
-
12
-
13
- def register_op(name: str) -> Callable[[Handler], Handler]:
14
- """Decorator that registers a DSL operation handler under a given name."""
15
-
16
- def decorator(func: Handler) -> Handler:
17
- if name in _OP_HANDLERS:
18
- raise ValueError(f"Handler for op '{name}' is already registered")
19
- _OP_HANDLERS[name] = func
20
- return func
21
-
22
- return decorator
23
-
24
-
25
- def get_handler(name: str) -> Handler:
26
- """Return a registered handler or raise ValueError if it does not exist."""
27
- try:
28
- return _OP_HANDLERS[name]
29
- except KeyError:
30
- raise ValueError(f"Unknown op '{name}'") from None
j_perm/utils/special.py DELETED
@@ -1,41 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import copy
4
- from typing import Any, Mapping
5
-
6
- from .pointers import maybe_slice
7
- from .subst import substitute
8
- from ..engine import apply_actions
9
-
10
- _MISSING = object()
11
-
12
-
13
- def resolve_special(val: Any, src: Mapping[str, Any]):
14
- """Resolve $ref / $eval constructs inside an arbitrary value tree."""
15
- if isinstance(val, dict):
16
- if "$ref" in val:
17
- ptr = substitute(val["$ref"], src)
18
- dflt = val.get("$default", _MISSING)
19
- try:
20
- return copy.deepcopy(maybe_slice(ptr, src))
21
- except Exception:
22
- if dflt is not _MISSING:
23
- return copy.deepcopy(dflt)
24
- raise
25
-
26
- if "$eval" in val:
27
- out = apply_actions(val["$eval"], dest={}, source=src)
28
- if "$select" in val:
29
- sel = maybe_slice(val["$select"], out) # type: ignore[arg-type]
30
- return sel
31
- return out
32
-
33
- return {k: resolve_special(v, src) for k, v in val.items()}
34
-
35
- if isinstance(val, list):
36
- return [resolve_special(x, src) for x in val]
37
-
38
- if isinstance(val, tuple):
39
- return tuple(resolve_special(x, src) for x in val)
40
-
41
- return val
j_perm/utils/subst.py DELETED
@@ -1,133 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import copy
4
- import json
5
- from typing import Any, Mapping, List
6
-
7
- import jmespath
8
-
9
- from ..jmes_ext import JP_OPTIONS
10
-
11
- _CASTERS = {
12
- "int": int,
13
- "float": float,
14
- "str": str,
15
- "bool": lambda x: bool(int(x)) if isinstance(x, (int, str)) else bool(x),
16
- }
17
-
18
-
19
- def _resolve_expr(expr: str, data: Mapping[str, Any]) -> Any:
20
- """Resolve a single ${...} expression body."""
21
- from .pointers import maybe_slice # local import to avoid cycles
22
-
23
- expr = expr.strip()
24
-
25
- # 1) simple casters like int:/path
26
- for prefix, fn in _CASTERS.items():
27
- tag = f"{prefix}:"
28
- if expr.startswith(tag):
29
- inner = expr[len(tag):]
30
- value = flat_substitute(inner, data)
31
- if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
32
- value = flat_substitute(value, data)
33
- return fn(value)
34
-
35
- # 2) JMESPath expression ? <expr>
36
- if expr.startswith("?"):
37
- query_raw = expr[1:].lstrip()
38
- query_expanded = flat_substitute(query_raw, data)
39
- return jmespath.search(query_expanded, data, options=JP_OPTIONS)
40
-
41
- # 3) nested template ${...}
42
- if expr.startswith("${") and expr.endswith("}"):
43
- return flat_substitute(expr, data)
44
-
45
- # 4) default: treat as JSON Pointer (relative to root)
46
- pointer = "/" + expr.lstrip("/")
47
- try:
48
- return maybe_slice(pointer, data) # type: ignore[arg-type]
49
- except Exception:
50
- return None
51
-
52
-
53
- def flat_substitute(tmpl: str, data: Mapping[str, Any]) -> Any:
54
- """One-pass interpolation that replaces all ${...} occurrences in a string."""
55
- if "${" not in tmpl:
56
- return tmpl
57
-
58
- # Entire string is a single ${...}
59
- if tmpl.startswith("${") and tmpl.endswith("}"):
60
- body = tmpl[2:-1]
61
- return copy.deepcopy(_resolve_expr(body, data))
62
-
63
- out: List[str] = []
64
- i = 0
65
- while i < len(tmpl):
66
- if tmpl[i:i + 2] == "${":
67
- depth = 0
68
- j = i + 2
69
-
70
- while j < len(tmpl):
71
- ch = tmpl[j]
72
-
73
- if ch == "{" and tmpl[j - 1] == "$":
74
- depth += 1
75
- elif ch == "}":
76
- if depth == 0:
77
- expr = tmpl[i + 2:j]
78
- val = _resolve_expr(expr, data)
79
-
80
- if isinstance(val, (Mapping, list)):
81
- rendered = json.dumps(val, ensure_ascii=False)
82
- else:
83
- rendered = str(val)
84
-
85
- out.append(rendered)
86
- i = j + 1
87
- break
88
- depth -= 1
89
-
90
- j += 1
91
- else:
92
- # no closing brace found, treat '${' as a literal
93
- out.append(tmpl[i])
94
- i += 1
95
- else:
96
- out.append(tmpl[i])
97
- i += 1
98
-
99
- return "".join(out)
100
-
101
-
102
- def deep_substitute(obj: Any, data: Mapping[str, Any], _depth: int = 0) -> Any:
103
- """Recursively apply interpolation to strings, mapping keys/values and sequences."""
104
- if _depth > 50:
105
- raise RecursionError("too deep interpolation")
106
-
107
- if isinstance(obj, str):
108
- out = flat_substitute(obj, data)
109
- if isinstance(out, str) and "${" in out:
110
- return deep_substitute(out, data, _depth + 1)
111
- return out
112
-
113
- if isinstance(obj, list):
114
- return [deep_substitute(item, data, _depth) for item in obj]
115
-
116
- if isinstance(obj, tuple):
117
- return [deep_substitute(item, data, _depth) for item in obj]
118
-
119
- if isinstance(obj, Mapping):
120
- out: dict[Any, Any] = {}
121
- for k, v in obj.items():
122
- new_key = deep_substitute(k, data, _depth) if isinstance(k, str) else k
123
- if new_key in out:
124
- raise KeyError(f"duplicate key after substitution: {new_key!r}")
125
- out[new_key] = deep_substitute(v, data, _depth)
126
- return out
127
-
128
- return obj
129
-
130
-
131
- # Public alias mirroring the original _substitute name
132
- substitute = deep_substitute
133
- substitute.__name__ = "substitute"
j_perm/utils/tuples.py DELETED
@@ -1,17 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Mapping
4
-
5
-
6
- def tuples_to_lists(obj: Any) -> Any:
7
- """Recursively convert all tuples into lists so JMESPath indexers work reliably."""
8
- if isinstance(obj, tuple):
9
- return [tuples_to_lists(x) for x in obj]
10
-
11
- if isinstance(obj, list):
12
- return [tuples_to_lists(x) for x in obj]
13
-
14
- if isinstance(obj, Mapping):
15
- return {k: tuples_to_lists(v) for k, v in obj.items()}
16
-
17
- return obj
@@ -1,116 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: j-perm
3
- Version: 0.1.3
4
- Summary: jsom permutation library
5
- Author-email: Roman <kuschanow@gmail.com>
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/kuschanow/j-perm
8
- Project-URL: Source, https://github.com/kuschanow/j-perm
9
- Project-URL: Tracker, https://github.com/kuschanow/j-perm/issues
10
- Requires-Python: >=3.10
11
- Description-Content-Type: text/markdown
12
- Requires-Dist: jmespath
13
-
14
-
15
- # JSON patch applier
16
-
17
- A small, composable JSON transformation DSL implemented in Python.
18
-
19
- The library lets you describe transformations as data (a list of steps) and then
20
- apply them to an input document. It supports JSON Pointer paths, custom
21
- JMESPath expressions, interpolation with `${...}` syntax, and a set of
22
- built-in operations.
23
-
24
- ## Features
25
-
26
- - JSON Pointer read/write with support for:
27
- - root pointers (`''`, `'/'`, `'.'`)
28
- - relative `..` segments
29
- - list slices like `/items[1:3]`
30
- - String interpolation:
31
- - `${/path/to/node}` — JSON Pointer lookup
32
- - `${int:/path}` / `${float:/path}` / `${bool:/path}` — simple casters
33
- - `${? some.jmespath(expression) }` — JMESPath with custom functions
34
- - Special values:
35
- - `$ref` — reference into the source document
36
- - `$eval` — nested DSL evaluation with optional `$select`
37
- - Rich set of operations:
38
- - `set`, `copy`, `copyD`, `delete`, `assert`
39
- - `foreach`, `if`, `distinct`
40
- - `replace_root`, `exec`, `update`
41
- - Schema helper: approximate JSON Schema generation for a given DSL script.
42
-
43
- ## Basic usage
44
-
45
- ```python
46
- from j_perm import apply_actions
47
-
48
- source = {
49
- "users": [
50
- {"name": "Alice", "age": 17},
51
- {"name": "Bob", "age": 22}
52
- ]
53
- }
54
-
55
- actions = [
56
- # Start with empty list
57
- {"op": "replace_root", "value": []},
58
-
59
- # For each user - build a simplified object
60
- {
61
- "op": "foreach",
62
- "in": "/users",
63
- "as": "u",
64
- "do": [
65
- {
66
- "op": "set",
67
- "path": "/-",
68
- "value": {
69
- "name": "${/u/name}",
70
- "is_adult": {
71
- "$eval": [
72
- {"op": "replace_root", "value": False},
73
- {
74
- "op": "if",
75
- "cond": "${?`${/u/age}` >= `18`}",
76
- "then": [{"op": "replace_root", "value": True}]
77
- }
78
- ]
79
- }
80
- }
81
- }
82
- ]
83
- }
84
- ]
85
-
86
- result = apply_actions(actions, dest={}, source=source)
87
- # result -> {"fullName": "Alice", "isAdult": True}
88
- ```
89
-
90
- ## Schema generation
91
-
92
- ```python
93
- from j_perm import build_schema
94
-
95
- schema = build_schema(script)
96
- ```
97
-
98
- `schema` is a JSON-Schema-like structure that you can use for documentation,
99
- validation or introspection.
100
-
101
- ## Extending with custom operations
102
-
103
- ```python
104
- from j_perm import register_op
105
-
106
- @register_op("my_op")
107
- def my_op(step, dest, src):
108
- # implement your logic here
109
- return dest
110
- ```
111
-
112
- Any registered operation can then be used in DSL scripts via `{"op": "my_op", ...}`.
113
-
114
- ## License
115
-
116
- This package is provided as-is; feel free to adapt it to your project structure.
@@ -1,26 +0,0 @@
1
- j_perm/__init__.py,sha256=2be5yKOQ_3owmfpZB4op_uso6rHf06aUiOBuiVZO8rU,354
2
- j_perm/engine.py,sha256=5u0aB_hr9qgwMRaq_KKo34z-dUo6rdFmnc6XIIi3Bss,2484
3
- j_perm/jmes_ext.py,sha256=z4Feqx0H_4EzEILYd6jo7AGvsyFvabAfp2kVXL_E5Mg,527
4
- j_perm/registry.py,sha256=mq6TrP9kehJlqCOp-bflGAetEwoE2PHFoRbM7E-ov6o,890
5
- j_perm/ops/__init__.py,sha256=TdwKGx97X9_1Ti-VWLNBt5DGwR-cvXS7lkJf1lMD7Oo,298
6
- j_perm/ops/_exec.py,sha256=7Ip1keFIv2V29sb-N10cRGTHrKhI6MP-zgTNx8aPNvM,1887
7
- j_perm/ops/_if.py,sha256=ggQa73LmXbPmgkvOdeaY7czTzFaWRrVWpFL4EZ0tZY4,1569
8
- j_perm/ops/assert.py,sha256=p-wu1wcoPzrgT1VtKSoQAd8ff06unz-gvaETPGW3cB0,735
9
- j_perm/ops/copy.py,sha256=7cKQrRci31MxbVX7nuDGpuQDytTQ1rTF5RoL1FV2wlg,1068
10
- j_perm/ops/copy_d.py,sha256=kzzOCqOpVvEeNZ-ypIi7r__ei13E0aOQsLDi0QkDxuY,975
11
- j_perm/ops/delete.py,sha256=j4VZ-cnc_Txg7lgkJgJcQV9YDRIIXlTge9lRi03gPQk,893
12
- j_perm/ops/distinct.py,sha256=U8BO14Nf5wLE19hR7-oNQuv2rWHTv7QKkViXfNn561E,994
13
- j_perm/ops/foreach.py,sha256=EYNiM2zpxOLvX5W23ajIHrBOpbqNdV4256EAUdac_es,1442
14
- j_perm/ops/replace_root.py,sha256=pUreYnQDC6jekLZMnDsRx1MsBVsKzWX-KMea5jHHqts,475
15
- j_perm/ops/set.py,sha256=27CdeSoQRP2hNqTXWMvFMqxEj3BISOX7iySZdLftxHs,1869
16
- j_perm/ops/update.py,sha256=JvaajmKhzHrcD_tVTT9JB-lSPaCsyvFAIt7OvXiwqT4,2439
17
- j_perm/schema/__init__.py,sha256=xJML5yc85qnU-_aSTLeeDqfPYqo9-DfrENihnp1DfK8,3201
18
- j_perm/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- j_perm/utils/pointers.py,sha256=X1HSCag4t-qq5zTvBMRAlBKXwpHVNNZYJrdJRtyEr4g,2788
20
- j_perm/utils/special.py,sha256=KhKgrmR_pRyIbMubtV-62nNPzoLIMbxVBGpOPZQTTlA,1221
21
- j_perm/utils/subst.py,sha256=hPLtqgaRTzJmvDGuQxM9VnIMCKK08IAWo_gfhogb57s,4077
22
- j_perm/utils/tuples.py,sha256=0XsMId9J2T7MxtmpWQaNnbmxhileTs8Xm_vSPICkxNI,469
23
- j_perm-0.1.3.dist-info/METADATA,sha256=nQqj6jp30FxhTi39rghOfOYGU2gvuSR5bvxrE1p_81U,3169
24
- j_perm-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- j_perm-0.1.3.dist-info/top_level.txt,sha256=yveBxREqVn9DliNwmen1QWoxR0uta7bU5giS1CCffRI,7
26
- j_perm-0.1.3.dist-info/RECORD,,