jsonatapy 2.2.13__tar.gz → 2.2.14__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.
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/CHANGELOG.md +45 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/Cargo.lock +6 -26
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/Cargo.toml +4 -3
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/PKG-INFO +6 -6
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/README.md +3 -3
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/pyproject.toml +6 -4
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/__main__.py +1 -1
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/builtins.rs +63 -2
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/evaluator.rs +15 -3
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/functions.rs +448 -2
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/value.rs +67 -3
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/vm.rs +2 -1
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/integration_test.rs +158 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.gitignore +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.gitmodules +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/.gitignore +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/memories/architecture.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/memories/conventions.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/memories/core.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/memories/memory_maintenance.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/memories/suggested_commands.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/memories/task_completion.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/.serena/memories/tech_stack.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/CONTRIBUTORS.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/LICENSE +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/SECURITY.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/benches/evaluator_bench.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/bindings/c/README.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/bindings/c/examples/smoke.c +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/bindings/c/jsonata.h +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/examples/evaluator_demo.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/examples/host_functions.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/examples/parser_demo.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/examples/simd_json_bench.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/__init__.py +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/_cli/__init__.py +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/_cli/bindings.py +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/_cli/error_format.py +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/_cli/mcp_server.py +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/_cli/resolve.py +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/_cli/run.py +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/python/jsonatapy/py.typed +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/allocator.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/ast.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/ast_transform.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/bin/jsonata/bindings.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/bin/jsonata/error_format.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/bin/jsonata/main.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/bin/jsonata/resolve.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/capi.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/compiler.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/datetime.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/expression.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/lazy.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/lib.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/parser.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/pystack.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/src/signature.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/study/cli_fixtures.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/study/cli_fixtures_testdata.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/study/cli_spec.md +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/array_flags_test.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/cli_fixtures_test.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/cli_test.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/common/mod.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/datetime_picture_suite.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/deep_value_drop_test.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/evaluator_reuse_test.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/array_flags_differential.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/builtin_arity.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/builtin_differential.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/builtin_signatures.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/fastpath_differential.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/fastpath_known_divergences.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/path_semantics_combinations.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/path_semantics_combinations_known.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/fixtures/path_semantics_differential.json +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/host_functions_test.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/lambda_closure_suite.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/parent_and_focus_binding_suite.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/path_semantics_test.rs +0 -0
- {jsonatapy-2.2.13 → jsonatapy-2.2.14}/tests/step_cache_test.rs +0 -0
|
@@ -19,6 +19,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
### Security
|
|
21
21
|
|
|
22
|
+
## [2.2.14] - 2026-11-04
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Compiled regexes are reused.** The same regex literal evaluated again, such as once per element
|
|
29
|
+
under `$map`, no longer compiles again; each thread keeps up to 256.
|
|
30
|
+
- Updated Rust dependencies: `simd-json` 0.18.1 → 0.19.0, used by the default `simd` feature. (#274)
|
|
31
|
+
- **The `mcp` extra now needs Python 3.11 or later.** On Python 3.10, `pip install
|
|
32
|
+
'jsonatapy[mcp]'` installs no `fastmcp`, and `jsonatapy mcp` says the extra needs 3.11. Its
|
|
33
|
+
`dnspython` dependency (through `email-validator`) has no release for 3.10 with the fix for
|
|
34
|
+
GHSA-45c3-73f7-pv4m. The rest of `jsonatapy` still supports 3.10.
|
|
35
|
+
|
|
36
|
+
### Deprecated
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **Object keys are in JavaScript's order**: integer-like keys first in ascending order, then the
|
|
43
|
+
rest in insertion order, so `$keys({"b":1,"2":2})` is `["2","b"]` as in jsonata-js. Python
|
|
44
|
+
callers see it too: a dict an expression builds comes back in that order, while a dict passed
|
|
45
|
+
through untouched is the caller's own object and keeps its order. (#273) Thanks to
|
|
46
|
+
@Sawmonabo for finding this and for the fix.
|
|
47
|
+
- **A cancel or a timeout now stops `$distinct` and `$keys` part way.** Both compare each element
|
|
48
|
+
with every one kept so far, and over a large input that ran to the end before U1003 or D1012 was
|
|
49
|
+
seen. (#272) Thanks to @Sawmonabo for the cancel check; the timeout check followed from it.
|
|
50
|
+
- **`\d`, `\w`, `\s` and `\b` in a regex now mean what they mean in jsonata-js**: ASCII digits and
|
|
51
|
+
word characters and ECMAScript's whitespace, so `$contains("٣", /\d/)` is `false`. (#271)
|
|
52
|
+
Thanks to @Sawmonabo for finding and fixing this.
|
|
53
|
+
- **A regex with the `i` flag folds case as jsonata-js does**, by upper case rather than Unicode
|
|
54
|
+
case folding. The Kelvin sign (U+212A) and the long s (U+017F) match only themselves, so
|
|
55
|
+
`$contains("\u212A", /k/i)` and `/[\w]/i` against it are `false`; `ß` and `ẞ` no longer match,
|
|
56
|
+
nor the Ohm and Angstrom signs with `ω` and `å`; and characters outside the BMP no longer fold.
|
|
57
|
+
|
|
58
|
+
### Security
|
|
59
|
+
|
|
60
|
+
- `uv.lock`: `authlib` 1.7.2 → 1.9.1 (seven advisories), `joserfc` 1.7.3 → 1.7.7
|
|
61
|
+
(GHSA-jpxq-7h8p-2q76, GHSA-9pg5-8c7x-5hfx), and `dnspython` 2.8.0 → 2.9.0
|
|
62
|
+
(GHSA-45c3-73f7-pv4m), which no longer resolves for Python 3.10 now that the `mcp` extra needs
|
|
63
|
+
3.11 (see Changed). All three are transitive dependencies of the optional `mcp` extra, not
|
|
64
|
+
of the published crate or of the wheel, which has no runtime dependencies. Users of the `mcp`
|
|
65
|
+
extra resolve their own versions and should upgrade these. (#269, #270)
|
|
66
|
+
|
|
22
67
|
## [2.2.13] - 2026-10-24
|
|
23
68
|
|
|
24
69
|
**Upgrade notes**
|
|
@@ -620,7 +620,7 @@ dependencies = [
|
|
|
620
620
|
|
|
621
621
|
[[package]]
|
|
622
622
|
name = "jsonata-core"
|
|
623
|
-
version = "2.2.
|
|
623
|
+
version = "2.2.14"
|
|
624
624
|
dependencies = [
|
|
625
625
|
"assert_cmd",
|
|
626
626
|
"base64",
|
|
@@ -637,6 +637,7 @@ dependencies = [
|
|
|
637
637
|
"pyo3",
|
|
638
638
|
"rand",
|
|
639
639
|
"regex",
|
|
640
|
+
"regex-syntax",
|
|
640
641
|
"serde",
|
|
641
642
|
"serde_json",
|
|
642
643
|
"simd-json",
|
|
@@ -986,26 +987,6 @@ dependencies = [
|
|
|
986
987
|
"crossbeam-utils",
|
|
987
988
|
]
|
|
988
989
|
|
|
989
|
-
[[package]]
|
|
990
|
-
name = "ref-cast"
|
|
991
|
-
version = "1.0.25"
|
|
992
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
993
|
-
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
|
994
|
-
dependencies = [
|
|
995
|
-
"ref-cast-impl",
|
|
996
|
-
]
|
|
997
|
-
|
|
998
|
-
[[package]]
|
|
999
|
-
name = "ref-cast-impl"
|
|
1000
|
-
version = "1.0.25"
|
|
1001
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1002
|
-
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
|
1003
|
-
dependencies = [
|
|
1004
|
-
"proc-macro2",
|
|
1005
|
-
"quote",
|
|
1006
|
-
"syn 2.0.118",
|
|
1007
|
-
]
|
|
1008
|
-
|
|
1009
990
|
[[package]]
|
|
1010
991
|
name = "regex"
|
|
1011
992
|
version = "1.13.1"
|
|
@@ -1134,12 +1115,11 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
|
1134
1115
|
|
|
1135
1116
|
[[package]]
|
|
1136
1117
|
name = "simd-json"
|
|
1137
|
-
version = "0.
|
|
1118
|
+
version = "0.19.0"
|
|
1138
1119
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1139
|
-
checksum = "
|
|
1120
|
+
checksum = "e8f2e4d523646f7da6dde3983bd18ede0babc21c258ab708b3dd93ea0259719d"
|
|
1140
1121
|
dependencies = [
|
|
1141
1122
|
"halfbrown",
|
|
1142
|
-
"ref-cast",
|
|
1143
1123
|
"serde",
|
|
1144
1124
|
"serde_json",
|
|
1145
1125
|
"simdutf8",
|
|
@@ -1304,9 +1284,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
|
1304
1284
|
|
|
1305
1285
|
[[package]]
|
|
1306
1286
|
name = "value-trait"
|
|
1307
|
-
version = "0.12.
|
|
1287
|
+
version = "0.12.3"
|
|
1308
1288
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1309
|
-
checksum = "
|
|
1289
|
+
checksum = "545a3d7089f11f1741d12963c057adf9443fc24325110a6a6daf582e825fd51b"
|
|
1310
1290
|
dependencies = [
|
|
1311
1291
|
"float-cmp",
|
|
1312
1292
|
"halfbrown",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonata-core"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.14"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["txjmb <txjmb@users.noreply.github.com>"]
|
|
6
6
|
description = "High-performance Rust implementation of JSONata query and transformation language"
|
|
@@ -46,14 +46,15 @@ serde = { version = "1.0", features = ["derive"] }
|
|
|
46
46
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
|
47
47
|
thiserror = "2.0"
|
|
48
48
|
regex = "1.12"
|
|
49
|
+
regex-syntax = "0.8"
|
|
49
50
|
chrono = "0.4"
|
|
50
51
|
num-traits = "0.2"
|
|
51
52
|
base64 = "0.23"
|
|
52
|
-
indexmap = "2.2"
|
|
53
|
+
indexmap = "2.2.3"
|
|
53
54
|
percent-encoding = "2.3"
|
|
54
55
|
rand = "0.10"
|
|
55
56
|
stacker = "0.1"
|
|
56
|
-
simd-json = { version = "0.
|
|
57
|
+
simd-json = { version = "0.19", optional = true }
|
|
57
58
|
clap = { version = "4.6", features = ["derive"], optional = true }
|
|
58
59
|
mimalloc = { version = "0.1.52", optional = true }
|
|
59
60
|
libmimalloc-sys = { version = "0.1.49", optional = true }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonatapy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.14
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -24,12 +24,12 @@ Requires-Dist: pytest-asyncio>=0.24 ; extra == 'dev'
|
|
|
24
24
|
Requires-Dist: ruff>=0.3.0 ; extra == 'dev'
|
|
25
25
|
Requires-Dist: mypy>=1.0 ; extra == 'dev'
|
|
26
26
|
Requires-Dist: maturin>=1.0 ; extra == 'dev'
|
|
27
|
-
Requires-Dist: fastmcp>=4.1.2 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: fastmcp>=4.1.2 ; python_full_version >= '3.11' and extra == 'dev'
|
|
28
28
|
Requires-Dist: mkdocs>=1.5 ; extra == 'docs'
|
|
29
29
|
Requires-Dist: mkdocs-material>=9.0 ; extra == 'docs'
|
|
30
30
|
Requires-Dist: mkdocstrings[python]>=0.24 ; extra == 'docs'
|
|
31
31
|
Requires-Dist: pymdown-extensions>=10.0 ; extra == 'docs'
|
|
32
|
-
Requires-Dist: fastmcp>=4.1.2 ; extra == 'mcp'
|
|
32
|
+
Requires-Dist: fastmcp>=4.1.2 ; python_full_version >= '3.11' and extra == 'mcp'
|
|
33
33
|
Provides-Extra: bench
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Provides-Extra: docs
|
|
@@ -75,7 +75,7 @@ Project-URL: Repository, https://github.com/txjmb/jsonata-core
|
|
|
75
75
|
<source media="(prefers-color-scheme: dark)"
|
|
76
76
|
srcset="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-dark.svg">
|
|
77
77
|
<img width="880"
|
|
78
|
-
alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 8.
|
|
78
|
+
alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 8.2x faster than the jsonata-js reference on the geometric mean of 5 e-commerce queries, and the pure-Rust jsonata-core engine is faster still."
|
|
79
79
|
src="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-light.svg">
|
|
80
80
|
</picture>
|
|
81
81
|
</a>
|
|
@@ -156,10 +156,10 @@ let result = Evaluator::new().evaluate(&ast, &data)?;
|
|
|
156
156
|
```toml
|
|
157
157
|
# Cargo.toml
|
|
158
158
|
[dependencies]
|
|
159
|
-
jsonata-core = "2.2.
|
|
159
|
+
jsonata-core = "2.2.14" # pure Rust, no Python dependency
|
|
160
160
|
|
|
161
161
|
# Optional: disable SIMD for constrained targets
|
|
162
|
-
jsonata-core = { version = "2.2.
|
|
162
|
+
jsonata-core = { version = "2.2.14", default-features = false }
|
|
163
163
|
```
|
|
164
164
|
|
|
165
165
|
---
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<source media="(prefers-color-scheme: dark)"
|
|
27
27
|
srcset="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-dark.svg">
|
|
28
28
|
<img width="880"
|
|
29
|
-
alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 8.
|
|
29
|
+
alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 8.2x faster than the jsonata-js reference on the geometric mean of 5 e-commerce queries, and the pure-Rust jsonata-core engine is faster still."
|
|
30
30
|
src="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-light.svg">
|
|
31
31
|
</picture>
|
|
32
32
|
</a>
|
|
@@ -107,10 +107,10 @@ let result = Evaluator::new().evaluate(&ast, &data)?;
|
|
|
107
107
|
```toml
|
|
108
108
|
# Cargo.toml
|
|
109
109
|
[dependencies]
|
|
110
|
-
jsonata-core = "2.2.
|
|
110
|
+
jsonata-core = "2.2.14" # pure Rust, no Python dependency
|
|
111
111
|
|
|
112
112
|
# Optional: disable SIMD for constrained targets
|
|
113
|
-
jsonata-core = { version = "2.2.
|
|
113
|
+
jsonata-core = { version = "2.2.14", default-features = false }
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
---
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jsonatapy"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.14"
|
|
8
8
|
description = "High-performance Python/Rust implementation of JSONata query and transformation language"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "txjmb", email = "txjmb@users.noreply.github.com"}
|
|
@@ -42,10 +42,12 @@ dev = [
|
|
|
42
42
|
"ruff>=0.3.0", # Linting and formatting
|
|
43
43
|
"mypy>=1.0",
|
|
44
44
|
"maturin>=1.0",
|
|
45
|
-
"fastmcp>=4.1.2",
|
|
45
|
+
"fastmcp>=4.1.2; python_version >= '3.11'", # So `dev` installs can run the full test suite
|
|
46
46
|
]
|
|
47
47
|
mcp = [
|
|
48
|
-
|
|
48
|
+
# 3.11+ only: fastmcp pulls in dnspython through email-validator, and dnspython 2.9.0,
|
|
49
|
+
# the first release with the fix for GHSA-45c3-73f7-pv4m, dropped 3.10.
|
|
50
|
+
"fastmcp>=4.1.2; python_version >= '3.11'",
|
|
49
51
|
]
|
|
50
52
|
bench = [
|
|
51
53
|
"jsonata>=0.2.0", # jsonata-python for benchmarking
|
|
@@ -82,7 +84,7 @@ dev = [
|
|
|
82
84
|
"ruff>=0.3.0",
|
|
83
85
|
"mypy>=1.0",
|
|
84
86
|
"maturin>=1.0",
|
|
85
|
-
"fastmcp>=4.1.2",
|
|
87
|
+
"fastmcp>=4.1.2; python_version >= '3.11'",
|
|
86
88
|
]
|
|
87
89
|
|
|
88
90
|
[tool.ruff]
|
|
@@ -24,7 +24,7 @@ def _run_mcp(argv: list[str]) -> int:
|
|
|
24
24
|
from ._cli.mcp_server import serve
|
|
25
25
|
except ImportError:
|
|
26
26
|
print(
|
|
27
|
-
"error: the 'mcp' extra is not installed. Run:\n"
|
|
27
|
+
"error: the 'mcp' extra is not installed (it needs Python 3.11 or later). Run:\n"
|
|
28
28
|
' uvx --from "jsonatapy[mcp]" jsonatapy mcp\n'
|
|
29
29
|
"or: pip install jsonatapy[mcp]",
|
|
30
30
|
file=sys.stderr,
|
|
@@ -241,11 +241,17 @@ fn describe_operand(v: &JValue) -> String {
|
|
|
241
241
|
/// boundary collapses it, so a builtin passed as a callback (`$map(arr,
|
|
242
242
|
/// $keys)`) is unwrapped once, by the higher-order function's own result,
|
|
243
243
|
/// exactly as in jsonata-js.
|
|
244
|
+
///
|
|
245
|
+
/// `start_time` is when the evaluation started, if it has a timeout; the
|
|
246
|
+
/// builtins whose walk is quadratic (`$distinct`, `$keys` over an array)
|
|
247
|
+
/// check it with the cancel token per element, so neither runs on past
|
|
248
|
+
/// D1012 or U1003.
|
|
244
249
|
pub(crate) fn dispatch_pure(
|
|
245
250
|
name: &str,
|
|
246
251
|
args: &[JValue],
|
|
247
252
|
context: &JValue,
|
|
248
253
|
options: &EvaluatorOptions,
|
|
254
|
+
start_time: Option<std::time::Instant>,
|
|
249
255
|
) -> Result<JValue, EvaluatorError> {
|
|
250
256
|
// 1. Implicit context insertion. The union of what the two paths used to
|
|
251
257
|
// do separately: only the tree-walker had `fromMillis` here and
|
|
@@ -916,7 +922,9 @@ pub(crate) fn dispatch_pure(
|
|
|
916
922
|
// genuinely absent argument) moves to Undefined.
|
|
917
923
|
None | Some(JValue::Undefined) => Ok(JValue::Undefined),
|
|
918
924
|
Some(JValue::Array(arr)) if arr.len() > 1 => {
|
|
919
|
-
let mut result = functions::array::
|
|
925
|
+
let mut result = functions::array::distinct_checked(arr, || {
|
|
926
|
+
crate::evaluator::check_loop_timeout(options, start_time)
|
|
927
|
+
})?;
|
|
920
928
|
// jsonata-js builds a sequence only when the input was one.
|
|
921
929
|
if arr.sequence {
|
|
922
930
|
result.mark_sequence();
|
|
@@ -1035,6 +1043,7 @@ pub(crate) fn dispatch_pure(
|
|
|
1035
1043
|
let normalized_item = crate::evaluator::normalize_lazy(item)?;
|
|
1036
1044
|
if let JValue::Object(obj) = &normalized_item {
|
|
1037
1045
|
for key in obj.keys() {
|
|
1046
|
+
crate::evaluator::check_loop_timeout(options, start_time)?;
|
|
1038
1047
|
let k = JValue::string(key.clone());
|
|
1039
1048
|
if !all_keys.contains(&k) {
|
|
1040
1049
|
all_keys.push(k);
|
|
@@ -1608,6 +1617,7 @@ mod tests {
|
|
|
1608
1617
|
&[JValue::Undefined],
|
|
1609
1618
|
&JValue::Number(5.0),
|
|
1610
1619
|
&opts,
|
|
1620
|
+
None,
|
|
1611
1621
|
)
|
|
1612
1622
|
.expect_err("a numeric context cannot satisfy substring's first parameter");
|
|
1613
1623
|
assert!(
|
|
@@ -1625,11 +1635,62 @@ mod tests {
|
|
|
1625
1635
|
// assert on the member only one list had.
|
|
1626
1636
|
let opts = EvaluatorOptions::default();
|
|
1627
1637
|
// $fromMillis() with a numeric context formats that context.
|
|
1628
|
-
let got = dispatch_pure("fromMillis", &[], &JValue::Number(0.0), &opts)
|
|
1638
|
+
let got = dispatch_pure("fromMillis", &[], &JValue::Number(0.0), &opts, None)
|
|
1629
1639
|
.expect("fromMillis should read the context");
|
|
1630
1640
|
assert!(
|
|
1631
1641
|
matches!(&got, JValue::String(s) if s.starts_with("1970-01-01")),
|
|
1632
1642
|
"expected an epoch timestamp, got {got:?}"
|
|
1633
1643
|
);
|
|
1634
1644
|
}
|
|
1645
|
+
|
|
1646
|
+
#[test]
|
|
1647
|
+
fn distinct_and_keys_stop_on_cancel() {
|
|
1648
|
+
// Both walk their input once per element against everything kept so
|
|
1649
|
+
// far, so a cancel has to be seen inside the walk, not only after it.
|
|
1650
|
+
let token = crate::evaluator::CancelToken::new();
|
|
1651
|
+
token.cancel();
|
|
1652
|
+
let opts = EvaluatorOptions {
|
|
1653
|
+
cancel: Some(token),
|
|
1654
|
+
..Default::default()
|
|
1655
|
+
};
|
|
1656
|
+
for (name, arg) in [
|
|
1657
|
+
("distinct", serde_json::json!([1, 2, 1])),
|
|
1658
|
+
("keys", serde_json::json!([{"a": 1}, {"b": 2}])),
|
|
1659
|
+
] {
|
|
1660
|
+
let err = dispatch_pure(name, &[JValue::from(arg)], &JValue::Undefined, &opts, None)
|
|
1661
|
+
.expect_err("a cancelled evaluation must not finish the walk");
|
|
1662
|
+
assert!(
|
|
1663
|
+
err.to_string().contains("U1003"),
|
|
1664
|
+
"{name}: expected U1003, got {err}"
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
#[test]
|
|
1670
|
+
fn distinct_and_keys_stop_on_timeout() {
|
|
1671
|
+
// The same walks honour the evaluation timeout, not only a cancel.
|
|
1672
|
+
let opts = EvaluatorOptions {
|
|
1673
|
+
timeout_ms: Some(1),
|
|
1674
|
+
..Default::default()
|
|
1675
|
+
};
|
|
1676
|
+
let started = std::time::Instant::now();
|
|
1677
|
+
std::thread::sleep(std::time::Duration::from_millis(5));
|
|
1678
|
+
for (name, arg) in [
|
|
1679
|
+
("distinct", serde_json::json!([1, 2, 1])),
|
|
1680
|
+
("keys", serde_json::json!([{"a": 1}, {"b": 2}])),
|
|
1681
|
+
] {
|
|
1682
|
+
let err = dispatch_pure(
|
|
1683
|
+
name,
|
|
1684
|
+
&[JValue::from(arg)],
|
|
1685
|
+
&JValue::Undefined,
|
|
1686
|
+
&opts,
|
|
1687
|
+
Some(started),
|
|
1688
|
+
)
|
|
1689
|
+
.expect_err("a timed-out evaluation must not finish the walk");
|
|
1690
|
+
assert!(
|
|
1691
|
+
err.to_string().contains("D1012"),
|
|
1692
|
+
"{name}: expected D1012, got {err}"
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1635
1696
|
}
|
|
@@ -1396,7 +1396,7 @@ fn eval_compiled_node(
|
|
|
1396
1396
|
arg, data, vars, ctx, shape, options, start_time,
|
|
1397
1397
|
)?);
|
|
1398
1398
|
}
|
|
1399
|
-
crate::builtins::dispatch_pure(name, &evaled_args, data, options)
|
|
1399
|
+
crate::builtins::dispatch_pure(name, &evaled_args, data, options, start_time)
|
|
1400
1400
|
}
|
|
1401
1401
|
|
|
1402
1402
|
// Block: evaluate each expression in sequence, return the last value.
|
|
@@ -8249,7 +8249,13 @@ impl Evaluator {
|
|
|
8249
8249
|
// per-function logic. Only the builtins that call back into evaluation
|
|
8250
8250
|
// are still handled below.
|
|
8251
8251
|
if crate::builtins::is_pure_builtin(name) {
|
|
8252
|
-
return crate::builtins::dispatch_pure(
|
|
8252
|
+
return crate::builtins::dispatch_pure(
|
|
8253
|
+
name,
|
|
8254
|
+
&evaluated_args,
|
|
8255
|
+
data,
|
|
8256
|
+
&self.options,
|
|
8257
|
+
self.start_time,
|
|
8258
|
+
);
|
|
8253
8259
|
}
|
|
8254
8260
|
|
|
8255
8261
|
// JSONata feature: when a function is called with one fewer argument than
|
|
@@ -10757,7 +10763,13 @@ impl Evaluator {
|
|
|
10757
10763
|
// "at least 1 argument" first would wrongly reject `$zip`/`$millis`
|
|
10758
10764
|
// (arity 0) reached by reference.
|
|
10759
10765
|
if crate::builtins::is_pure_builtin(name) {
|
|
10760
|
-
return crate::builtins::dispatch_pure(
|
|
10766
|
+
return crate::builtins::dispatch_pure(
|
|
10767
|
+
name,
|
|
10768
|
+
values,
|
|
10769
|
+
context,
|
|
10770
|
+
&self.options,
|
|
10771
|
+
self.start_time,
|
|
10772
|
+
);
|
|
10761
10773
|
}
|
|
10762
10774
|
|
|
10763
10775
|
// `$eval` is evaluator-dependent but its arguments are ordinary
|