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.
@@ -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 = "registry+https://github.com/rust-lang/crates.io-index"
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.184"
34
+ version = "0.2.186"
57
35
  source = "registry+https://github.com/rust-lang/crates.io-index"
58
- checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
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.2"
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.22.6"
69
+ version = "0.28.3"
101
70
  source = "registry+https://github.com/rust-lang/crates.io-index"
102
- checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
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.22.6"
83
+ version = "0.28.3"
119
84
  source = "registry+https://github.com/rust-lang/crates.io-index"
120
- checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
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.22.6"
93
+ version = "0.28.3"
130
94
  source = "registry+https://github.com/rust-lang/crates.io-index"
131
- checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
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.22.6"
103
+ version = "0.28.3"
140
104
  source = "registry+https://github.com/rust-lang/crates.io-index"
141
- checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
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.22.6"
115
+ version = "0.28.3"
152
116
  source = "registry+https://github.com/rust-lang/crates.io-index"
153
- checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
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.12.16"
163
+ version = "0.13.5"
206
164
  source = "registry+https://github.com/rust-lang/crates.io-index"
207
- checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
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"
@@ -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
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pygjson)
3
20
  ![PyPI - Version](https://img.shields.io/pypi/v/pygjson)
4
21
  ![GitHub License](https://img.shields.io/github/license/minefuto/pygjson)
5
22
 
@@ -45,12 +62,14 @@ pygjson.valid(JSON) # True
45
62
 
46
63
  ### Module-level functions
47
64
 
48
- | Function | Description |
49
- |-------------------------------|--------------------------------------------------------------|
50
- | `get(json, path)` | Query `json` at `path`; returns `Value` (gjson-native) |
51
- | `get(json, path, default)` | Returns `default` if path is not found (Pythonic) |
52
- | `parse(json)` | Parse the entire JSON document into a `Value` |
53
- | `valid(json)` | `True` if `json` is syntactically valid |
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)` | Sub-query relative to this value (gjson-native) |
72
- | `v.get(path, default)` | Sub-query; returns `default` if not found (Pythonic) |
73
- | `v.to_list()` | `list[Value]` for arrays |
74
- | `v.to_dict()` | `dict[str, Value]` for objects |
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
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pygjson)
13
4
  ![PyPI - Version](https://img.shields.io/pypi/v/pygjson)
14
5
  ![GitHub License](https://img.shields.io/github/license/minefuto/pygjson)
15
6
 
@@ -55,12 +46,14 @@ pygjson.valid(JSON) # True
55
46
 
56
47
  ### Module-level functions
57
48
 
58
- | Function | Description |
59
- |-------------------------------|--------------------------------------------------------------|
60
- | `get(json, path)` | Query `json` at `path`; returns `Value` (gjson-native) |
61
- | `get(json, path, default)` | Returns `default` if path is not found (Pythonic) |
62
- | `parse(json)` | Parse the entire JSON document into a `Value` |
63
- | `valid(json)` | `True` if `json` is syntactically valid |
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)` | Sub-query relative to this value (gjson-native) |
82
- | `v.get(path, default)` | Sub-query; returns `default` if not found (Pythonic) |
83
- | `v.to_list()` | `list[Value]` for arrays |
84
- | `v.to_dict()` | `dict[str, Value]` for objects |
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.2"
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<'_>) -> PyObject {
127
- self.parsed().i64().into_py(py)
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<'_>) -> PyObject {
132
- self.parsed().u64().into_py(py)
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<PyObject> {
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)?.into_py(py));
169
+ return Ok(args.get_item(0)?.unbind());
170
170
  }
171
- Ok(val.into_py(py))
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::new_bound(py);
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.into_py(py)) {
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<'_>) -> PyObject {
372
+ fn __int__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
340
373
  if self.raw_slice().starts_with('-') {
341
- self.parsed().i64().into_py(py)
374
+ Ok(self.parsed().i64().into_pyobject(py)?.into_any().unbind())
342
375
  } else {
343
- self.parsed().u64().into_py(py)
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<PyObject>> {
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().to_string().into_py(py)))
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)?.into_py(py)))
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().to_string().into_py(py);
507
- let val_obj = Py::new(py, cloned)?.into_py(py);
508
- let tup = PyTuple::new_bound(py, &[key_obj, val_obj]);
509
- Ok(Some(tup.into_py(py)))
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