jsonatapy 2.2.2__tar.gz → 2.2.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.
Files changed (56) hide show
  1. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/CHANGELOG.md +71 -0
  2. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/Cargo.lock +171 -5
  3. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/Cargo.toml +10 -1
  4. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/PKG-INFO +21 -2
  5. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/README.md +16 -1
  6. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/pyproject.toml +12 -1
  7. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/python/jsonatapy/__init__.py +51 -0
  8. jsonatapy-2.2.4/python/jsonatapy/__main__.py +46 -0
  9. jsonatapy-2.2.4/python/jsonatapy/_cli/__init__.py +1 -0
  10. jsonatapy-2.2.4/python/jsonatapy/_cli/bindings.py +51 -0
  11. jsonatapy-2.2.4/python/jsonatapy/_cli/error_format.py +25 -0
  12. jsonatapy-2.2.4/python/jsonatapy/_cli/mcp_server.py +192 -0
  13. jsonatapy-2.2.4/python/jsonatapy/_cli/resolve.py +85 -0
  14. jsonatapy-2.2.4/python/jsonatapy/_cli/run.py +204 -0
  15. jsonatapy-2.2.4/src/bin/jsonata/bindings.rs +62 -0
  16. jsonatapy-2.2.4/src/bin/jsonata/error_format.rs +47 -0
  17. jsonatapy-2.2.4/src/bin/jsonata/main.rs +182 -0
  18. jsonatapy-2.2.4/src/bin/jsonata/resolve.rs +148 -0
  19. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/evaluator.rs +582 -77
  20. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/functions.rs +109 -3
  21. jsonatapy-2.2.4/src/lazy.rs +232 -0
  22. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/lib.rs +74 -89
  23. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/parser.rs +39 -0
  24. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/signature.rs +4 -0
  25. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/value.rs +76 -1
  26. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/vm.rs +4 -2
  27. jsonatapy-2.2.4/study/cli_fixtures.json +138 -0
  28. jsonatapy-2.2.4/study/cli_fixtures_testdata.json +1 -0
  29. jsonatapy-2.2.4/study/cli_spec.md +99 -0
  30. jsonatapy-2.2.4/tests/cli_fixtures_test.rs +69 -0
  31. jsonatapy-2.2.4/tests/cli_test.rs +352 -0
  32. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/tests/datetime_picture_suite.rs +2 -0
  33. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/tests/parent_and_focus_binding_suite.rs +2 -0
  34. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.gitignore +0 -0
  35. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.gitmodules +0 -0
  36. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/.gitignore +0 -0
  37. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/memories/architecture.md +0 -0
  38. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/memories/conventions.md +0 -0
  39. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/memories/core.md +0 -0
  40. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/memories/memory_maintenance.md +0 -0
  41. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/memories/suggested_commands.md +0 -0
  42. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/memories/task_completion.md +0 -0
  43. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/memories/tech_stack.md +0 -0
  44. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/.serena/project.yml +0 -0
  45. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/LICENSE +0 -0
  46. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/benches/evaluator_bench.rs +0 -0
  47. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/examples/evaluator_demo.rs +0 -0
  48. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/examples/parser_demo.rs +0 -0
  49. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/examples/simd_json_bench.rs +0 -0
  50. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/python/jsonatapy/py.typed +0 -0
  51. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/ast.rs +0 -0
  52. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/ast_transform.rs +0 -0
  53. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/compiler.rs +0 -0
  54. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/datetime.rs +0 -0
  55. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/src/parser/README.md +0 -0
  56. {jsonatapy-2.2.2 → jsonatapy-2.2.4}/tests/integration_test.rs +0 -0
@@ -19,6 +19,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  ### Security
21
21
 
