dictwalk 0.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.
@@ -0,0 +1,54 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ tox:
15
+ name: ${{ matrix.tox_env }}
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ include:
21
+ - tox_env: py310
22
+ python_version: "3.10"
23
+ - tox_env: py311
24
+ python_version: "3.11"
25
+ - tox_env: py312
26
+ python_version: "3.12"
27
+ - tox_env: py313
28
+ python_version: "3.13"
29
+ - tox_env: py314
30
+ python_version: "3.14"
31
+ - tox_env: lint
32
+ python_version: "3.13"
33
+ - tox_env: type
34
+ python_version: "3.13"
35
+ - tox_env: deptry
36
+ python_version: "3.13"
37
+
38
+ steps:
39
+ - name: Checkout
40
+ uses: actions/checkout@v4
41
+
42
+ - name: Set up Python
43
+ uses: actions/setup-python@v5
44
+ with:
45
+ python-version: ${{ matrix.python_version }}
46
+ allow-prereleases: true
47
+
48
+ - name: Set up uv
49
+ uses: astral-sh/setup-uv@v4
50
+ with:
51
+ enable-cache: true
52
+
53
+ - name: Run tox
54
+ run: uv run tox -e ${{ matrix.tox_env }}
@@ -0,0 +1,49 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: write
8
+
9
+ jobs:
10
+ release:
11
+ if: github.ref == 'refs/heads/main'
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.13"
24
+
25
+ - name: Set up uv
26
+ uses: astral-sh/setup-uv@v4
27
+ with:
28
+ enable-cache: true
29
+
30
+ - name: Resolve version
31
+ run: echo "VERSION=$(uv version --short)" >> "$GITHUB_ENV"
32
+
33
+ - name: Build distributions
34
+ run: uv build
35
+
36
+ - name: Publish to PyPI
37
+ env:
38
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
39
+ run: |
40
+ test -n "$UV_PUBLISH_TOKEN"
41
+ uv publish --token "$UV_PUBLISH_TOKEN"
42
+
43
+ - name: Tag release
44
+ run: |
45
+ test -n "$VERSION"
46
+ git config user.name "github-actions[bot]"
47
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
48
+ git tag "v$VERSION"
49
+ git push origin "v$VERSION"
@@ -0,0 +1,16 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ target/
10
+
11
+ # Virtual environments
12
+ .venv
13
+
14
+ .tox/
15
+ .pytest_cache/
16
+ .ruff_cache/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,8 @@
1
+ {
2
+ "python.analysis.typeCheckingMode": "off",
3
+ "python.testing.pytestArgs": [
4
+ "tests"
5
+ ],
6
+ "python.testing.unittestEnabled": false,
7
+ "python.testing.pytestEnabled": true
8
+ }
@@ -0,0 +1,23 @@
1
+ .PHONY: test lint type deptry build release ci
2
+
3
+ test:
4
+ uv run tox -e py310,py311,py312,py313,py314
5
+
6
+ lint:
7
+ uv run tox -e lint
8
+
9
+ type:
10
+ uv run tox -e type
11
+
12
+ deptry:
13
+ uv run tox -e deptry
14
+
15
+ build:
16
+ uv build
17
+
18
+ release:
19
+ @test -n "$$PYPI_TOKEN" || (echo "PYPI_TOKEN is not set"; exit 1)
20
+ uv publish --token "$$PYPI_TOKEN"
21
+
22
+ ci:
23
+ uv run tox -e py310,py311,py312,py313,py314,lint,type,deptry
@@ -0,0 +1,251 @@
1
+ Metadata-Version: 2.4
2
+ Name: dictwalk
3
+ Version: 0.1.0
4
+ Summary: Dict path traversal and mutation utilities
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # dictwalk
9
+
10
+ > This library is basically overengineered and ridiculous.
11
+ > It mostly exists because I kept asking an AI to add one more feature, then one more, then one more.
12
+ > Then I asked the AI to come up with features and I said screw it add them all.
13
+ > Please do not use this instead of just writing normal code.
14
+ > This is bored-developer scope creep in library form.
15
+ > The original idea was basically “jq/yq, but for Python dicts, and here we are”.
16
+ > I hope you get some use out of it, because I didn't.
17
+
18
+ `dictwalk` is a small utility for traversing and mutating nested Python dict/list data using path expressions.
19
+
20
+ It supports:
21
+ - Deep reads (`get`)
22
+ - Existence checks (`exists`)
23
+ - In-place writes (`set`)
24
+ - In-place removals (`unset`)
25
+ - Predicate filtering for lists
26
+ - Wildcards (`*`, `**`)
27
+ - Transform/filter pipelines (`|$filter`)
28
+
29
+ ## Requirements
30
+
31
+ - Python `>=3.10`
32
+
33
+ ## Installation
34
+
35
+ From source:
36
+
37
+ ```bash
38
+ pip install .
39
+ ```
40
+
41
+ For local development:
42
+
43
+ ```bash
44
+ uv sync
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```python
50
+ from dictwalk import dictwalk
51
+
52
+ data = {
53
+ "a": {
54
+ "users": [
55
+ {"id": 1, "name": "Ada", "active": True},
56
+ {"id": 2, "name": "Lin", "active": False},
57
+ ]
58
+ }
59
+ }
60
+
61
+ # Read
62
+ names = dictwalk.get(data, "a.users[].name")
63
+ # ["Ada", "Lin"]
64
+
65
+ # Filter and map
66
+ active_names = dictwalk.get(data, "a.users[?active==True].name[]")
67
+ # ["Ada"]
68
+
69
+ # Write
70
+ dictwalk.set(data, "a.users[?id==2].active", True)
71
+
72
+ # Unset
73
+ dictwalk.unset(data, "a.users[?id==1].name")
74
+ ```
75
+
76
+ ## Path Syntax
77
+
78
+ ### Dot traversal
79
+
80
+ ```text
81
+ a.b.c
82
+ ```
83
+
84
+ Read nested object keys.
85
+
86
+ ### List map
87
+
88
+ ```text
89
+ a.items[].id
90
+ ```
91
+
92
+ Apply the next token to every item in a list.
93
+
94
+ ### List index and slice
95
+
96
+ ```text
97
+ a.items[0]
98
+ a.items[-1]
99
+ a.items[1:3]
100
+ ```
101
+
102
+ ### Predicates
103
+
104
+ ```text
105
+ a.items[?id==1]
106
+ a.items[?score>=10]
107
+ ```
108
+
109
+ List predicates support:
110
+ - `==`, `!=`, `>`, `<`, `>=`, `<=`
111
+
112
+ ### Predicate filters
113
+
114
+ Use registered filters on predicate values:
115
+
116
+ ```text
117
+ a.items[?id==$even]
118
+ a.items[?id==$gt(5)&&$lt(10)]
119
+ a.items[?id==!$odd]
120
+ ```
121
+
122
+ Boolean operators in predicate filters:
123
+ - `&&` (and)
124
+ - `||` (or)
125
+ - `!` (not)
126
+ - parentheses for grouping
127
+
128
+ ### Wildcards
129
+
130
+ ```text
131
+ a.*.id
132
+ a.**.id
133
+ ```
134
+
135
+ - `*`: one level
136
+ - `**`: deep descendant traversal
137
+
138
+ ### Output transforms
139
+
140
+ Apply filters to the final read value:
141
+
142
+ ```text
143
+ a.value|$double|$string
144
+ a.list|$max
145
+ a.list|$double[]|$max
146
+ ```
147
+
148
+ ## API
149
+
150
+ ## `dictwalk.get(data, path, default=None, strict=False)`
151
+
152
+ - Returns resolved value.
153
+ - If `strict=False`: resolution failures return `default`.
154
+ - If `strict=True`: raises `DictWalkResolutionError`.
155
+
156
+ Special root token support in read paths:
157
+
158
+ ```text
159
+ $$root.x
160
+ a.b.$$root.x
161
+ ```
162
+
163
+ ## `dictwalk.exists(data, path, strict=False) -> bool`
164
+
165
+ - Returns `True` if path resolves, else `False`.
166
+ - If `strict=True`, raises `DictWalkResolutionError` on resolution failures.
167
+
168
+ ## `dictwalk.set(data, path, value, *, strict=False, create_missing=True, create_filter_match=True, overwrite_incompatible=True) -> dict`
169
+
170
+ Mutates and returns the same `data` object.
171
+
172
+ `value` can be:
173
+ - A direct value (`42`, `"x"`, `{"k": 1}`)
174
+ - A filter string (`"$double"`, `"$add(2)|$string"`)
175
+ - A root reference expression:
176
+ - `$$root`
177
+ - `$$root.some.path`
178
+ - `$$root.some.path|$filter`
179
+
180
+ Notes:
181
+ - `$$root` is valid in `value`, not in write `path`.
182
+ - With `strict=True`, parent path must already resolve.
183
+
184
+ ## `dictwalk.unset(data, path, *, strict=False) -> dict`
185
+
186
+ Removes targeted values in-place and returns the same object.
187
+
188
+ At terminal paths this can:
189
+ - Remove dict keys
190
+ - Remove list indexes/slices
191
+ - Remove list items matching a filter
192
+
193
+ ## Filters
194
+
195
+ Built-in filters include arithmetic, string, collection, predicate, and datetime utilities.
196
+
197
+ Examples:
198
+ - Arithmetic: `$inc`, `$double`, `$add(2)`, `$round(1)`
199
+ - Collection: `$len`, `$max`, `$min`, `$sum`, `$avg`, `$unique`
200
+ - String: `$lower`, `$upper`, `$replace("a","b")`, `$split(",")`
201
+ - Predicates: `$even`, `$odd`, `$gt(10)`, `$contains("x")`
202
+ - Datetime: `$to_datetime`, `$timestamp`, `$age_seconds`, `$before(...)`, `$after(...)`
203
+
204
+ Register custom filters:
205
+
206
+ ```python
207
+ from dictwalk import dictwalk
208
+
209
+ dictwalk.register_path_filter("triple", lambda x: x * 3)
210
+
211
+ value = dictwalk.get({"a": {"b": 2}}, "a.b|$triple")
212
+ # 6
213
+ ```
214
+
215
+ ## Errors
216
+
217
+ From `dictwalk.errors`:
218
+ - `DictWalkError` (base)
219
+ - `DictWalkParseError`
220
+ - `DictWalkOperatorError`
221
+ - `DictWalkResolutionError`
222
+
223
+ Use `strict=True` when you want explicit failures instead of fallback defaults.
224
+
225
+ ## Development
226
+
227
+ Run tests:
228
+
229
+ ```bash
230
+ make test
231
+ ```
232
+
233
+ Run lint/type/dependency checks:
234
+
235
+ ```bash
236
+ make lint
237
+ make type
238
+ make deptry
239
+ ```
240
+
241
+ Run everything:
242
+
243
+ ```bash
244
+ make ci
245
+ ```
246
+
247
+ Direct tox usage:
248
+
249
+ ```bash
250
+ uv run tox -e py310,py311,py312,py313,py314,lint,type,deptry
251
+ ```
@@ -0,0 +1,244 @@
1
+ # dictwalk
2
+
3
+ > This library is basically overengineered and ridiculous.
4
+ > It mostly exists because I kept asking an AI to add one more feature, then one more, then one more.
5
+ > Then I asked the AI to come up with features and I said screw it add them all.
6
+ > Please do not use this instead of just writing normal code.
7
+ > This is bored-developer scope creep in library form.
8
+ > The original idea was basically “jq/yq, but for Python dicts, and here we are”.
9
+ > I hope you get some use out of it, because I didn't.
10
+
11
+ `dictwalk` is a small utility for traversing and mutating nested Python dict/list data using path expressions.
12
+
13
+ It supports:
14
+ - Deep reads (`get`)
15
+ - Existence checks (`exists`)
16
+ - In-place writes (`set`)
17
+ - In-place removals (`unset`)
18
+ - Predicate filtering for lists
19
+ - Wildcards (`*`, `**`)
20
+ - Transform/filter pipelines (`|$filter`)
21
+
22
+ ## Requirements
23
+
24
+ - Python `>=3.10`
25
+
26
+ ## Installation
27
+
28
+ From source:
29
+
30
+ ```bash
31
+ pip install .
32
+ ```
33
+
34
+ For local development:
35
+
36
+ ```bash
37
+ uv sync
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from dictwalk import dictwalk
44
+
45
+ data = {
46
+ "a": {
47
+ "users": [
48
+ {"id": 1, "name": "Ada", "active": True},
49
+ {"id": 2, "name": "Lin", "active": False},
50
+ ]
51
+ }
52
+ }
53
+
54
+ # Read
55
+ names = dictwalk.get(data, "a.users[].name")
56
+ # ["Ada", "Lin"]
57
+
58
+ # Filter and map
59
+ active_names = dictwalk.get(data, "a.users[?active==True].name[]")
60
+ # ["Ada"]
61
+
62
+ # Write
63
+ dictwalk.set(data, "a.users[?id==2].active", True)
64
+
65
+ # Unset
66
+ dictwalk.unset(data, "a.users[?id==1].name")
67
+ ```
68
+
69
+ ## Path Syntax
70
+
71
+ ### Dot traversal
72
+
73
+ ```text
74
+ a.b.c
75
+ ```
76
+
77
+ Read nested object keys.
78
+
79
+ ### List map
80
+
81
+ ```text
82
+ a.items[].id
83
+ ```
84
+
85
+ Apply the next token to every item in a list.
86
+
87
+ ### List index and slice
88
+
89
+ ```text
90
+ a.items[0]
91
+ a.items[-1]
92
+ a.items[1:3]
93
+ ```
94
+
95
+ ### Predicates
96
+
97
+ ```text
98
+ a.items[?id==1]
99
+ a.items[?score>=10]
100
+ ```
101
+
102
+ List predicates support:
103
+ - `==`, `!=`, `>`, `<`, `>=`, `<=`
104
+
105
+ ### Predicate filters
106
+
107
+ Use registered filters on predicate values:
108
+
109
+ ```text
110
+ a.items[?id==$even]
111
+ a.items[?id==$gt(5)&&$lt(10)]
112
+ a.items[?id==!$odd]
113
+ ```
114
+
115
+ Boolean operators in predicate filters:
116
+ - `&&` (and)
117
+ - `||` (or)
118
+ - `!` (not)
119
+ - parentheses for grouping
120
+
121
+ ### Wildcards
122
+
123
+ ```text
124
+ a.*.id
125
+ a.**.id
126
+ ```
127
+
128
+ - `*`: one level
129
+ - `**`: deep descendant traversal
130
+
131
+ ### Output transforms
132
+
133
+ Apply filters to the final read value:
134
+
135
+ ```text
136
+ a.value|$double|$string
137
+ a.list|$max
138
+ a.list|$double[]|$max
139
+ ```
140
+
141
+ ## API
142
+
143
+ ## `dictwalk.get(data, path, default=None, strict=False)`
144
+
145
+ - Returns resolved value.
146
+ - If `strict=False`: resolution failures return `default`.
147
+ - If `strict=True`: raises `DictWalkResolutionError`.
148
+
149
+ Special root token support in read paths:
150
+
151
+ ```text
152
+ $$root.x
153
+ a.b.$$root.x
154
+ ```
155
+
156
+ ## `dictwalk.exists(data, path, strict=False) -> bool`
157
+
158
+ - Returns `True` if path resolves, else `False`.
159
+ - If `strict=True`, raises `DictWalkResolutionError` on resolution failures.
160
+
161
+ ## `dictwalk.set(data, path, value, *, strict=False, create_missing=True, create_filter_match=True, overwrite_incompatible=True) -> dict`
162
+
163
+ Mutates and returns the same `data` object.
164
+
165
+ `value` can be:
166
+ - A direct value (`42`, `"x"`, `{"k": 1}`)
167
+ - A filter string (`"$double"`, `"$add(2)|$string"`)
168
+ - A root reference expression:
169
+ - `$$root`
170
+ - `$$root.some.path`
171
+ - `$$root.some.path|$filter`
172
+
173
+ Notes:
174
+ - `$$root` is valid in `value`, not in write `path`.
175
+ - With `strict=True`, parent path must already resolve.
176
+
177
+ ## `dictwalk.unset(data, path, *, strict=False) -> dict`
178
+
179
+ Removes targeted values in-place and returns the same object.
180
+
181
+ At terminal paths this can:
182
+ - Remove dict keys
183
+ - Remove list indexes/slices
184
+ - Remove list items matching a filter
185
+
186
+ ## Filters
187
+
188
+ Built-in filters include arithmetic, string, collection, predicate, and datetime utilities.
189
+
190
+ Examples:
191
+ - Arithmetic: `$inc`, `$double`, `$add(2)`, `$round(1)`
192
+ - Collection: `$len`, `$max`, `$min`, `$sum`, `$avg`, `$unique`
193
+ - String: `$lower`, `$upper`, `$replace("a","b")`, `$split(",")`
194
+ - Predicates: `$even`, `$odd`, `$gt(10)`, `$contains("x")`
195
+ - Datetime: `$to_datetime`, `$timestamp`, `$age_seconds`, `$before(...)`, `$after(...)`
196
+
197
+ Register custom filters:
198
+
199
+ ```python
200
+ from dictwalk import dictwalk
201
+
202
+ dictwalk.register_path_filter("triple", lambda x: x * 3)
203
+
204
+ value = dictwalk.get({"a": {"b": 2}}, "a.b|$triple")
205
+ # 6
206
+ ```
207
+
208
+ ## Errors
209
+
210
+ From `dictwalk.errors`:
211
+ - `DictWalkError` (base)
212
+ - `DictWalkParseError`
213
+ - `DictWalkOperatorError`
214
+ - `DictWalkResolutionError`
215
+
216
+ Use `strict=True` when you want explicit failures instead of fallback defaults.
217
+
218
+ ## Development
219
+
220
+ Run tests:
221
+
222
+ ```bash
223
+ make test
224
+ ```
225
+
226
+ Run lint/type/dependency checks:
227
+
228
+ ```bash
229
+ make lint
230
+ make type
231
+ make deptry
232
+ ```
233
+
234
+ Run everything:
235
+
236
+ ```bash
237
+ make ci
238
+ ```
239
+
240
+ Direct tox usage:
241
+
242
+ ```bash
243
+ uv run tox -e py310,py311,py312,py313,py314,lint,type,deptry
244
+ ```
@@ -0,0 +1,4 @@
1
+ from .dictwalk import dictwalk, DictWalk
2
+
3
+
4
+ __all__ = ["dictwalk", "DictWalk"]