pygjson 0.0.2__tar.gz → 0.0.4__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.
- pygjson-0.0.4/.github/workflows/test.yml +38 -0
- {pygjson-0.0.2 → pygjson-0.0.4}/Cargo.lock +16 -64
- pygjson-0.0.4/Cargo.toml +13 -0
- pygjson-0.0.2/README.md → pygjson-0.0.4/PKG-INFO +48 -10
- pygjson-0.0.2/PKG-INFO → pygjson-0.0.4/README.md +31 -21
- {pygjson-0.0.2 → pygjson-0.0.4}/pyproject.toml +7 -1
- {pygjson-0.0.2 → pygjson-0.0.4}/python/pygjson/__init__.py +14 -1
- {pygjson-0.0.2 → pygjson-0.0.4}/python/pygjson/_pygjson.pyi +23 -1
- {pygjson-0.0.2 → pygjson-0.0.4}/src/lib.rs +65 -20
- {pygjson-0.0.2 → pygjson-0.0.4}/tests/test_pygjson.py +69 -1
- pygjson-0.0.2/Cargo.toml +0 -13
- {pygjson-0.0.2 → pygjson-0.0.4}/.github/workflows/publish.yml +0 -0
- {pygjson-0.0.2 → pygjson-0.0.4}/.gitignore +0 -0
- {pygjson-0.0.2 → pygjson-0.0.4}/LICENSE +0 -0
- {pygjson-0.0.2 → pygjson-0.0.4}/python/pygjson/py.typed +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v6
|
|
19
|
+
|
|
20
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
+
uses: actions/setup-python@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Build wheel
|
|
26
|
+
uses: PyO3/maturin-action@v1
|
|
27
|
+
with:
|
|
28
|
+
command: build
|
|
29
|
+
args: --out dist
|
|
30
|
+
env:
|
|
31
|
+
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1
|
|
32
|
+
|
|
33
|
+
- name: Install wheel and pytest
|
|
34
|
+
shell: bash
|
|
35
|
+
run: pip install dist/*.whl pytest
|
|
36
|
+
|
|
37
|
+
- name: Run tests
|
|
38
|
+
run: pytest tests/ -v
|
|
@@ -2,12 +2,6 @@
|
|
|
2
2
|
# It is not intended for manual editing.
|
|
3
3
|
version = 4
|
|
4
4
|
|
|
5
|
-
[[package]]
|
|
6
|
-
name = "autocfg"
|
|
7
|
-
version = "1.5.0"
|
|
8
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
-
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
10
|
-
|
|
11
5
|
[[package]]
|
|
12
6
|
name = "cc"
|
|
13
7
|
version = "1.2.60"
|
|
@@ -18,12 +12,6 @@ dependencies = [
|
|
|
18
12
|
"shlex",
|
|
19
13
|
]
|
|
20
14
|
|
|
21
|
-
[[package]]
|
|
22
|
-
name = "cfg-if"
|
|
23
|
-
version = "1.0.4"
|
|
24
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
25
|
-
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
26
|
-
|
|
27
15
|
[[package]]
|
|
28
16
|
name = "find-msvc-tools"
|
|
29
17
|
version = "0.1.9"
|
|
@@ -33,8 +21,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
|
|
33
21
|
[[package]]
|
|
34
22
|
name = "gjson"
|
|
35
23
|
version = "0.8.1"
|
|
36
|
-
source = "
|
|
37
|
-
checksum = "43503cc176394dd30a6525f5f36e838339b8b5619be33ed9a7783841580a97b6"
|
|
24
|
+
source = "git+https://github.com/minefuto/gjson.rs?branch=claude%2Fadd-get-many-function-LUHtX#4772e994795d821a17f76f4fa11a94780bdfc8ff"
|
|
38
25
|
|
|
39
26
|
[[package]]
|
|
40
27
|
name = "heck"
|
|
@@ -42,29 +29,11 @@ version = "0.5.0"
|
|
|
42
29
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
43
30
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
44
31
|
|
|
45
|
-
[[package]]
|
|
46
|
-
name = "indoc"
|
|
47
|
-
version = "2.0.7"
|
|
48
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
-
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
50
|
-
dependencies = [
|
|
51
|
-
"rustversion",
|
|
52
|
-
]
|
|
53
|
-
|
|
54
32
|
[[package]]
|
|
55
33
|
name = "libc"
|
|
56
|
-
version = "0.2.
|
|
34
|
+
version = "0.2.186"
|
|
57
35
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
58
|
-
checksum = "
|
|
59
|
-
|
|
60
|
-
[[package]]
|
|
61
|
-
name = "memoffset"
|
|
62
|
-
version = "0.9.1"
|
|
63
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
64
|
-
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
65
|
-
dependencies = [
|
|
66
|
-
"autocfg",
|
|
67
|
-
]
|
|
36
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
68
37
|
|
|
69
38
|
[[package]]
|
|
70
39
|
name = "once_cell"
|
|
@@ -89,7 +58,7 @@ dependencies = [
|
|
|
89
58
|
|
|
90
59
|
[[package]]
|
|
91
60
|
name = "pygjson"
|
|
92
|
-
version = "0.0.
|
|
61
|
+
version = "0.0.4"
|
|
93
62
|
dependencies = [
|
|
94
63
|
"gjson",
|
|
95
64
|
"pyo3",
|
|
@@ -97,38 +66,33 @@ dependencies = [
|
|
|
97
66
|
|
|
98
67
|
[[package]]
|
|
99
68
|
name = "pyo3"
|
|
100
|
-
version = "0.
|
|
69
|
+
version = "0.28.3"
|
|
101
70
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
102
|
-
checksum = "
|
|
71
|
+
checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
|
|
103
72
|
dependencies = [
|
|
104
|
-
"cfg-if",
|
|
105
|
-
"indoc",
|
|
106
73
|
"libc",
|
|
107
|
-
"memoffset",
|
|
108
74
|
"once_cell",
|
|
109
75
|
"portable-atomic",
|
|
110
76
|
"pyo3-build-config",
|
|
111
77
|
"pyo3-ffi",
|
|
112
78
|
"pyo3-macros",
|
|
113
|
-
"unindent",
|
|
114
79
|
]
|
|
115
80
|
|
|
116
81
|
[[package]]
|
|
117
82
|
name = "pyo3-build-config"
|
|
118
|
-
version = "0.
|
|
83
|
+
version = "0.28.3"
|
|
119
84
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
120
|
-
checksum = "
|
|
85
|
+
checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
|
|
121
86
|
dependencies = [
|
|
122
|
-
"once_cell",
|
|
123
87
|
"python3-dll-a",
|
|
124
88
|
"target-lexicon",
|
|
125
89
|
]
|
|
126
90
|
|
|
127
91
|
[[package]]
|
|
128
92
|
name = "pyo3-ffi"
|
|
129
|
-
version = "0.
|
|
93
|
+
version = "0.28.3"
|
|
130
94
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
131
|
-
checksum = "
|
|
95
|
+
checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
|
|
132
96
|
dependencies = [
|
|
133
97
|
"libc",
|
|
134
98
|
"pyo3-build-config",
|
|
@@ -136,9 +100,9 @@ dependencies = [
|
|
|
136
100
|
|
|
137
101
|
[[package]]
|
|
138
102
|
name = "pyo3-macros"
|
|
139
|
-
version = "0.
|
|
103
|
+
version = "0.28.3"
|
|
140
104
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
141
|
-
checksum = "
|
|
105
|
+
checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
|
|
142
106
|
dependencies = [
|
|
143
107
|
"proc-macro2",
|
|
144
108
|
"pyo3-macros-backend",
|
|
@@ -148,9 +112,9 @@ dependencies = [
|
|
|
148
112
|
|
|
149
113
|
[[package]]
|
|
150
114
|
name = "pyo3-macros-backend"
|
|
151
|
-
version = "0.
|
|
115
|
+
version = "0.28.3"
|
|
152
116
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
153
|
-
checksum = "
|
|
117
|
+
checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
|
|
154
118
|
dependencies = [
|
|
155
119
|
"heck",
|
|
156
120
|
"proc-macro2",
|
|
@@ -177,12 +141,6 @@ dependencies = [
|
|
|
177
141
|
"proc-macro2",
|
|
178
142
|
]
|
|
179
143
|
|
|
180
|
-
[[package]]
|
|
181
|
-
name = "rustversion"
|
|
182
|
-
version = "1.0.22"
|
|
183
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
184
|
-
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
185
|
-
|
|
186
144
|
[[package]]
|
|
187
145
|
name = "shlex"
|
|
188
146
|
version = "1.3.0"
|
|
@@ -202,18 +160,12 @@ dependencies = [
|
|
|
202
160
|
|
|
203
161
|
[[package]]
|
|
204
162
|
name = "target-lexicon"
|
|
205
|
-
version = "0.
|
|
163
|
+
version = "0.13.5"
|
|
206
164
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
207
|
-
checksum = "
|
|
165
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
208
166
|
|
|
209
167
|
[[package]]
|
|
210
168
|
name = "unicode-ident"
|
|
211
169
|
version = "1.0.24"
|
|
212
170
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
213
171
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
214
|
-
|
|
215
|
-
[[package]]
|
|
216
|
-
name = "unindent"
|
|
217
|
-
version = "0.2.4"
|
|
218
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
219
|
-
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
pygjson-0.0.4/Cargo.toml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "pygjson"
|
|
3
|
+
version = "0.0.4"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "_pygjson"
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
pyo3 = { version = "0.28.3", features = ["extension-module", "abi3-py310", "generate-import-lib"] }
|
|
13
|
+
gjson = { git = "https://github.com/minefuto/gjson.rs", branch = "claude/add-get-many-function-LUHtX" }
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pygjson
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Summary: Python bindings for gjson.rs - fast JSON path queries
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
16
|
+
|
|
1
17
|
# pygjson
|
|
2
18
|
|
|
19
|
+

|
|
3
20
|

|
|
4
21
|

|
|
5
22
|
|
|
@@ -45,12 +62,14 @@ pygjson.valid(JSON) # True
|
|
|
45
62
|
|
|
46
63
|
### Module-level functions
|
|
47
64
|
|
|
48
|
-
| Function
|
|
49
|
-
|
|
50
|
-
| `get(json, path)`
|
|
51
|
-
| `get(json, path, default)`
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
65
|
+
| Function | Description |
|
|
66
|
+
|----------------------------------|------------------------------------------------------------------------|
|
|
67
|
+
| `get(json, path)` | Query `json` at `path`; returns `Value` |
|
|
68
|
+
| `get(json, path, default)` | Returns `default` if path is not found |
|
|
69
|
+
| `get_many(json, paths)` | Query `json` at each path; returns `list[Value]` |
|
|
70
|
+
| `get_many(json, paths, default)` | Like `get_many` but replaces missing values with `default` |
|
|
71
|
+
| `parse(json)` | Parse the entire JSON document into a `Value` |
|
|
72
|
+
| `valid(json)` | `True` if `json` is syntactically valid |
|
|
54
73
|
|
|
55
74
|
### Value
|
|
56
75
|
|
|
@@ -68,10 +87,12 @@ pygjson.valid(JSON) # True
|
|
|
68
87
|
| `v.to_float()` | 64-bit float |
|
|
69
88
|
| `v.to_bool()` | `True` only for the JSON literal `true` |
|
|
70
89
|
| `v.json()` | Raw JSON text for this value |
|
|
71
|
-
| `v.get(path)`
|
|
72
|
-
| `v.get(path, default)`
|
|
73
|
-
| `v.
|
|
74
|
-
| `v.
|
|
90
|
+
| `v.get(path)` | Sub-query relative to this value |
|
|
91
|
+
| `v.get(path, default)` | Sub-query; returns `default` if not found |
|
|
92
|
+
| `v.get_many(paths)` | Sub-query at multiple paths; returns `list[Value]` |
|
|
93
|
+
| `v.get_many(paths, default)` | Sub-query at multiple paths; returns `list[Value]` but replaces missing values with `default` |
|
|
94
|
+
| `v.to_list()` | `list[Value]` for arrays |
|
|
95
|
+
| `v.to_dict()` | `dict[str, Value]` for objects |
|
|
75
96
|
|
|
76
97
|
**Pythonic methods** — follow standard Python protocols:
|
|
77
98
|
|
|
@@ -164,6 +185,22 @@ for k, v in name.items():
|
|
|
164
185
|
|
|
165
186
|
# Chained queries
|
|
166
187
|
parse(JSON).get("name").get("first") # Value("Tom")
|
|
188
|
+
|
|
189
|
+
# Fetch multiple paths in one call
|
|
190
|
+
pygjson.get_many(JSON, ["name.first", "age", "children.1"])
|
|
191
|
+
# [Value(Tom), Value(37), Value(Alex)]
|
|
192
|
+
|
|
193
|
+
# Missing paths return Value(exists=False) without a default …
|
|
194
|
+
pygjson.get_many(JSON, ["name.first", "no.such.path"])
|
|
195
|
+
# [Value(Tom), Value()]
|
|
196
|
+
|
|
197
|
+
# … or your chosen default when one is provided
|
|
198
|
+
pygjson.get_many(JSON, ["name.first", "no.such.path"], default=None)
|
|
199
|
+
# [Value(Tom), None]
|
|
200
|
+
|
|
201
|
+
# Value.get_many for sub-queries relative to a parsed document
|
|
202
|
+
parse(JSON).get_many(["name.first", "name.last"])
|
|
203
|
+
# [Value(Tom), Value(Anderson)]
|
|
167
204
|
```
|
|
168
205
|
|
|
169
206
|
## Path syntax
|
|
@@ -175,3 +212,4 @@ For the full path / query / modifier syntax see the upstream
|
|
|
175
212
|
## License
|
|
176
213
|
|
|
177
214
|
MIT
|
|
215
|
+
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pygjson
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Classifier: Programming Language :: Rust
|
|
5
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Summary: Python bindings for gjson.rs - fast JSON path queries
|
|
8
|
-
Requires-Python: >=3.10
|
|
9
|
-
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
10
|
-
|
|
11
1
|
# pygjson
|
|
12
2
|
|
|
3
|
+

|
|
13
4
|

|
|
14
5
|

|
|
15
6
|
|
|
@@ -55,12 +46,14 @@ pygjson.valid(JSON) # True
|
|
|
55
46
|
|
|
56
47
|
### Module-level functions
|
|
57
48
|
|
|
58
|
-
| Function
|
|
59
|
-
|
|
60
|
-
| `get(json, path)`
|
|
61
|
-
| `get(json, path, default)`
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
49
|
+
| Function | Description |
|
|
50
|
+
|----------------------------------|------------------------------------------------------------------------|
|
|
51
|
+
| `get(json, path)` | Query `json` at `path`; returns `Value` |
|
|
52
|
+
| `get(json, path, default)` | Returns `default` if path is not found |
|
|
53
|
+
| `get_many(json, paths)` | Query `json` at each path; returns `list[Value]` |
|
|
54
|
+
| `get_many(json, paths, default)` | Like `get_many` but replaces missing values with `default` |
|
|
55
|
+
| `parse(json)` | Parse the entire JSON document into a `Value` |
|
|
56
|
+
| `valid(json)` | `True` if `json` is syntactically valid |
|
|
64
57
|
|
|
65
58
|
### Value
|
|
66
59
|
|
|
@@ -78,10 +71,12 @@ pygjson.valid(JSON) # True
|
|
|
78
71
|
| `v.to_float()` | 64-bit float |
|
|
79
72
|
| `v.to_bool()` | `True` only for the JSON literal `true` |
|
|
80
73
|
| `v.json()` | Raw JSON text for this value |
|
|
81
|
-
| `v.get(path)`
|
|
82
|
-
| `v.get(path, default)`
|
|
83
|
-
| `v.
|
|
84
|
-
| `v.
|
|
74
|
+
| `v.get(path)` | Sub-query relative to this value |
|
|
75
|
+
| `v.get(path, default)` | Sub-query; returns `default` if not found |
|
|
76
|
+
| `v.get_many(paths)` | Sub-query at multiple paths; returns `list[Value]` |
|
|
77
|
+
| `v.get_many(paths, default)` | Sub-query at multiple paths; returns `list[Value]` but replaces missing values with `default` |
|
|
78
|
+
| `v.to_list()` | `list[Value]` for arrays |
|
|
79
|
+
| `v.to_dict()` | `dict[str, Value]` for objects |
|
|
85
80
|
|
|
86
81
|
**Pythonic methods** — follow standard Python protocols:
|
|
87
82
|
|
|
@@ -174,6 +169,22 @@ for k, v in name.items():
|
|
|
174
169
|
|
|
175
170
|
# Chained queries
|
|
176
171
|
parse(JSON).get("name").get("first") # Value("Tom")
|
|
172
|
+
|
|
173
|
+
# Fetch multiple paths in one call
|
|
174
|
+
pygjson.get_many(JSON, ["name.first", "age", "children.1"])
|
|
175
|
+
# [Value(Tom), Value(37), Value(Alex)]
|
|
176
|
+
|
|
177
|
+
# Missing paths return Value(exists=False) without a default …
|
|
178
|
+
pygjson.get_many(JSON, ["name.first", "no.such.path"])
|
|
179
|
+
# [Value(Tom), Value()]
|
|
180
|
+
|
|
181
|
+
# … or your chosen default when one is provided
|
|
182
|
+
pygjson.get_many(JSON, ["name.first", "no.such.path"], default=None)
|
|
183
|
+
# [Value(Tom), None]
|
|
184
|
+
|
|
185
|
+
# Value.get_many for sub-queries relative to a parsed document
|
|
186
|
+
parse(JSON).get_many(["name.first", "name.last"])
|
|
187
|
+
# [Value(Tom), Value(Anderson)]
|
|
177
188
|
```
|
|
178
189
|
|
|
179
190
|
## Path syntax
|
|
@@ -185,4 +196,3 @@ For the full path / query / modifier syntax see the upstream
|
|
|
185
196
|
## License
|
|
186
197
|
|
|
187
198
|
MIT
|
|
188
|
-
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pygjson"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.4"
|
|
8
8
|
description = "Python bindings for gjson.rs - fast JSON path queries"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -12,6 +12,12 @@ readme = "README.md"
|
|
|
12
12
|
classifiers = [
|
|
13
13
|
"Programming Language :: Rust",
|
|
14
14
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
15
21
|
]
|
|
16
22
|
|
|
17
23
|
[tool.maturin]
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from ._pygjson import Kind, Value
|
|
4
4
|
from ._pygjson import get as _get, parse, valid
|
|
5
|
+
from ._pygjson import get_many as _get_many
|
|
5
6
|
|
|
6
|
-
__all__ = ["Kind", "Value", "get", "parse", "valid"]
|
|
7
|
+
__all__ = ["Kind", "Value", "get", "get_many", "parse", "valid"]
|
|
7
8
|
|
|
8
9
|
_MISSING = object()
|
|
9
10
|
|
|
@@ -18,3 +19,15 @@ def get(json: str, path: str, default=_MISSING):
|
|
|
18
19
|
if not result.exists() and default is not _MISSING:
|
|
19
20
|
return default
|
|
20
21
|
return result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_many(json: str, paths, default=_MISSING):
|
|
25
|
+
"""Get the values at each path in ``paths`` from the given JSON document.
|
|
26
|
+
|
|
27
|
+
If ``default`` is given, any path that is not found returns ``default``
|
|
28
|
+
instead of a ``Value`` with ``exists=False``.
|
|
29
|
+
"""
|
|
30
|
+
results = _get_many(json, list(paths))
|
|
31
|
+
if default is _MISSING:
|
|
32
|
+
return results
|
|
33
|
+
return [r if r.exists() else default for r in results]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Type stubs for the pygjson native extension module."""
|
|
2
2
|
|
|
3
|
-
from typing import Dict, Iterator, List, Tuple, TypeVar, Union, overload
|
|
3
|
+
from typing import Dict, Iterator, List, Sequence, Tuple, TypeVar, Union, overload
|
|
4
4
|
|
|
5
5
|
T = TypeVar("T")
|
|
6
6
|
|
|
@@ -94,6 +94,17 @@ class Value:
|
|
|
94
94
|
instead of a ``Value`` with ``exists=False``.
|
|
95
95
|
"""
|
|
96
96
|
|
|
97
|
+
@overload
|
|
98
|
+
def get_many(self, paths: Sequence[str]) -> List["Value"]: ...
|
|
99
|
+
@overload
|
|
100
|
+
def get_many(self, paths: Sequence[str], default: T) -> List[Union["Value", T]]: ...
|
|
101
|
+
def get_many(self, paths: Sequence[str], default: object = ...) -> object:
|
|
102
|
+
"""Get child values at each of the given gjson paths.
|
|
103
|
+
|
|
104
|
+
If ``default`` is given, any path that is not found returns ``default``
|
|
105
|
+
instead of a ``Value`` with ``exists=False``.
|
|
106
|
+
"""
|
|
107
|
+
|
|
97
108
|
def to_list(self) -> List["Value"]:
|
|
98
109
|
"""Return the value as a list of :class:`Value` objects."""
|
|
99
110
|
|
|
@@ -142,6 +153,17 @@ def get(json: str, path: str, default: object = ...) -> object:
|
|
|
142
153
|
instead of a ``Value`` with ``exists=False``.
|
|
143
154
|
"""
|
|
144
155
|
|
|
156
|
+
@overload
|
|
157
|
+
def get_many(json: str, paths: Sequence[str]) -> List[Value]: ...
|
|
158
|
+
@overload
|
|
159
|
+
def get_many(json: str, paths: Sequence[str], default: T) -> List[Union[Value, T]]: ...
|
|
160
|
+
def get_many(json: str, paths: Sequence[str], default: object = ...) -> object:
|
|
161
|
+
"""Get the values at each path in ``paths`` from the given JSON document.
|
|
162
|
+
|
|
163
|
+
If ``default`` is given, any path that is not found returns ``default``
|
|
164
|
+
instead of a ``Value`` with ``exists=False``.
|
|
165
|
+
"""
|
|
166
|
+
|
|
145
167
|
def parse(json: str) -> Value:
|
|
146
168
|
"""Parse the entire JSON document into a :class:`Value`."""
|
|
147
169
|
|
|
@@ -4,7 +4,7 @@ use pyo3::types::{PyDict, PyTuple};
|
|
|
4
4
|
use std::sync::Arc;
|
|
5
5
|
|
|
6
6
|
/// Mirror of `gjson::Kind`, exposed to Python as a class with constant attributes.
|
|
7
|
-
#[pyclass(module = "pygjson._pygjson", eq, eq_int)]
|
|
7
|
+
#[pyclass(module = "pygjson._pygjson", eq, eq_int, skip_from_py_object)]
|
|
8
8
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
9
9
|
pub enum Kind {
|
|
10
10
|
Null,
|
|
@@ -123,13 +123,13 @@ impl Value {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/// Signed integer value (`i64`).
|
|
126
|
-
fn to_int(&self, py: Python<'_>) ->
|
|
127
|
-
self.parsed().i64().
|
|
126
|
+
fn to_int(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
|
127
|
+
Ok(self.parsed().i64().into_pyobject(py)?.into_any().unbind())
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/// Unsigned integer value (`u64`).
|
|
131
|
-
fn to_uint(&self, py: Python<'_>) ->
|
|
132
|
-
self.parsed().u64().
|
|
131
|
+
fn to_uint(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
|
132
|
+
Ok(self.parsed().u64().into_pyobject(py)?.into_any().unbind())
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/// Floating point value.
|
|
@@ -157,7 +157,7 @@ impl Value {
|
|
|
157
157
|
py: Python<'_>,
|
|
158
158
|
path: &str,
|
|
159
159
|
args: &Bound<'_, pyo3::types::PyTuple>,
|
|
160
|
-
) -> PyResult<
|
|
160
|
+
) -> PyResult<Py<PyAny>> {
|
|
161
161
|
if args.len() > 1 {
|
|
162
162
|
return Err(pyo3::exceptions::PyTypeError::new_err(
|
|
163
163
|
"get() takes at most 2 positional arguments",
|
|
@@ -166,9 +166,42 @@ impl Value {
|
|
|
166
166
|
let parsed = self.parsed();
|
|
167
167
|
let val = Value::child(&self.raw, parsed.get(path));
|
|
168
168
|
if !val.exists && !args.is_empty() {
|
|
169
|
-
return Ok(args.get_item(0)?.
|
|
169
|
+
return Ok(args.get_item(0)?.unbind());
|
|
170
170
|
}
|
|
171
|
-
Ok(
|
|
171
|
+
Ok(Py::new(py, val)?.into_any())
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Get child values at each of the given gjson paths.
|
|
175
|
+
///
|
|
176
|
+
/// If `default` is given and a path is not found, returns `default` in
|
|
177
|
+
/// that position instead of a `Value` with `exists=False`.
|
|
178
|
+
#[pyo3(signature = (paths, *args))]
|
|
179
|
+
fn get_many(
|
|
180
|
+
&self,
|
|
181
|
+
py: Python<'_>,
|
|
182
|
+
paths: Vec<String>,
|
|
183
|
+
args: &Bound<'_, pyo3::types::PyTuple>,
|
|
184
|
+
) -> PyResult<Py<PyAny>> {
|
|
185
|
+
if args.len() > 1 {
|
|
186
|
+
return Err(pyo3::exceptions::PyTypeError::new_err(
|
|
187
|
+
"get_many() takes at most 2 positional arguments",
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
let path_refs: Vec<&str> = paths.iter().map(String::as_str).collect();
|
|
191
|
+
let values: Vec<Value> = gjson::get_many(self.raw_slice(), &path_refs)
|
|
192
|
+
.into_iter()
|
|
193
|
+
.map(|v| Value::child(&self.raw, v))
|
|
194
|
+
.collect();
|
|
195
|
+
let has_default = !args.is_empty();
|
|
196
|
+
let list = pyo3::types::PyList::empty(py);
|
|
197
|
+
for v in values {
|
|
198
|
+
if has_default && !v.exists {
|
|
199
|
+
list.append(args.get_item(0)?)?;
|
|
200
|
+
} else {
|
|
201
|
+
list.append(Py::new(py, v)?)?;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
Ok(list.into_any().unbind())
|
|
172
205
|
}
|
|
173
206
|
|
|
174
207
|
/// Return the value as a list of `Value` objects (empty for non-arrays).
|
|
@@ -184,7 +217,7 @@ impl Value {
|
|
|
184
217
|
|
|
185
218
|
/// Return the value as a `dict[str, Value]` (empty for non-objects).
|
|
186
219
|
fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
|
|
187
|
-
let dict = PyDict::
|
|
220
|
+
let dict = PyDict::new(py);
|
|
188
221
|
// Only iterate as a map for objects; arrays would yield empty keys.
|
|
189
222
|
if matches!(self.kind, Kind::Object) {
|
|
190
223
|
let parsed = self.parsed();
|
|
@@ -192,7 +225,7 @@ impl Value {
|
|
|
192
225
|
parsed.each(|k, v| {
|
|
193
226
|
let key = k.str().to_string();
|
|
194
227
|
let child = Value::child(&self.raw, v);
|
|
195
|
-
match dict.set_item(key, child
|
|
228
|
+
match dict.set_item(key, child) {
|
|
196
229
|
Ok(()) => true,
|
|
197
230
|
Err(e) => {
|
|
198
231
|
err = Some(e);
|
|
@@ -336,11 +369,11 @@ impl Value {
|
|
|
336
369
|
)
|
|
337
370
|
}
|
|
338
371
|
|
|
339
|
-
fn __int__(&self, py: Python<'_>) ->
|
|
372
|
+
fn __int__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
|
340
373
|
if self.raw_slice().starts_with('-') {
|
|
341
|
-
self.parsed().i64().
|
|
374
|
+
Ok(self.parsed().i64().into_pyobject(py)?.into_any().unbind())
|
|
342
375
|
} else {
|
|
343
|
-
self.parsed().u64().
|
|
376
|
+
Ok(self.parsed().u64().into_pyobject(py)?.into_any().unbind())
|
|
344
377
|
}
|
|
345
378
|
}
|
|
346
379
|
|
|
@@ -465,7 +498,7 @@ impl ValueIterator {
|
|
|
465
498
|
slf
|
|
466
499
|
}
|
|
467
500
|
|
|
468
|
-
fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<
|
|
501
|
+
fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
|
|
469
502
|
let i = self.cursor;
|
|
470
503
|
match self.mode {
|
|
471
504
|
IterMode::Strings => {
|
|
@@ -473,7 +506,7 @@ impl ValueIterator {
|
|
|
473
506
|
return Ok(None);
|
|
474
507
|
}
|
|
475
508
|
self.cursor += 1;
|
|
476
|
-
Ok(Some(self.strings[i].as_ref().
|
|
509
|
+
Ok(Some(self.strings[i].as_ref().into_pyobject(py)?.into_any().unbind()))
|
|
477
510
|
}
|
|
478
511
|
IterMode::Values => {
|
|
479
512
|
if i >= self.children.len() {
|
|
@@ -488,7 +521,7 @@ impl ValueIterator {
|
|
|
488
521
|
kind: v.kind,
|
|
489
522
|
exists: v.exists,
|
|
490
523
|
};
|
|
491
|
-
Ok(Some(Py::new(py, cloned)?.
|
|
524
|
+
Ok(Some(Py::new(py, cloned)?.into_any()))
|
|
492
525
|
}
|
|
493
526
|
IterMode::Items => {
|
|
494
527
|
if i >= self.children.len() {
|
|
@@ -503,10 +536,10 @@ impl ValueIterator {
|
|
|
503
536
|
kind: v.kind,
|
|
504
537
|
exists: v.exists,
|
|
505
538
|
};
|
|
506
|
-
let key_obj = self.strings[i].as_ref().
|
|
507
|
-
let val_obj = Py::new(py, cloned)?.
|
|
508
|
-
let tup = PyTuple::
|
|
509
|
-
Ok(Some(tup.
|
|
539
|
+
let key_obj = self.strings[i].as_ref().into_pyobject(py)?;
|
|
540
|
+
let val_obj = Py::new(py, cloned)?.into_bound(py).into_any();
|
|
541
|
+
let tup = PyTuple::new(py, [key_obj.into_any(), val_obj])?;
|
|
542
|
+
Ok(Some(tup.into_any().unbind()))
|
|
510
543
|
}
|
|
511
544
|
}
|
|
512
545
|
}
|
|
@@ -646,6 +679,17 @@ fn valid(json: &str) -> bool {
|
|
|
646
679
|
gjson::valid(json)
|
|
647
680
|
}
|
|
648
681
|
|
|
682
|
+
/// Get the values at each path in `paths` from the given JSON document.
|
|
683
|
+
#[pyfunction]
|
|
684
|
+
fn get_many(json: &str, paths: Vec<String>) -> Vec<Value> {
|
|
685
|
+
let raw: Arc<str> = Arc::from(json);
|
|
686
|
+
let path_refs: Vec<&str> = paths.iter().map(String::as_str).collect();
|
|
687
|
+
gjson::get_many(&raw, &path_refs)
|
|
688
|
+
.into_iter()
|
|
689
|
+
.map(|v| Value::child(&raw, v))
|
|
690
|
+
.collect()
|
|
691
|
+
}
|
|
692
|
+
|
|
649
693
|
#[pymodule]
|
|
650
694
|
fn _pygjson(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
651
695
|
m.add_class::<Kind>()?;
|
|
@@ -657,5 +701,6 @@ fn _pygjson(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
657
701
|
m.add_function(wrap_pyfunction!(get, m)?)?;
|
|
658
702
|
m.add_function(wrap_pyfunction!(parse, m)?)?;
|
|
659
703
|
m.add_function(wrap_pyfunction!(valid, m)?)?;
|
|
704
|
+
m.add_function(wrap_pyfunction!(get_many, m)?)?;
|
|
660
705
|
Ok(())
|
|
661
706
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pygjson
|
|
2
|
-
from pygjson import Kind, Value, get, parse, valid
|
|
2
|
+
from pygjson import Kind, Value, get, get_many, parse, valid
|
|
3
3
|
|
|
4
4
|
JSON = """{
|
|
5
5
|
"name": {"first": "Tom", "last": "Anderson"},
|
|
@@ -309,3 +309,71 @@ def test_contains_non_collection_raises():
|
|
|
309
309
|
_ = "x" in get(JSON, "age")
|
|
310
310
|
with pytest.raises(TypeError):
|
|
311
311
|
_ = "x" in get(JSON, "name.first")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_get_many_module_level_basic():
|
|
315
|
+
results = get_many(JSON, ["name.first", "age", "children.1"])
|
|
316
|
+
assert len(results) == 3
|
|
317
|
+
assert str(results[0]) == "Tom"
|
|
318
|
+
assert int(results[1]) == 37
|
|
319
|
+
assert str(results[2]) == "Alex"
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def test_get_many_missing_no_default():
|
|
323
|
+
results = get_many(JSON, ["name.first", "no.such.path"])
|
|
324
|
+
assert len(results) == 2
|
|
325
|
+
assert str(results[0]) == "Tom"
|
|
326
|
+
assert isinstance(results[1], Value)
|
|
327
|
+
assert not results[1].exists()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def test_get_many_missing_with_default_none():
|
|
331
|
+
results = get_many(JSON, ["name.first", "no.such.path"], default=None)
|
|
332
|
+
assert str(results[0]) == "Tom"
|
|
333
|
+
assert results[1] is None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def test_get_many_missing_with_default_value():
|
|
337
|
+
results = get_many(JSON, ["name.first", "no.such.path"], default=42)
|
|
338
|
+
assert str(results[0]) == "Tom"
|
|
339
|
+
assert results[1] == 42
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def test_get_many_empty_paths():
|
|
343
|
+
results = get_many(JSON, [])
|
|
344
|
+
assert results == []
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def test_get_many_value_method():
|
|
348
|
+
root = parse(JSON)
|
|
349
|
+
results = root.get_many(["name.first", "name.last"])
|
|
350
|
+
assert len(results) == 2
|
|
351
|
+
assert str(results[0]) == "Tom"
|
|
352
|
+
assert str(results[1]) == "Anderson"
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def test_get_many_value_method_missing_no_default():
|
|
356
|
+
root = parse(JSON)
|
|
357
|
+
results = root.get_many(["name.first", "no.such.path"])
|
|
358
|
+
assert len(results) == 2
|
|
359
|
+
assert str(results[0]) == "Tom"
|
|
360
|
+
assert isinstance(results[1], Value)
|
|
361
|
+
assert not results[1].exists()
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def test_get_many_value_method_missing_with_default_none():
|
|
365
|
+
root = parse(JSON)
|
|
366
|
+
results = root.get_many(["name.first", "no.such.path"], None)
|
|
367
|
+
assert str(results[0]) == "Tom"
|
|
368
|
+
assert results[1] is None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_get_many_value_method_missing_with_default_value():
|
|
372
|
+
root = parse(JSON)
|
|
373
|
+
results = root.get_many(["name.first", "no.such.path"], 42)
|
|
374
|
+
assert str(results[0]) == "Tom"
|
|
375
|
+
assert results[1] == 42
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def test_get_many_module_export():
|
|
379
|
+
assert pygjson.get_many is get_many
|
pygjson-0.0.2/Cargo.toml
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "pygjson"
|
|
3
|
-
version = "0.0.2"
|
|
4
|
-
edition = "2021"
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
|
|
7
|
-
[lib]
|
|
8
|
-
name = "_pygjson"
|
|
9
|
-
crate-type = ["cdylib"]
|
|
10
|
-
|
|
11
|
-
[dependencies]
|
|
12
|
-
pyo3 = { version = "0.22", features = ["extension-module", "abi3-py310", "generate-import-lib"] }
|
|
13
|
-
gjson = "0.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|