jsonatapy 2.2.12__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.12 → jsonatapy-2.2.14}/CHANGELOG.md +94 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/Cargo.lock +6 -26
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/Cargo.toml +4 -3
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/PKG-INFO +7 -7
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/README.md +4 -4
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/pyproject.toml +6 -4
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/__main__.py +1 -1
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/builtins.rs +63 -2
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/capi.rs +1 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/evaluator.rs +114 -9
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/functions.rs +448 -2
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/lib.rs +2 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/value.rs +67 -3
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/vm.rs +22 -1
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/integration_test.rs +314 -1
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.gitignore +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.gitmodules +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/.gitignore +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/memories/architecture.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/memories/conventions.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/memories/core.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/memories/memory_maintenance.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/memories/suggested_commands.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/memories/task_completion.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/.serena/memories/tech_stack.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/CONTRIBUTORS.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/LICENSE +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/SECURITY.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/benches/evaluator_bench.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/bindings/c/README.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/bindings/c/examples/smoke.c +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/bindings/c/jsonata.h +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/examples/evaluator_demo.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/examples/host_functions.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/examples/parser_demo.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/examples/simd_json_bench.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/__init__.py +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/_cli/__init__.py +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/_cli/bindings.py +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/_cli/error_format.py +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/_cli/mcp_server.py +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/_cli/resolve.py +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/_cli/run.py +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/python/jsonatapy/py.typed +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/allocator.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/ast.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/ast_transform.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/bin/jsonata/bindings.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/bin/jsonata/error_format.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/bin/jsonata/main.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/bin/jsonata/resolve.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/compiler.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/datetime.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/expression.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/lazy.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/parser.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/pystack.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/src/signature.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/study/cli_fixtures.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/study/cli_fixtures_testdata.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/study/cli_spec.md +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/array_flags_test.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/cli_fixtures_test.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/cli_test.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/common/mod.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/datetime_picture_suite.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/deep_value_drop_test.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/evaluator_reuse_test.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/array_flags_differential.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/builtin_arity.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/builtin_differential.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/builtin_signatures.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/fastpath_differential.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/fastpath_known_divergences.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/path_semantics_combinations.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/path_semantics_combinations_known.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/fixtures/path_semantics_differential.json +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/host_functions_test.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/lambda_closure_suite.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/parent_and_focus_binding_suite.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/path_semantics_test.rs +0 -0
- {jsonatapy-2.2.12 → jsonatapy-2.2.14}/tests/step_cache_test.rs +0 -0
|
@@ -19,6 +19,100 @@ 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
|
+
|
|
67
|
+
## [2.2.13] - 2026-10-24
|
|
68
|
+
|
|
69
|
+
**Upgrade notes**
|
|
70
|
+
|
|
71
|
+
- **Breaking for Rust code that builds `EvaluatorOptions` field by field.** `EvaluatorOptions`
|
|
72
|
+
has a new public `cancel` field (see Added), so a struct literal that lists every field
|
|
73
|
+
without `..Default::default()` no longer compiles. Add `cancel: None`, or end the literal with
|
|
74
|
+
`..Default::default()`. Strict semver would call this a major change. It ships in a patch
|
|
75
|
+
release because jsonata-core's version tracks the jsonata-js version it targets (2.2), and
|
|
76
|
+
code that uses `..Default::default()` is unaffected. The Python and C APIs are unchanged.
|
|
77
|
+
- **`jsonatapy[mcp]` now needs fastmcp 4.1.2 or later**, which brings `mcp` 2.x (see Changed).
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- **An evaluation can be cancelled from another thread.** `EvaluatorOptions` has a new
|
|
82
|
+
`cancel` field that takes a `CancelToken`. Calling `cancel()` on any clone of the token
|
|
83
|
+
stops the evaluation with `U1003: Evaluation cancelled` wherever `timeout_ms` is checked:
|
|
84
|
+
each node evaluated, each element a higher-order builtin hands its callback, each match
|
|
85
|
+
`$replace` hands its replacement function, and each pass of a tail-call loop. A single
|
|
86
|
+
builtin call over a large input finishes first, as it does under `timeout_ms`, and if it is
|
|
87
|
+
the last step the evaluation returns its result. jsonata-js has no cancellation, so the
|
|
88
|
+
code is in the `U` series. This is a Rust API only. See the upgrade notes above for the
|
|
89
|
+
`EvaluatorOptions` source break. (#264) Thanks to @jonking-ajar for the design, the
|
|
90
|
+
implementation and its tests.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- **The `mcp` extra now needs fastmcp 4.1.2 or later** (was 3.0). fastmcp 4 brings `mcp` 2.x
|
|
95
|
+
and replaces `httpx` with `httpx2`, so an environment that pins `mcp<2` or depends on
|
|
96
|
+
`httpx` alongside `jsonatapy[mcp]` may need updating. The `jsonatapy mcp` server itself is
|
|
97
|
+
unchanged. 4.1.2 is also the first 4.x release with the fix for GHSA-7xgj-8392-5q4r.
|
|
98
|
+
|
|
99
|
+
### Deprecated
|
|
100
|
+
|
|
101
|
+
### Removed
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
|
|
105
|
+
- **`timeout_ms` now stops `$map`, `$filter`, `$reduce`, `$single`, `$sift`, `$each` and
|
|
106
|
+
`$sort` when their callback is a builtin, host or partial function, and `$replace` when its
|
|
107
|
+
replacement function is one.** Their loops over such a function had no checkpoint, because
|
|
108
|
+
the call evaluates no node, so `$map(items, $string)` over a large array ran to the end past
|
|
109
|
+
its timeout. A lambda callback was already stopped, since its body is checked like any node.
|
|
110
|
+
An expression with `timeout_ms` set that used to finish late in one of these loops and
|
|
111
|
+
return a result now fails with D1012 instead. (#264) Thanks to @jonking-ajar, who found and
|
|
112
|
+
fixed this alongside the cancellation work.
|
|
113
|
+
|
|
114
|
+
### Security
|
|
115
|
+
|
|
22
116
|
## [2.2.12] "Gymkata" - 2026-10-20
|
|
23
117
|
|
|
24
118
|
### Added
|
|
@@ -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>=
|
|
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>=
|
|
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,13 +75,13 @@ 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>
|
|
82
82
|
|
|
83
83
|
*Click the chart for the full category-by-category tables, including jsonata-python
|
|
84
|
-
and jsonata-rs, which are left off the chart because at
|
|
84
|
+
and jsonata-rs, which are left off the chart because at 67 ms–9 s they would
|
|
85
85
|
flatten everything else into a sliver.*
|
|
86
86
|
<!-- END generated performance chart -->
|
|
87
87
|
|
|
@@ -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,13 +26,13 @@
|
|
|
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>
|
|
33
33
|
|
|
34
34
|
*Click the chart for the full category-by-category tables, including jsonata-python
|
|
35
|
-
and jsonata-rs, which are left off the chart because at
|
|
35
|
+
and jsonata-rs, which are left off the chart because at 67 ms–9 s they would
|
|
36
36
|
flatten everything else into a sliver.*
|
|
37
37
|
<!-- END generated performance chart -->
|
|
38
38
|
|
|
@@ -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>=3.
|
|
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>=3.
|
|
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
|
}
|
|
@@ -491,6 +491,7 @@ pub unsafe extern "C" fn jsonata_set_limits(
|
|
|
491
491
|
timeout_ms: (timeout_ms > 0).then_some(timeout_ms),
|
|
492
492
|
max_stack_depth: (max_stack_depth > 0).then_some(max_stack_depth as usize),
|
|
493
493
|
max_sequence_length: (max_sequence_length > 0).then_some(max_sequence_length as usize),
|
|
494
|
+
cancel: None,
|
|
494
495
|
};
|
|
495
496
|
clear_error();
|
|
496
497
|
0
|