22
+ ## [2.2.4] - 2026-07-13
23
+
24
+ ### Added
25
+ - `JSONATAPY_FORCE_TREE_WALKER=1` environment variable (testing/debugging): forces every
26
+ evaluation through the tree-walking evaluator, bypassing the default lazy/VM-preferred path.
27
+
28
+ ### Changed
29
+ - `evaluate(dict)` now converts Python data lazily by default. Previously every call eagerly
30
+ converted the entire input `dict` (and nested structures) to the internal value tree before
31
+ evaluation began; now only the fields an expression actually touches are converted, and
32
+ untouched input subtrees pass straight through to the output unchanged. Measured on the dev
33
+ machine (min-of-5, vs. the prior eager `evaluate(dict)`):
34
+ - `products.price` (100 objects): 34.9µs → 11.3µs (3.1x)
35
+ - Filter by category (100 products, 9 fields each): 129.3µs → 23.4µs (5.5x)
36
+ - `$sum(products[inStock].price)`: 116.4µs → 17.1µs (6.8x)
37
+ - Complex dense transformation: 160.9µs → 79µs (2.0x)
38
+
39
+ Every measured row now beats jsonata-js on identical data and machine (14.6/93.7/67.6/415.1µs
40
+ respectively). See `benchmarks/python/lazy_check.py` for the reproducible gate.
41
+ - **Behavior change:** results containing an unmodified input subtree now reference the
42
+ caller's *original* Python `dict`/`list` objects (result aliasing, matching jsonata-js),
43
+ rather than a fresh copy. Mutating such a result mutates the corresponding input — copy
44
+ explicitly (e.g. `copy.deepcopy`) first if you plan to mutate. Passed-through values also keep
45
+ their exact Python type (an `int` field the expression never reads stays an `int`); fields the
46
+ expression does touch still round-trip through the engine's number representation (whole
47
+ values come back as Python `int`), as before.
48
+ - **Behavior change:** an unconvertible input value (e.g. a `set`) now raises `TypeError` only
49
+ when the expression actually touches it, instead of eagerly for the whole input at the start
50
+ of `evaluate()`. Any Python exception raised while lazily reading a field (e.g. `OverflowError`
51
+ on an integer too large to represent) is likewise normalized to `TypeError` at this boundary,
52
+ rather than propagating as its original exception type.
53
+
54
+ ### Deprecated
55
+
56
+ ### Removed
57
+
58
+ ### Fixed
59
+
60
+ ### Security
61
+
62
+ ## [2.2.3] - 2026-07-12
63
+
64
+ ### Added
65
+ - `evaluate_json_or_none()`'s `json_str` parameter now accepts `None` (in addition to a JSON
66
+ string), binding the top-level context (`$`) to a true JSONata `Undefined` rather than an
67
+ explicit `null`. (#68)
68
+ - `docs/cli.md`: a full command-line reference for both `jsonata` (Rust) and `jsonatapy`
69
+ (Python), covering flags, input resolution, output/exit-code semantics, and the MCP
70
+ subcommand — previously undocumented outside internal spec/plan files. (#68)
71
+
72
+ ### Changed
73
+ - Release CLI binary archives are now named `jsonata-v<version>-<target>.tar.gz`/`.zip`
74
+ (previously `jsonata-<target>.tar.gz`/`.zip` with no version embedded).
75
+
76
+ ### Deprecated
77
+
78
+ ### Removed
79
+
80
+ ### Fixed
81
+ - The Python CLI's `-n`/`--null-input` now binds `$` to a true `Undefined`, matching the Rust
82
+ CLI exactly (`jsonatapy -n '$'` now prints nothing, as `jsonata -n '$'` already did). It
83
+ previously passed an explicit JSON `null` context instead, observable only for expressions
84
+ referencing `$` directly. (#68)
85
+ - Release workflow: `publish-pypi` and `publish-crates` now require `build-cli-binaries` to
86
+ succeed first. Previously the two registry publishes were independent of the CLI binary
87
+ build, so a CLI build failure (untested in a real release prior to this) would have shipped
88
+ a version to PyPI/crates.io with no CLI binaries attached and no way to reuse that version
89
+ number.
90
+
91
+ ### Security
92
+
22
93
  ## [2.2.2] - 2026-07-09
23
94
 
24
95
  ### 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.2"
589
+ version = "2.2.4"
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"
@@ -840,9 +988,9 @@ dependencies = [
840
988
 
841
989
  [[package]]
842
990
  name = "regex"
843
- version = "1.12.4"
991
+ version = "1.13.0"
844
992
  source = "registry+https://github.com/rust-lang/crates.io-index"
845
- checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
993
+ checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
846
994
  dependencies = [
847
995
  "aho-corasick",
848
996
  "memchr",
@@ -965,9 +1113,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
965
1113
 
966
1114
  [[package]]
967
1115
  name = "simd-json"
968
- version = "0.17.2"
1116
+ version = "0.17.3"
969
1117
  source = "registry+https://github.com/rust-lang/crates.io-index"
970
- checksum = "6f75934ad5ee9e78b79ce7731b822debcb3e5fa6c5aabe57410084715897d1ee"
1118
+ checksum = "e32d7ab2678282d21e53374fbead7119b7eacbede73685dcaac472870a29a11c"
971
1119
  dependencies = [
972
1120
  "halfbrown",
973
1121
  "ref-cast",
@@ -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.2"
3
+ version = "2.2.4"
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.2
3
+ Version: 2.2.4
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.2"
7
+ version = "2.2.4"
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."""