jsonatapy 2.2.2__tar.gz → 2.2.3__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.2 → jsonatapy-2.2.3}/CHANGELOG.md +31 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/Cargo.lock +167 -1
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/Cargo.toml +10 -1
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/PKG-INFO +21 -2
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/README.md +16 -1
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/pyproject.toml +12 -1
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/python/jsonatapy/__init__.py +51 -0
- jsonatapy-2.2.3/python/jsonatapy/__main__.py +46 -0
- jsonatapy-2.2.3/python/jsonatapy/_cli/__init__.py +1 -0
- jsonatapy-2.2.3/python/jsonatapy/_cli/bindings.py +51 -0
- jsonatapy-2.2.3/python/jsonatapy/_cli/error_format.py +25 -0
- jsonatapy-2.2.3/python/jsonatapy/_cli/mcp_server.py +192 -0
- jsonatapy-2.2.3/python/jsonatapy/_cli/resolve.py +85 -0
- jsonatapy-2.2.3/python/jsonatapy/_cli/run.py +204 -0
- jsonatapy-2.2.3/src/bin/jsonata/bindings.rs +62 -0
- jsonatapy-2.2.3/src/bin/jsonata/error_format.rs +47 -0
- jsonatapy-2.2.3/src/bin/jsonata/main.rs +182 -0
- jsonatapy-2.2.3/src/bin/jsonata/resolve.rs +148 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/evaluator.rs +51 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/lib.rs +53 -30
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/parser.rs +39 -0
- jsonatapy-2.2.3/study/cli_fixtures.json +138 -0
- jsonatapy-2.2.3/study/cli_fixtures_testdata.json +1 -0
- jsonatapy-2.2.3/study/cli_spec.md +99 -0
- jsonatapy-2.2.3/tests/cli_fixtures_test.rs +69 -0
- jsonatapy-2.2.3/tests/cli_test.rs +352 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.gitignore +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.gitmodules +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/.gitignore +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/memories/architecture.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/memories/conventions.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/memories/core.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/memories/memory_maintenance.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/memories/suggested_commands.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/memories/task_completion.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/memories/tech_stack.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/.serena/project.yml +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/LICENSE +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/benches/evaluator_bench.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/examples/evaluator_demo.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/examples/parser_demo.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/examples/simd_json_bench.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/python/jsonatapy/py.typed +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/ast.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/ast_transform.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/compiler.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/datetime.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/functions.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/parser/README.md +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/signature.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/value.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/src/vm.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/tests/datetime_picture_suite.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/tests/integration_test.rs +0 -0
- {jsonatapy-2.2.2 → jsonatapy-2.2.3}/tests/parent_and_focus_binding_suite.rs +0 -0
|
@@ -19,6 +19,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
### Security
|
|
21
21
|
|
|
22
|
+
## [2.2.3] - 2026-07-12
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `evaluate_json_or_none()`'s `json_str` parameter now accepts `None` (in addition to a JSON
|
|
26
|
+
string), binding the top-level context (`$`) to a true JSONata `Undefined` rather than an
|
|
27
|
+
explicit `null`. (#68)
|
|
28
|
+
- `docs/cli.md`: a full command-line reference for both `jsonata` (Rust) and `jsonatapy`
|
|
29
|
+
(Python), covering flags, input resolution, output/exit-code semantics, and the MCP
|
|
30
|
+
subcommand — previously undocumented outside internal spec/plan files. (#68)
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Release CLI binary archives are now named `jsonata-v<version>-<target>.tar.gz`/`.zip`
|
|
34
|
+
(previously `jsonata-<target>.tar.gz`/`.zip` with no version embedded).
|
|
35
|
+
|
|
36
|
+
### Deprecated
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- The Python CLI's `-n`/`--null-input` now binds `$` to a true `Undefined`, matching the Rust
|
|
42
|
+
CLI exactly (`jsonatapy -n '$'` now prints nothing, as `jsonata -n '$'` already did). It
|
|
43
|
+
previously passed an explicit JSON `null` context instead, observable only for expressions
|
|
44
|
+
referencing `$` directly. (#68)
|
|
45
|
+
- Release workflow: `publish-pypi` and `publish-crates` now require `build-cli-binaries` to
|
|
46
|
+
succeed first. Previously the two registry publishes were independent of the CLI binary
|
|
47
|
+
build, so a CLI build failure (untested in a real release prior to this) would have shipped
|
|
48
|
+
a version to PyPI/crates.io with no CLI binaries attached and no way to reuse that version
|
|
49
|
+
number.
|
|
50
|
+
|
|
51
|
+
### Security
|
|
52
|
+
|
|
22
53
|
## [2.2.2] - 2026-07-09
|
|
23
54
|
|
|
24
55
|
### Added
|
|
@@ -41,12 +41,56 @@ version = "0.1.6"
|
|
|
41
41
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
42
|
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
|
43
43
|
|
|
44
|
+
[[package]]
|
|
45
|
+
name = "anstream"
|
|
46
|
+
version = "1.0.0"
|
|
47
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
|
+
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
|
49
|
+
dependencies = [
|
|
50
|
+
"anstyle",
|
|
51
|
+
"anstyle-parse",
|
|
52
|
+
"anstyle-query",
|
|
53
|
+
"anstyle-wincon",
|
|
54
|
+
"colorchoice",
|
|
55
|
+
"is_terminal_polyfill",
|
|
56
|
+
"utf8parse",
|
|
57
|
+
]
|
|
58
|
+
|
|
44
59
|
[[package]]
|
|
45
60
|
name = "anstyle"
|
|
46
61
|
version = "1.0.14"
|
|
47
62
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
63
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
|
49
64
|
|
|
65
|
+
[[package]]
|
|
66
|
+
name = "anstyle-parse"
|
|
67
|
+
version = "1.0.0"
|
|
68
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
69
|
+
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
|
70
|
+
dependencies = [
|
|
71
|
+
"utf8parse",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[[package]]
|
|
75
|
+
name = "anstyle-query"
|
|
76
|
+
version = "1.1.5"
|
|
77
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
78
|
+
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
|
79
|
+
dependencies = [
|
|
80
|
+
"windows-sys",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[[package]]
|
|
84
|
+
name = "anstyle-wincon"
|
|
85
|
+
version = "3.0.11"
|
|
86
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
87
|
+
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|
88
|
+
dependencies = [
|
|
89
|
+
"anstyle",
|
|
90
|
+
"once_cell_polyfill",
|
|
91
|
+
"windows-sys",
|
|
92
|
+
]
|
|
93
|
+
|
|
50
94
|
[[package]]
|
|
51
95
|
name = "ar_archive_writer"
|
|
52
96
|
version = "0.5.2"
|
|
@@ -56,6 +100,21 @@ dependencies = [
|
|
|
56
100
|
"object",
|
|
57
101
|
]
|
|
58
102
|
|
|
103
|
+
[[package]]
|
|
104
|
+
name = "assert_cmd"
|
|
105
|
+
version = "2.2.2"
|
|
106
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
107
|
+
checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
|
|
108
|
+
dependencies = [
|
|
109
|
+
"anstyle",
|
|
110
|
+
"bstr",
|
|
111
|
+
"libc",
|
|
112
|
+
"predicates",
|
|
113
|
+
"predicates-core",
|
|
114
|
+
"predicates-tree",
|
|
115
|
+
"wait-timeout",
|
|
116
|
+
]
|
|
117
|
+
|
|
59
118
|
[[package]]
|
|
60
119
|
name = "autocfg"
|
|
61
120
|
version = "1.5.1"
|
|
@@ -89,6 +148,17 @@ version = "2.13.0"
|
|
|
89
148
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
90
149
|
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
|
91
150
|
|
|
151
|
+
[[package]]
|
|
152
|
+
name = "bstr"
|
|
153
|
+
version = "1.12.3"
|
|
154
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
155
|
+
checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79"
|
|
156
|
+
dependencies = [
|
|
157
|
+
"memchr",
|
|
158
|
+
"regex-automata",
|
|
159
|
+
"serde_core",
|
|
160
|
+
]
|
|
161
|
+
|
|
92
162
|
[[package]]
|
|
93
163
|
name = "bumpalo"
|
|
94
164
|
version = "3.20.3"
|
|
@@ -175,6 +245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
175
245
|
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
|
176
246
|
dependencies = [
|
|
177
247
|
"clap_builder",
|
|
248
|
+
"clap_derive",
|
|
178
249
|
]
|
|
179
250
|
|
|
180
251
|
[[package]]
|
|
@@ -183,8 +254,22 @@ version = "4.6.0"
|
|
|
183
254
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
184
255
|
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
|
185
256
|
dependencies = [
|
|
257
|
+
"anstream",
|
|
186
258
|
"anstyle",
|
|
187
259
|
"clap_lex",
|
|
260
|
+
"strsim",
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
[[package]]
|
|
264
|
+
name = "clap_derive"
|
|
265
|
+
version = "4.6.1"
|
|
266
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
267
|
+
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
|
268
|
+
dependencies = [
|
|
269
|
+
"heck",
|
|
270
|
+
"proc-macro2",
|
|
271
|
+
"quote",
|
|
272
|
+
"syn",
|
|
188
273
|
]
|
|
189
274
|
|
|
190
275
|
[[package]]
|
|
@@ -193,6 +278,12 @@ version = "1.1.0"
|
|
|
193
278
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
194
279
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
|
195
280
|
|
|
281
|
+
[[package]]
|
|
282
|
+
name = "colorchoice"
|
|
283
|
+
version = "1.0.5"
|
|
284
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
285
|
+
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
286
|
+
|
|
196
287
|
[[package]]
|
|
197
288
|
name = "core-foundation-sys"
|
|
198
289
|
version = "0.8.7"
|
|
@@ -274,6 +365,12 @@ version = "0.2.4"
|
|
|
274
365
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
275
366
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
|
276
367
|
|
|
368
|
+
[[package]]
|
|
369
|
+
name = "difflib"
|
|
370
|
+
version = "0.4.0"
|
|
371
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
372
|
+
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
|
373
|
+
|
|
277
374
|
[[package]]
|
|
278
375
|
name = "either"
|
|
279
376
|
version = "1.16.0"
|
|
@@ -455,6 +552,12 @@ dependencies = [
|
|
|
455
552
|
"hashbrown 0.17.1",
|
|
456
553
|
]
|
|
457
554
|
|
|
555
|
+
[[package]]
|
|
556
|
+
name = "is_terminal_polyfill"
|
|
557
|
+
version = "1.70.2"
|
|
558
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
559
|
+
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|
560
|
+
|
|
458
561
|
[[package]]
|
|
459
562
|
name = "itertools"
|
|
460
563
|
version = "0.13.0"
|
|
@@ -483,14 +586,17 @@ dependencies = [
|
|
|
483
586
|
|
|
484
587
|
[[package]]
|
|
485
588
|
name = "jsonata-core"
|
|
486
|
-
version = "2.2.
|
|
589
|
+
version = "2.2.3"
|
|
487
590
|
dependencies = [
|
|
591
|
+
"assert_cmd",
|
|
488
592
|
"base64",
|
|
489
593
|
"chrono",
|
|
594
|
+
"clap",
|
|
490
595
|
"criterion",
|
|
491
596
|
"indexmap",
|
|
492
597
|
"num-traits",
|
|
493
598
|
"percent-encoding",
|
|
599
|
+
"predicates",
|
|
494
600
|
"proptest",
|
|
495
601
|
"pyo3",
|
|
496
602
|
"rand 0.10.2",
|
|
@@ -526,6 +632,12 @@ version = "2.8.3"
|
|
|
526
632
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
527
633
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
528
634
|
|
|
635
|
+
[[package]]
|
|
636
|
+
name = "normalize-line-endings"
|
|
637
|
+
version = "0.3.0"
|
|
638
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
639
|
+
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
|
640
|
+
|
|
529
641
|
[[package]]
|
|
530
642
|
name = "num-traits"
|
|
531
643
|
version = "0.2.19"
|
|
@@ -550,6 +662,12 @@ version = "1.21.4"
|
|
|
550
662
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
551
663
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
552
664
|
|
|
665
|
+
[[package]]
|
|
666
|
+
name = "once_cell_polyfill"
|
|
667
|
+
version = "1.70.2"
|
|
668
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
669
|
+
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
670
|
+
|
|
553
671
|
[[package]]
|
|
554
672
|
name = "oorandom"
|
|
555
673
|
version = "11.1.5"
|
|
@@ -621,6 +739,36 @@ dependencies = [
|
|
|
621
739
|
"zerocopy",
|
|
622
740
|
]
|
|
623
741
|
|
|
742
|
+
[[package]]
|
|
743
|
+
name = "predicates"
|
|
744
|
+
version = "3.1.4"
|
|
745
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
746
|
+
checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
|
|
747
|
+
dependencies = [
|
|
748
|
+
"anstyle",
|
|
749
|
+
"difflib",
|
|
750
|
+
"float-cmp",
|
|
751
|
+
"normalize-line-endings",
|
|
752
|
+
"predicates-core",
|
|
753
|
+
"regex",
|
|
754
|
+
]
|
|
755
|
+
|
|
756
|
+
[[package]]
|
|
757
|
+
name = "predicates-core"
|
|
758
|
+
version = "1.0.10"
|
|
759
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
760
|
+
checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
|
|
761
|
+
|
|
762
|
+
[[package]]
|
|
763
|
+
name = "predicates-tree"
|
|
764
|
+
version = "1.0.13"
|
|
765
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
766
|
+
checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
|
|
767
|
+
dependencies = [
|
|
768
|
+
"predicates-core",
|
|
769
|
+
"termtree",
|
|
770
|
+
]
|
|
771
|
+
|
|
624
772
|
[[package]]
|
|
625
773
|
name = "proc-macro2"
|
|
626
774
|
version = "1.0.106"
|
|
@@ -1002,6 +1150,12 @@ dependencies = [
|
|
|
1002
1150
|
"windows-sys",
|
|
1003
1151
|
]
|
|
1004
1152
|
|
|
1153
|
+
[[package]]
|
|
1154
|
+
name = "strsim"
|
|
1155
|
+
version = "0.11.1"
|
|
1156
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1157
|
+
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
1158
|
+
|
|
1005
1159
|
[[package]]
|
|
1006
1160
|
name = "syn"
|
|
1007
1161
|
version = "2.0.118"
|
|
@@ -1032,6 +1186,12 @@ dependencies = [
|
|
|
1032
1186
|
"windows-sys",
|
|
1033
1187
|
]
|
|
1034
1188
|
|
|
1189
|
+
[[package]]
|
|
1190
|
+
name = "termtree"
|
|
1191
|
+
version = "0.5.1"
|
|
1192
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1193
|
+
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
|
|
1194
|
+
|
|
1035
1195
|
[[package]]
|
|
1036
1196
|
name = "thiserror"
|
|
1037
1197
|
version = "2.0.18"
|
|
@@ -1074,6 +1234,12 @@ version = "1.0.24"
|
|
|
1074
1234
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1075
1235
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
1076
1236
|
|
|
1237
|
+
[[package]]
|
|
1238
|
+
name = "utf8parse"
|
|
1239
|
+
version = "0.2.2"
|
|
1240
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1241
|
+
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
1242
|
+
|
|
1077
1243
|
[[package]]
|
|
1078
1244
|
name = "value-trait"
|
|
1079
1245
|
version = "0.12.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonata-core"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.3"
|
|
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"
|
|
@@ -36,6 +36,11 @@ exclude = [
|
|
|
36
36
|
name = "jsonata_core"
|
|
37
37
|
crate-type = ["cdylib", "rlib"]
|
|
38
38
|
|
|
39
|
+
[[bin]]
|
|
40
|
+
name = "jsonata"
|
|
41
|
+
path = "src/bin/jsonata/main.rs"
|
|
42
|
+
required-features = ["cli"]
|
|
43
|
+
|
|
39
44
|
[dependencies]
|
|
40
45
|
pyo3 = { version = "0.29", optional = true }
|
|
41
46
|
serde = { version = "1.0", features = ["derive"] }
|
|
@@ -50,16 +55,20 @@ percent-encoding = "2.3"
|
|
|
50
55
|
rand = "0.10"
|
|
51
56
|
stacker = "0.1"
|
|
52
57
|
simd-json = { version = "0.17", optional = true }
|
|
58
|
+
clap = { version = "4.6", features = ["derive"], optional = true }
|
|
53
59
|
|
|
54
60
|
[features]
|
|
55
61
|
default = ["simd"]
|
|
56
62
|
simd = ["dep:simd-json"]
|
|
57
63
|
python = ["dep:pyo3"]
|
|
64
|
+
cli = ["dep:clap"]
|
|
58
65
|
bench = [] # exposes _bench facade for Criterion benchmarks
|
|
59
66
|
|
|
60
67
|
[dev-dependencies]
|
|
61
68
|
criterion = "0.8"
|
|
62
69
|
proptest = "1.10"
|
|
70
|
+
assert_cmd = "2.0"
|
|
71
|
+
predicates = "3.1"
|
|
63
72
|
|
|
64
73
|
[[bench]]
|
|
65
74
|
name = "evaluator_bench"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonatapy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.3
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -20,16 +20,20 @@ Requires-Dist: matplotlib>=3.5 ; extra == 'bench'
|
|
|
20
20
|
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
21
21
|
Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
|
|
22
22
|
Requires-Dist: pytest-xdist>=3.0 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.24 ; extra == 'dev'
|
|
23
24
|
Requires-Dist: ruff>=0.3.0 ; extra == 'dev'
|
|
24
25
|
Requires-Dist: mypy>=1.0 ; extra == 'dev'
|
|
25
26
|
Requires-Dist: maturin>=1.0 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: fastmcp>=3.0 ; extra == 'dev'
|
|
26
28
|
Requires-Dist: mkdocs>=1.5 ; extra == 'docs'
|
|
27
29
|
Requires-Dist: mkdocs-material>=9.0 ; extra == 'docs'
|
|
28
30
|
Requires-Dist: mkdocstrings[python]>=0.24 ; extra == 'docs'
|
|
29
31
|
Requires-Dist: pymdown-extensions>=10.0 ; extra == 'docs'
|
|
32
|
+
Requires-Dist: fastmcp>=3.0 ; extra == 'mcp'
|
|
30
33
|
Provides-Extra: bench
|
|
31
34
|
Provides-Extra: dev
|
|
32
35
|
Provides-Extra: docs
|
|
36
|
+
Provides-Extra: mcp
|
|
33
37
|
License-File: LICENSE
|
|
34
38
|
Summary: High-performance Python/Rust implementation of JSONata query and transformation language
|
|
35
39
|
Keywords: jsonata,json,query,transform
|
|
@@ -50,7 +54,7 @@ High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Py
|
|
|
50
54
|
Much of this project was built using Claude Code with significant human oversight. There was no performant
|
|
51
55
|
JSONata implementation in Python, so the goal was to port JSONata to Rust (with a PyO3 wrapper
|
|
52
56
|
for Python) and see how fast it could go. The answer: faster than V8 for most expression
|
|
53
|
-
workloads, and faster than the next pure-Rust implementation.
|
|
57
|
+
workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi.
|
|
54
58
|
|
|
55
59
|
Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
|
|
56
60
|
|
|
@@ -135,6 +139,20 @@ Supports Python 3.10, 3.11, 3.12, 3.13, 3.14 on Linux, macOS (Intel & ARM), and
|
|
|
135
139
|
|
|
136
140
|
---
|
|
137
141
|
|
|
142
|
+
## Command-line quick start
|
|
143
|
+
|
|
144
|
+
Both packages also ship a CLI, `jq`-shaped, with an identical contract:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install jsonatapy
|
|
148
|
+
echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price > 100].product'
|
|
149
|
+
# "Laptop"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
138
156
|
## What is JSONata?
|
|
139
157
|
|
|
140
158
|
JSONata is a query and transformation language for JSON data:
|
|
@@ -189,6 +207,7 @@ benchmark methodology.
|
|
|
189
207
|
- [Installation](docs/installation.md)
|
|
190
208
|
- [API Reference](docs/api.md)
|
|
191
209
|
- [Usage Guide](docs/usage.md)
|
|
210
|
+
- [CLI Reference](docs/cli.md)
|
|
192
211
|
- [Performance](docs/performance.md)
|
|
193
212
|
- [Optimization Tips](docs/optimization-tips.md)
|
|
194
213
|
- [Building from Source](docs/development/building.md)
|
|
@@ -5,7 +5,7 @@ High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Py
|
|
|
5
5
|
Much of this project was built using Claude Code with significant human oversight. There was no performant
|
|
6
6
|
JSONata implementation in Python, so the goal was to port JSONata to Rust (with a PyO3 wrapper
|
|
7
7
|
for Python) and see how fast it could go. The answer: faster than V8 for most expression
|
|
8
|
-
workloads, and faster than the next pure-Rust implementation.
|
|
8
|
+
workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi.
|
|
9
9
|
|
|
10
10
|
Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
|
|
11
11
|
|
|
@@ -90,6 +90,20 @@ Supports Python 3.10, 3.11, 3.12, 3.13, 3.14 on Linux, macOS (Intel & ARM), and
|
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
|
+
## Command-line quick start
|
|
94
|
+
|
|
95
|
+
Both packages also ship a CLI, `jq`-shaped, with an identical contract:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install jsonatapy
|
|
99
|
+
echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price > 100].product'
|
|
100
|
+
# "Laptop"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
93
107
|
## What is JSONata?
|
|
94
108
|
|
|
95
109
|
JSONata is a query and transformation language for JSON data:
|
|
@@ -144,6 +158,7 @@ benchmark methodology.
|
|
|
144
158
|
- [Installation](docs/installation.md)
|
|
145
159
|
- [API Reference](docs/api.md)
|
|
146
160
|
- [Usage Guide](docs/usage.md)
|
|
161
|
+
- [CLI Reference](docs/cli.md)
|
|
147
162
|
- [Performance](docs/performance.md)
|
|
148
163
|
- [Optimization Tips](docs/optimization-tips.md)
|
|
149
164
|
- [Building from Source](docs/development/building.md)
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jsonatapy"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.3"
|
|
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"}
|
|
@@ -30,14 +30,22 @@ classifiers = [
|
|
|
30
30
|
]
|
|
31
31
|
dependencies = []
|
|
32
32
|
|
|
33
|
+
[project.scripts]
|
|
34
|
+
jsonatapy = "jsonatapy.__main__:main"
|
|
35
|
+
|
|
33
36
|
[project.optional-dependencies]
|
|
34
37
|
dev = [
|
|
35
38
|
"pytest>=7.0",
|
|
36
39
|
"pytest-cov>=4.0",
|
|
37
40
|
"pytest-xdist>=3.0", # Parallel test execution
|
|
41
|
+
"pytest-asyncio>=0.24", # Async tests for the FastMCP server (Task 9)
|
|
38
42
|
"ruff>=0.3.0", # Linting and formatting
|
|
39
43
|
"mypy>=1.0",
|
|
40
44
|
"maturin>=1.0",
|
|
45
|
+
"fastmcp>=3.0", # So `dev` installs can run the full test suite (Task 9)
|
|
46
|
+
]
|
|
47
|
+
mcp = [
|
|
48
|
+
"fastmcp>=3.0",
|
|
41
49
|
]
|
|
42
50
|
bench = [
|
|
43
51
|
"jsonata>=0.2.0", # jsonata-python for benchmarking
|
|
@@ -67,9 +75,11 @@ dev = [
|
|
|
67
75
|
"pytest>=7.0",
|
|
68
76
|
"pytest-cov>=4.0",
|
|
69
77
|
"pytest-xdist>=3.0",
|
|
78
|
+
"pytest-asyncio>=0.24",
|
|
70
79
|
"ruff>=0.3.0",
|
|
71
80
|
"mypy>=1.0",
|
|
72
81
|
"maturin>=1.0",
|
|
82
|
+
"fastmcp>=3.0",
|
|
73
83
|
]
|
|
74
84
|
|
|
75
85
|
[tool.ruff]
|
|
@@ -112,6 +122,7 @@ python_files = "test_*.py"
|
|
|
112
122
|
python_classes = "Test*"
|
|
113
123
|
python_functions = "test_*"
|
|
114
124
|
addopts = "-v --strict-markers"
|
|
125
|
+
asyncio_mode = "auto"
|
|
115
126
|
markers = [
|
|
116
127
|
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
117
128
|
"compatibility: marks tests that verify JS compatibility",
|
|
@@ -230,6 +230,57 @@ class JsonataExpression:
|
|
|
230
230
|
json_str, bindings, timeout, max_stack_depth, max_sequence_length
|
|
231
231
|
)
|
|
232
232
|
|
|
233
|
+
def evaluate_json_or_none(
|
|
234
|
+
self,
|
|
235
|
+
json_str: str | None,
|
|
236
|
+
bindings: dict[str, Any] | None = None,
|
|
237
|
+
*,
|
|
238
|
+
timeout: int | None = None,
|
|
239
|
+
max_stack_depth: int | None = None,
|
|
240
|
+
max_sequence_length: int | None = None,
|
|
241
|
+
) -> str | None:
|
|
242
|
+
"""
|
|
243
|
+
Evaluate with JSON string input, distinguishing Undefined from null.
|
|
244
|
+
|
|
245
|
+
Unlike evaluate_json(), which serializes both a JSONata Undefined
|
|
246
|
+
result and an explicit JSON null result to the same text "null",
|
|
247
|
+
this method returns None (the Python value) for Undefined and the
|
|
248
|
+
string "null" for an explicit null result.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
json_str: Input data as a JSON string, or None for no input
|
|
252
|
+
document at all -- the top-level context (`$`) is then a
|
|
253
|
+
true JSONata Undefined, distinct from passing "null" (an
|
|
254
|
+
explicit JSON null context).
|
|
255
|
+
bindings: Optional additional variable bindings
|
|
256
|
+
timeout: Maximum evaluation time in milliseconds (raises ValueError
|
|
257
|
+
with a D1012 code on timeout). Overrides any default set via
|
|
258
|
+
`compile(timeout=...)` for this call only.
|
|
259
|
+
max_stack_depth: Maximum recursion stack depth (raises ValueError
|
|
260
|
+
with a D1011 code when exceeded). Overrides any compile-time default.
|
|
261
|
+
max_sequence_length: Maximum length of a query-result sequence
|
|
262
|
+
(map/filter/wildcard/descendants/etc; raises ValueError with a
|
|
263
|
+
D2015 code when exceeded). Overrides any compile-time default.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
None if the result is JSONata Undefined, otherwise the result
|
|
267
|
+
as a JSON string (e.g. "null" for an explicit null result).
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
ValueError: If JSON parsing or evaluation fails, or a guardrail is exceeded
|
|
271
|
+
|
|
272
|
+
Example:
|
|
273
|
+
>>> expr = compile("nonexistent")
|
|
274
|
+
>>> expr.evaluate_json_or_none('{"a": 1}') is None
|
|
275
|
+
True
|
|
276
|
+
>>> expr2 = compile("a")
|
|
277
|
+
>>> expr2.evaluate_json_or_none('{"a": null}')
|
|
278
|
+
'null'
|
|
279
|
+
"""
|
|
280
|
+
return self._expr.evaluate_json_or_none(
|
|
281
|
+
json_str, bindings, timeout, max_stack_depth, max_sequence_length
|
|
282
|
+
)
|
|
283
|
+
|
|
233
284
|
def evaluate_with_data(
|
|
234
285
|
self,
|
|
235
286
|
data: "JsonataData",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Console-script entry point for the `jsonatapy` CLI.
|
|
2
|
+
|
|
3
|
+
Dispatches to the MCP server subcommand (`jsonatapy mcp ...`) or evaluate
|
|
4
|
+
mode (everything else). See study/cli_spec.md for the full contract.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from ._cli.run import run
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _run_mcp(argv: list[str]) -> int:
|
|
16
|
+
parser = argparse.ArgumentParser(prog="jsonatapy mcp", description="Run the JSONata MCP server")
|
|
17
|
+
parser.add_argument("--http", action="store_true", help="Serve over HTTP instead of stdio")
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--port", type=int, default=None, metavar="N", help="HTTP port (default 8000)"
|
|
20
|
+
)
|
|
21
|
+
args = parser.parse_args(argv)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from ._cli.mcp_server import serve
|
|
25
|
+
except ImportError:
|
|
26
|
+
print(
|
|
27
|
+
"error: the 'mcp' extra is not installed. Run:\n"
|
|
28
|
+
' uvx --from "jsonatapy[mcp]" jsonatapy mcp\n'
|
|
29
|
+
"or: pip install jsonatapy[mcp]",
|
|
30
|
+
file=sys.stderr,
|
|
31
|
+
)
|
|
32
|
+
return 2
|
|
33
|
+
|
|
34
|
+
serve(args.http, args.port)
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> int:
|
|
39
|
+
argv = sys.argv[1:]
|
|
40
|
+
if argv[:1] == ["mcp"]:
|
|
41
|
+
return _run_mcp(argv[1:])
|
|
42
|
+
return run(argv)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal CLI implementation for the `jsonatapy` console script. Not public API."""
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Parses --arg/--argjson CLI specs into a name -> value map for
|
|
2
|
+
evaluate_json_or_none() bindings."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BindingError(Exception):
|
|
12
|
+
"""Raised when an --arg/--argjson spec is malformed."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _reject_constant(token: str) -> float:
|
|
16
|
+
raise ValueError(f"{token} is not valid JSON")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _finite_float(text: str) -> float:
|
|
20
|
+
value = float(text)
|
|
21
|
+
if not math.isfinite(value):
|
|
22
|
+
raise ValueError(f"{text} is not valid JSON")
|
|
23
|
+
return value
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_bindings(arg: list[str], argjson: list[str]) -> dict[str, Any]:
|
|
27
|
+
"""Parses --arg NAME=VALUE (string) and --argjson NAME=JSON (parsed) specs
|
|
28
|
+
into a single name -> value dict. --argjson wins on name collision
|
|
29
|
+
(applied second, matching src/bin/jsonata/bindings.rs's iteration order)."""
|
|
30
|
+
bindings: dict[str, Any] = {}
|
|
31
|
+
for spec in arg:
|
|
32
|
+
name, value = _split_name_value(spec, "--arg")
|
|
33
|
+
bindings[name] = value
|
|
34
|
+
for spec in argjson:
|
|
35
|
+
name, value = _split_name_value(spec, "--argjson")
|
|
36
|
+
try:
|
|
37
|
+
bindings[name] = json.loads(
|
|
38
|
+
value, parse_constant=_reject_constant, parse_float=_finite_float
|
|
39
|
+
)
|
|
40
|
+
except ValueError as e:
|
|
41
|
+
raise BindingError(f"--argjson {name}: invalid JSON value: {e}") from e
|
|
42
|
+
return bindings
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _split_name_value(spec: str, flag: str) -> tuple[str, str]:
|
|
46
|
+
if "=" not in spec:
|
|
47
|
+
raise BindingError(f"{flag} expects NAME=VALUE, got: {spec}")
|
|
48
|
+
name, _, value = spec.partition("=")
|
|
49
|
+
if not name:
|
|
50
|
+
raise BindingError(f"{flag} expects NAME=VALUE, got: {spec}")
|
|
51
|
+
return name, value
|