From 05d7fb24bd1e9ced3a5877c82e2de00367744037 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:16:56 -0700 Subject: [PATCH] feat(rust): add python-compat crate for Python data formats (#42510) * feat(rust): add python-compat crate for Python data formats Add litellm-python-compat, a PyO3-free crate that reproduces the Python data formats LiteLLM persists, so Rust readers and writers can interoperate with state written by the Python proxy: - literal::literal_eval: a linear recursive-descent port of ast.literal_eval (prefixes, escapes, implicit concatenation, numeric underscores and radixes, single unary sign, real +/- complex with 3.14 mixed-mode rules, set(), Python-equality key dedup) - repr::{repr, to_str}: byte-exact repr()/str(), with a printable table generated from CPython's str.isprintable (Unicode 16.0.0) - json::{dumps, from_json, to_json}: json.dumps defaults and the json.loads mapping - pickle::{loads, dumps}: plain-data pickles via serde-pickle's serde interface, which keeps dict insertion order - truthy::truthy: bool() for plain data Tests replay fixtures generated by CPython 3.14 (values across every format and pickle protocol 0-5, plus 154 literal_eval source texts). Accepted divergences are pinned in a KNOWN table that fails once one starts matching. A criterion bench covers each format and literal_eval cost by nesting depth, guarding the linear parse: the py_literal grammar doubled per nested bracket (105 ms at 16 nested dicts; 19 us at 128 now). Co-Authored-By: Claude Opus 5 * refactor(rust): split python-compat modules and harden the pickle verifier - Disable class resolution in scripts/verify_rust_pickles.py, and truncate the export file once instead of removing and appending to it, so the verifier cannot be pointed at a pre-created file whose rows execute code through pickle.loads - Move Error to error.rs and Value to value.rs, leaving lib.rs as the crate overview, module list and MAX_DEPTH - Move the generator and verifier to scripts/, beside the Unicode table generator, leaving tests/ to the Rust tests - Group the bench by measured surface, give every case a Throughput so criterion reports bytes per second, and document baseline comparison Co-Authored-By: Claude Opus 5 --------- Co-authored-by: Yujong Lee Co-authored-by: Claude Opus 5 --- litellm-rust/Cargo.lock | 15 + litellm-rust/crates/python-compat/AGENTS.md | 23 + litellm-rust/crates/python-compat/Cargo.toml | 24 + .../crates/python-compat/benches/formats.rs | 111 + .../python-compat/generated/nonprintable.rs | 745 ++++++ .../python-compat/generated/values.json | 2164 +++++++++++++++++ .../scripts/generate_fixtures.py | 352 +++ .../scripts/generate_nonprintable.py | 35 + .../scripts/verify_rust_pickles.py | 43 + .../crates/python-compat/src/error.rs | 25 + litellm-rust/crates/python-compat/src/json.rs | 168 ++ litellm-rust/crates/python-compat/src/lib.rs | 39 + .../crates/python-compat/src/literal.rs | 745 ++++++ .../crates/python-compat/src/pickle.rs | 187 ++ litellm-rust/crates/python-compat/src/repr.rs | 237 ++ .../crates/python-compat/src/truthy.rs | 18 + .../crates/python-compat/src/value.rs | 53 + .../crates/python-compat/tests/fixtures.rs | 343 +++ .../crates/python-compat/tests/limits.rs | 122 + 19 files changed, 5449 insertions(+) create mode 100644 litellm-rust/crates/python-compat/AGENTS.md create mode 100644 litellm-rust/crates/python-compat/Cargo.toml create mode 100644 litellm-rust/crates/python-compat/benches/formats.rs create mode 100644 litellm-rust/crates/python-compat/generated/nonprintable.rs create mode 100644 litellm-rust/crates/python-compat/generated/values.json create mode 100644 litellm-rust/crates/python-compat/scripts/generate_fixtures.py create mode 100644 litellm-rust/crates/python-compat/scripts/generate_nonprintable.py create mode 100644 litellm-rust/crates/python-compat/scripts/verify_rust_pickles.py create mode 100644 litellm-rust/crates/python-compat/src/error.rs create mode 100644 litellm-rust/crates/python-compat/src/json.rs create mode 100644 litellm-rust/crates/python-compat/src/lib.rs create mode 100644 litellm-rust/crates/python-compat/src/literal.rs create mode 100644 litellm-rust/crates/python-compat/src/pickle.rs create mode 100644 litellm-rust/crates/python-compat/src/repr.rs create mode 100644 litellm-rust/crates/python-compat/src/truthy.rs create mode 100644 litellm-rust/crates/python-compat/src/value.rs create mode 100644 litellm-rust/crates/python-compat/tests/fixtures.rs create mode 100644 litellm-rust/crates/python-compat/tests/limits.rs diff --git a/litellm-rust/Cargo.lock b/litellm-rust/Cargo.lock index 76004c5d978..ea63746d56d 100644 --- a/litellm-rust/Cargo.lock +++ b/litellm-rust/Cargo.lock @@ -3072,6 +3072,21 @@ dependencies = [ "wiremock", ] +[[package]] +name = "litellm-python-compat" +version = "0.1.0" +dependencies = [ + "criterion", + "hex", + "num-bigint 0.4.8", + "num-traits", + "rstest", + "serde", + "serde-pickle", + "serde_json", + "thiserror 2.0.19", +] + [[package]] name = "litellm-secrets" version = "0.1.0" diff --git a/litellm-rust/crates/python-compat/AGENTS.md b/litellm-rust/crates/python-compat/AGENTS.md new file mode 100644 index 00000000000..0869568d33b --- /dev/null +++ b/litellm-rust/crates/python-compat/AGENTS.md @@ -0,0 +1,23 @@ +- Pure Python *data formats* in Rust, for state Python LiteLLM writes and Rust must read or write byte-compatibly + - No PyO3, no live objects: truthiness, `__str__`, descriptors of real Python objects belong to `python-bridge`'s coercion layer + - Format *choices* stay with callers: the `{timestamp, response}` envelope, diskcache modes, and the cache-key recipe live in the cache crates and only call into this crate +- Intended users + - `cache-response` codec: reading `str(dict)` values Python's sync Redis path writes (`literal_eval`) + - `cache-disk`: diskcache's pickled values (`pickle`), falsy-is-miss (`truthy`) + - Cache-key derivation: sha256 over `str(value)` must match Python byte for byte (`repr::to_str`) + - Byte-identical writes where Python compares raw values (`json::dumps`, `repr`) +- Relation to [`py_literal`](https://docs.rs/py_literal/latest/py_literal/): replaced, do not reintroduce + - Its pest grammar backtracks: parse time doubles per nested `[`/`{` (105 ms at depth 16); ours is linear (19 µs at depth 128) + - Its formatter is not `repr` (`2e-1`, always single quotes, escapes non-ASCII); it also lost `-0.0`, `(1+2j)`, `set()` + - `cache-response` and `cache-disk` still depend on it; migrate them here +- Relation to [`serde-pickle`](https://docs.rs/serde-pickle/latest/serde_pickle/): the pickle codec, used only through its serde interface + - Never `serde_pickle::Value`: its `BTreeMap` dicts reorder keys + - Accepted limits: ints beyond i64, `tuple`/`set`/`frozenset` decode as lists, class references (`GLOBAL`/`REDUCE`) fail; writes protocol 3 +- Every behavior is pinned by CPython output, not by reasoning; everything under `generated/` is script output, never hand-edited + - Regenerate `generated/values.json` with `scripts/generate_fixtures.py`; add a corpus row before changing behavior + - Divergences go in `KNOWN` in `tests/fixtures.rs` with a reason; an entry that starts matching fails until deleted + - Regenerate `generated/nonprintable.rs` with `scripts/generate_nonprintable.py` when the target Python's Unicode version changes + - `scripts/verify_rust_pickles.py` checks CPython reads Rust pickles, with class resolution disabled; CI does not run Python +- Decoders recurse, so they reject nesting beyond `MAX_DEPTH` for stack safety: deliberately stricter than CPython, whose parser takes ~200 levels and whose unpickler has no limit (pinned as `nested_150`) + - Formatters (`repr`, `json`) are unbounded; values from the decoders are already capped, a hand-built `Value` is the caller's responsibility + - `literal_eval` must stay linear in depth: `tests/limits.rs` times the deepest parse, `benches/formats.rs` measures the curve but is manual, since CI runs no Rust bench diff --git a/litellm-rust/crates/python-compat/Cargo.toml b/litellm-rust/crates/python-compat/Cargo.toml new file mode 100644 index 00000000000..2ab6fb29843 --- /dev/null +++ b/litellm-rust/crates/python-compat/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "litellm-python-compat" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Python data formats (repr, literal_eval, json.dumps, pickle) reproduced for interop with persisted LiteLLM state" + +[dependencies] +num-bigint = "0.4" +num-traits = "0.2" +serde.workspace = true +serde-pickle = "1.2" +serde_json = { workspace = true, features = ["preserve_order"] } +thiserror.workspace = true + +[dev-dependencies] +criterion.workspace = true +hex = "0.4" +rstest.workspace = true + +[[bench]] +name = "formats" +harness = false diff --git a/litellm-rust/crates/python-compat/benches/formats.rs b/litellm-rust/crates/python-compat/benches/formats.rs new file mode 100644 index 00000000000..f0b83cb1658 --- /dev/null +++ b/litellm-rust/crates/python-compat/benches/formats.rs @@ -0,0 +1,111 @@ +//! Throughput of each format on a cached chat completion, and `literal_eval` cost by nesting. +//! +//! Run one group with `cargo bench -p litellm-python-compat -- cached_completion`, and compare +//! against a stored run with `--save-baseline ` / `--baseline `. +//! +//! `literal_eval/nesting` guards against backtracking: the `py_literal` grammar this parser +//! replaced doubled its time per nested `[` or `{` (105 ms at depth 16), so cost must stay +//! linear in depth for every container shape. + +use std::{hint::black_box, time::Duration}; + +use criterion::{ + BatchSize, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, + measurement::WallTime, +}; +use litellm_python_compat::{Value, json, literal::literal_eval, pickle, repr::repr}; + +/// `str(entry)` for the `{timestamp, response}` envelope Python's sync Redis path writes. +fn cached_completion() -> String { + let choices: Vec = (0..4) + .map(|index| { + format!( + "{{'finish_reason': 'stop', 'index': {index}, 'message': {{'content': \ + 'Benchmarks compare the same workload under controlled conditions, so a \ + change in time reflects the code rather than the environment. café 日本 \ + {index}', 'role': 'assistant', 'tool_calls': None, 'function_call': None}}, \ + 'logprobs': None}}" + ) + }) + .collect(); + format!( + "{{'timestamp': 1726000000.123, 'response': {{'id': 'chatcmpl-9x1', 'created': \ + 1726000000, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion', \ + 'system_fingerprint': 'fp_1', 'choices': [{}], 'usage': {{'completion_tokens': 120, \ + 'prompt_tokens': 42, 'total_tokens': 162, 'completion_tokens_details': None}}}}}}", + choices.join(", ") + ) +} + +/// Every text format, measured against the source bytes it reads or writes. +fn text_formats(group: &mut BenchmarkGroup<'_, WallTime>, text: &str, value: &Value) { + group.throughput(Throughput::Bytes(text.len() as u64)); + group.bench_function("literal_eval", |bencher| { + bencher.iter(|| literal_eval(black_box(text))) + }); + group.bench_function("repr", |bencher| bencher.iter(|| repr(black_box(value)))); + group.bench_function("json_dumps", |bencher| { + bencher.iter(|| json::dumps(black_box(value))) + }); + group.bench_function("to_json", |bencher| { + bencher.iter(|| json::to_json(black_box(value))) + }); +} + +/// Pickle, measured against its own encoding rather than the source text. +fn binary_formats(group: &mut BenchmarkGroup<'_, WallTime>, value: &Value, pickled: &[u8]) { + group.throughput(Throughput::Bytes(pickled.len() as u64)); + group.bench_function("pickle_dumps", |bencher| { + bencher.iter(|| pickle::dumps(black_box(value))) + }); + group.bench_function("pickle_loads", |bencher| { + bencher.iter(|| pickle::loads(black_box(pickled))) + }); +} + +fn formats(c: &mut Criterion) { + let text = cached_completion(); + let value = literal_eval(&text).expect("benchmark payload is a literal"); + let pickled = pickle::dumps(&value).expect("benchmark payload pickles"); + let dumped = json::dumps(&value).expect("benchmark payload is JSON serializable"); + + let mut group = c.benchmark_group("cached_completion"); + text_formats(&mut group, &text, &value); + binary_formats(&mut group, &value, &pickled); + // `from_json` consumes its input, so each iteration gets a freshly parsed one. + group.throughput(Throughput::Bytes(dumped.len() as u64)); + group.bench_function("from_json", |bencher| { + bencher.iter_batched( + || serde_json::from_str::(&dumped).expect("dumps output parses"), + json::from_json, + BatchSize::SmallInput, + ) + }); + group.finish(); +} + +/// One nesting level of each container shape, as `(name, open, close)`. +const SHAPES: [(&str, &str, &str); 3] = [ + ("list", "[", "]"), + ("dict", "{'a': ", "}"), + ("tuple", "(", ",)"), +]; + +fn literal_nesting(c: &mut Criterion) { + let mut group = c.benchmark_group("literal_eval/nesting"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(3)); + for depth in [4, 16, 64, 128] { + for (shape, open, close) in SHAPES { + let text = format!("{}1{}", open.repeat(depth), close.repeat(depth)); + group.throughput(Throughput::Bytes(text.len() as u64)); + group.bench_with_input(BenchmarkId::new(shape, depth), &text, |bencher, text| { + bencher.iter(|| literal_eval(black_box(text))) + }); + } + } + group.finish(); +} + +criterion_group!(benches, formats, literal_nesting); +criterion_main!(benches); diff --git a/litellm-rust/crates/python-compat/generated/nonprintable.rs b/litellm-rust/crates/python-compat/generated/nonprintable.rs new file mode 100644 index 00000000000..a044210f07d --- /dev/null +++ b/litellm-rust/crates/python-compat/generated/nonprintable.rs @@ -0,0 +1,745 @@ +// Generated by scripts/generate_nonprintable.py from Python 3.14.7 +// (Unicode 16.0.0). Do not edit by hand. + +pub(crate) const UNICODE_VERSION: &str = "16.0.0"; + +/// Inclusive code point ranges for which Python's `str.isprintable()` is false. +pub(crate) const NONPRINTABLE: [(u32, u32); 737] = [ + (0x0000, 0x001F), + (0x007F, 0x00A0), + (0x00AD, 0x00AD), + (0x0378, 0x0379), + (0x0380, 0x0383), + (0x038B, 0x038B), + (0x038D, 0x038D), + (0x03A2, 0x03A2), + (0x0530, 0x0530), + (0x0557, 0x0558), + (0x058B, 0x058C), + (0x0590, 0x0590), + (0x05C8, 0x05CF), + (0x05EB, 0x05EE), + (0x05F5, 0x0605), + (0x061C, 0x061C), + (0x06DD, 0x06DD), + (0x070E, 0x070F), + (0x074B, 0x074C), + (0x07B2, 0x07BF), + (0x07FB, 0x07FC), + (0x082E, 0x082F), + (0x083F, 0x083F), + (0x085C, 0x085D), + (0x085F, 0x085F), + (0x086B, 0x086F), + (0x088F, 0x0896), + (0x08E2, 0x08E2), + (0x0984, 0x0984), + (0x098D, 0x098E), + (0x0991, 0x0992), + (0x09A9, 0x09A9), + (0x09B1, 0x09B1), + (0x09B3, 0x09B5), + (0x09BA, 0x09BB), + (0x09C5, 0x09C6), + (0x09C9, 0x09CA), + (0x09CF, 0x09D6), + (0x09D8, 0x09DB), + (0x09DE, 0x09DE), + (0x09E4, 0x09E5), + (0x09FF, 0x0A00), + (0x0A04, 0x0A04), + (0x0A0B, 0x0A0E), + (0x0A11, 0x0A12), + (0x0A29, 0x0A29), + (0x0A31, 0x0A31), + (0x0A34, 0x0A34), + (0x0A37, 0x0A37), + (0x0A3A, 0x0A3B), + (0x0A3D, 0x0A3D), + (0x0A43, 0x0A46), + (0x0A49, 0x0A4A), + (0x0A4E, 0x0A50), + (0x0A52, 0x0A58), + (0x0A5D, 0x0A5D), + (0x0A5F, 0x0A65), + (0x0A77, 0x0A80), + (0x0A84, 0x0A84), + (0x0A8E, 0x0A8E), + (0x0A92, 0x0A92), + (0x0AA9, 0x0AA9), + (0x0AB1, 0x0AB1), + (0x0AB4, 0x0AB4), + (0x0ABA, 0x0ABB), + (0x0AC6, 0x0AC6), + (0x0ACA, 0x0ACA), + (0x0ACE, 0x0ACF), + (0x0AD1, 0x0ADF), + (0x0AE4, 0x0AE5), + (0x0AF2, 0x0AF8), + (0x0B00, 0x0B00), + (0x0B04, 0x0B04), + (0x0B0D, 0x0B0E), + (0x0B11, 0x0B12), + (0x0B29, 0x0B29), + (0x0B31, 0x0B31), + (0x0B34, 0x0B34), + (0x0B3A, 0x0B3B), + (0x0B45, 0x0B46), + (0x0B49, 0x0B4A), + (0x0B4E, 0x0B54), + (0x0B58, 0x0B5B), + (0x0B5E, 0x0B5E), + (0x0B64, 0x0B65), + (0x0B78, 0x0B81), + (0x0B84, 0x0B84), + (0x0B8B, 0x0B8D), + (0x0B91, 0x0B91), + (0x0B96, 0x0B98), + (0x0B9B, 0x0B9B), + (0x0B9D, 0x0B9D), + (0x0BA0, 0x0BA2), + (0x0BA5, 0x0BA7), + (0x0BAB, 0x0BAD), + (0x0BBA, 0x0BBD), + (0x0BC3, 0x0BC5), + (0x0BC9, 0x0BC9), + (0x0BCE, 0x0BCF), + (0x0BD1, 0x0BD6), + (0x0BD8, 0x0BE5), + (0x0BFB, 0x0BFF), + (0x0C0D, 0x0C0D), + (0x0C11, 0x0C11), + (0x0C29, 0x0C29), + (0x0C3A, 0x0C3B), + (0x0C45, 0x0C45), + (0x0C49, 0x0C49), + (0x0C4E, 0x0C54), + (0x0C57, 0x0C57), + (0x0C5B, 0x0C5C), + (0x0C5E, 0x0C5F), + (0x0C64, 0x0C65), + (0x0C70, 0x0C76), + (0x0C8D, 0x0C8D), + (0x0C91, 0x0C91), + (0x0CA9, 0x0CA9), + (0x0CB4, 0x0CB4), + (0x0CBA, 0x0CBB), + (0x0CC5, 0x0CC5), + (0x0CC9, 0x0CC9), + (0x0CCE, 0x0CD4), + (0x0CD7, 0x0CDC), + (0x0CDF, 0x0CDF), + (0x0CE4, 0x0CE5), + (0x0CF0, 0x0CF0), + (0x0CF4, 0x0CFF), + (0x0D0D, 0x0D0D), + (0x0D11, 0x0D11), + (0x0D45, 0x0D45), + (0x0D49, 0x0D49), + (0x0D50, 0x0D53), + (0x0D64, 0x0D65), + (0x0D80, 0x0D80), + (0x0D84, 0x0D84), + (0x0D97, 0x0D99), + (0x0DB2, 0x0DB2), + (0x0DBC, 0x0DBC), + (0x0DBE, 0x0DBF), + (0x0DC7, 0x0DC9), + (0x0DCB, 0x0DCE), + (0x0DD5, 0x0DD5), + (0x0DD7, 0x0DD7), + (0x0DE0, 0x0DE5), + (0x0DF0, 0x0DF1), + (0x0DF5, 0x0E00), + (0x0E3B, 0x0E3E), + (0x0E5C, 0x0E80), + (0x0E83, 0x0E83), + (0x0E85, 0x0E85), + (0x0E8B, 0x0E8B), + (0x0EA4, 0x0EA4), + (0x0EA6, 0x0EA6), + (0x0EBE, 0x0EBF), + (0x0EC5, 0x0EC5), + (0x0EC7, 0x0EC7), + (0x0ECF, 0x0ECF), + (0x0EDA, 0x0EDB), + (0x0EE0, 0x0EFF), + (0x0F48, 0x0F48), + (0x0F6D, 0x0F70), + (0x0F98, 0x0F98), + (0x0FBD, 0x0FBD), + (0x0FCD, 0x0FCD), + (0x0FDB, 0x0FFF), + (0x10C6, 0x10C6), + (0x10C8, 0x10CC), + (0x10CE, 0x10CF), + (0x1249, 0x1249), + (0x124E, 0x124F), + (0x1257, 0x1257), + (0x1259, 0x1259), + (0x125E, 0x125F), + (0x1289, 0x1289), + (0x128E, 0x128F), + (0x12B1, 0x12B1), + (0x12B6, 0x12B7), + (0x12BF, 0x12BF), + (0x12C1, 0x12C1), + (0x12C6, 0x12C7), + (0x12D7, 0x12D7), + (0x1311, 0x1311), + (0x1316, 0x1317), + (0x135B, 0x135C), + (0x137D, 0x137F), + (0x139A, 0x139F), + (0x13F6, 0x13F7), + (0x13FE, 0x13FF), + (0x1680, 0x1680), + (0x169D, 0x169F), + (0x16F9, 0x16FF), + (0x1716, 0x171E), + (0x1737, 0x173F), + (0x1754, 0x175F), + (0x176D, 0x176D), + (0x1771, 0x1771), + (0x1774, 0x177F), + (0x17DE, 0x17DF), + (0x17EA, 0x17EF), + (0x17FA, 0x17FF), + (0x180E, 0x180E), + (0x181A, 0x181F), + (0x1879, 0x187F), + (0x18AB, 0x18AF), + (0x18F6, 0x18FF), + (0x191F, 0x191F), + (0x192C, 0x192F), + (0x193C, 0x193F), + (0x1941, 0x1943), + (0x196E, 0x196F), + (0x1975, 0x197F), + (0x19AC, 0x19AF), + (0x19CA, 0x19CF), + (0x19DB, 0x19DD), + (0x1A1C, 0x1A1D), + (0x1A5F, 0x1A5F), + (0x1A7D, 0x1A7E), + (0x1A8A, 0x1A8F), + (0x1A9A, 0x1A9F), + (0x1AAE, 0x1AAF), + (0x1ACF, 0x1AFF), + (0x1B4D, 0x1B4D), + (0x1BF4, 0x1BFB), + (0x1C38, 0x1C3A), + (0x1C4A, 0x1C4C), + (0x1C8B, 0x1C8F), + (0x1CBB, 0x1CBC), + (0x1CC8, 0x1CCF), + (0x1CFB, 0x1CFF), + (0x1F16, 0x1F17), + (0x1F1E, 0x1F1F), + (0x1F46, 0x1F47), + (0x1F4E, 0x1F4F), + (0x1F58, 0x1F58), + (0x1F5A, 0x1F5A), + (0x1F5C, 0x1F5C), + (0x1F5E, 0x1F5E), + (0x1F7E, 0x1F7F), + (0x1FB5, 0x1FB5), + (0x1FC5, 0x1FC5), + (0x1FD4, 0x1FD5), + (0x1FDC, 0x1FDC), + (0x1FF0, 0x1FF1), + (0x1FF5, 0x1FF5), + (0x1FFF, 0x200F), + (0x2028, 0x202F), + (0x205F, 0x206F), + (0x2072, 0x2073), + (0x208F, 0x208F), + (0x209D, 0x209F), + (0x20C1, 0x20CF), + (0x20F1, 0x20FF), + (0x218C, 0x218F), + (0x242A, 0x243F), + (0x244B, 0x245F), + (0x2B74, 0x2B75), + (0x2B96, 0x2B96), + (0x2CF4, 0x2CF8), + (0x2D26, 0x2D26), + (0x2D28, 0x2D2C), + (0x2D2E, 0x2D2F), + (0x2D68, 0x2D6E), + (0x2D71, 0x2D7E), + (0x2D97, 0x2D9F), + (0x2DA7, 0x2DA7), + (0x2DAF, 0x2DAF), + (0x2DB7, 0x2DB7), + (0x2DBF, 0x2DBF), + (0x2DC7, 0x2DC7), + (0x2DCF, 0x2DCF), + (0x2DD7, 0x2DD7), + (0x2DDF, 0x2DDF), + (0x2E5E, 0x2E7F), + (0x2E9A, 0x2E9A), + (0x2EF4, 0x2EFF), + (0x2FD6, 0x2FEF), + (0x3000, 0x3000), + (0x3040, 0x3040), + (0x3097, 0x3098), + (0x3100, 0x3104), + (0x3130, 0x3130), + (0x318F, 0x318F), + (0x31E6, 0x31EE), + (0x321F, 0x321F), + (0xA48D, 0xA48F), + (0xA4C7, 0xA4CF), + (0xA62C, 0xA63F), + (0xA6F8, 0xA6FF), + (0xA7CE, 0xA7CF), + (0xA7D2, 0xA7D2), + (0xA7D4, 0xA7D4), + (0xA7DD, 0xA7F1), + (0xA82D, 0xA82F), + (0xA83A, 0xA83F), + (0xA878, 0xA87F), + (0xA8C6, 0xA8CD), + (0xA8DA, 0xA8DF), + (0xA954, 0xA95E), + (0xA97D, 0xA97F), + (0xA9CE, 0xA9CE), + (0xA9DA, 0xA9DD), + (0xA9FF, 0xA9FF), + (0xAA37, 0xAA3F), + (0xAA4E, 0xAA4F), + (0xAA5A, 0xAA5B), + (0xAAC3, 0xAADA), + (0xAAF7, 0xAB00), + (0xAB07, 0xAB08), + (0xAB0F, 0xAB10), + (0xAB17, 0xAB1F), + (0xAB27, 0xAB27), + (0xAB2F, 0xAB2F), + (0xAB6C, 0xAB6F), + (0xABEE, 0xABEF), + (0xABFA, 0xABFF), + (0xD7A4, 0xD7AF), + (0xD7C7, 0xD7CA), + (0xD7FC, 0xF8FF), + (0xFA6E, 0xFA6F), + (0xFADA, 0xFAFF), + (0xFB07, 0xFB12), + (0xFB18, 0xFB1C), + (0xFB37, 0xFB37), + (0xFB3D, 0xFB3D), + (0xFB3F, 0xFB3F), + (0xFB42, 0xFB42), + (0xFB45, 0xFB45), + (0xFBC3, 0xFBD2), + (0xFD90, 0xFD91), + (0xFDC8, 0xFDCE), + (0xFDD0, 0xFDEF), + (0xFE1A, 0xFE1F), + (0xFE53, 0xFE53), + (0xFE67, 0xFE67), + (0xFE6C, 0xFE6F), + (0xFE75, 0xFE75), + (0xFEFD, 0xFF00), + (0xFFBF, 0xFFC1), + (0xFFC8, 0xFFC9), + (0xFFD0, 0xFFD1), + (0xFFD8, 0xFFD9), + (0xFFDD, 0xFFDF), + (0xFFE7, 0xFFE7), + (0xFFEF, 0xFFFB), + (0xFFFE, 0xFFFF), + (0x1000C, 0x1000C), + (0x10027, 0x10027), + (0x1003B, 0x1003B), + (0x1003E, 0x1003E), + (0x1004E, 0x1004F), + (0x1005E, 0x1007F), + (0x100FB, 0x100FF), + (0x10103, 0x10106), + (0x10134, 0x10136), + (0x1018F, 0x1018F), + (0x1019D, 0x1019F), + (0x101A1, 0x101CF), + (0x101FE, 0x1027F), + (0x1029D, 0x1029F), + (0x102D1, 0x102DF), + (0x102FC, 0x102FF), + (0x10324, 0x1032C), + (0x1034B, 0x1034F), + (0x1037B, 0x1037F), + (0x1039E, 0x1039E), + (0x103C4, 0x103C7), + (0x103D6, 0x103FF), + (0x1049E, 0x1049F), + (0x104AA, 0x104AF), + (0x104D4, 0x104D7), + (0x104FC, 0x104FF), + (0x10528, 0x1052F), + (0x10564, 0x1056E), + (0x1057B, 0x1057B), + (0x1058B, 0x1058B), + (0x10593, 0x10593), + (0x10596, 0x10596), + (0x105A2, 0x105A2), + (0x105B2, 0x105B2), + (0x105BA, 0x105BA), + (0x105BD, 0x105BF), + (0x105F4, 0x105FF), + (0x10737, 0x1073F), + (0x10756, 0x1075F), + (0x10768, 0x1077F), + (0x10786, 0x10786), + (0x107B1, 0x107B1), + (0x107BB, 0x107FF), + (0x10806, 0x10807), + (0x10809, 0x10809), + (0x10836, 0x10836), + (0x10839, 0x1083B), + (0x1083D, 0x1083E), + (0x10856, 0x10856), + (0x1089F, 0x108A6), + (0x108B0, 0x108DF), + (0x108F3, 0x108F3), + (0x108F6, 0x108FA), + (0x1091C, 0x1091E), + (0x1093A, 0x1093E), + (0x10940, 0x1097F), + (0x109B8, 0x109BB), + (0x109D0, 0x109D1), + (0x10A04, 0x10A04), + (0x10A07, 0x10A0B), + (0x10A14, 0x10A14), + (0x10A18, 0x10A18), + (0x10A36, 0x10A37), + (0x10A3B, 0x10A3E), + (0x10A49, 0x10A4F), + (0x10A59, 0x10A5F), + (0x10AA0, 0x10ABF), + (0x10AE7, 0x10AEA), + (0x10AF7, 0x10AFF), + (0x10B36, 0x10B38), + (0x10B56, 0x10B57), + (0x10B73, 0x10B77), + (0x10B92, 0x10B98), + (0x10B9D, 0x10BA8), + (0x10BB0, 0x10BFF), + (0x10C49, 0x10C7F), + (0x10CB3, 0x10CBF), + (0x10CF3, 0x10CF9), + (0x10D28, 0x10D2F), + (0x10D3A, 0x10D3F), + (0x10D66, 0x10D68), + (0x10D86, 0x10D8D), + (0x10D90, 0x10E5F), + (0x10E7F, 0x10E7F), + (0x10EAA, 0x10EAA), + (0x10EAE, 0x10EAF), + (0x10EB2, 0x10EC1), + (0x10EC5, 0x10EFB), + (0x10F28, 0x10F2F), + (0x10F5A, 0x10F6F), + (0x10F8A, 0x10FAF), + (0x10FCC, 0x10FDF), + (0x10FF7, 0x10FFF), + (0x1104E, 0x11051), + (0x11076, 0x1107E), + (0x110BD, 0x110BD), + (0x110C3, 0x110CF), + (0x110E9, 0x110EF), + (0x110FA, 0x110FF), + (0x11135, 0x11135), + (0x11148, 0x1114F), + (0x11177, 0x1117F), + (0x111E0, 0x111E0), + (0x111F5, 0x111FF), + (0x11212, 0x11212), + (0x11242, 0x1127F), + (0x11287, 0x11287), + (0x11289, 0x11289), + (0x1128E, 0x1128E), + (0x1129E, 0x1129E), + (0x112AA, 0x112AF), + (0x112EB, 0x112EF), + (0x112FA, 0x112FF), + (0x11304, 0x11304), + (0x1130D, 0x1130E), + (0x11311, 0x11312), + (0x11329, 0x11329), + (0x11331, 0x11331), + (0x11334, 0x11334), + (0x1133A, 0x1133A), + (0x11345, 0x11346), + (0x11349, 0x1134A), + (0x1134E, 0x1134F), + (0x11351, 0x11356), + (0x11358, 0x1135C), + (0x11364, 0x11365), + (0x1136D, 0x1136F), + (0x11375, 0x1137F), + (0x1138A, 0x1138A), + (0x1138C, 0x1138D), + (0x1138F, 0x1138F), + (0x113B6, 0x113B6), + (0x113C1, 0x113C1), + (0x113C3, 0x113C4), + (0x113C6, 0x113C6), + (0x113CB, 0x113CB), + (0x113D6, 0x113D6), + (0x113D9, 0x113E0), + (0x113E3, 0x113FF), + (0x1145C, 0x1145C), + (0x11462, 0x1147F), + (0x114C8, 0x114CF), + (0x114DA, 0x1157F), + (0x115B6, 0x115B7), + (0x115DE, 0x115FF), + (0x11645, 0x1164F), + (0x1165A, 0x1165F), + (0x1166D, 0x1167F), + (0x116BA, 0x116BF), + (0x116CA, 0x116CF), + (0x116E4, 0x116FF), + (0x1171B, 0x1171C), + (0x1172C, 0x1172F), + (0x11747, 0x117FF), + (0x1183C, 0x1189F), + (0x118F3, 0x118FE), + (0x11907, 0x11908), + (0x1190A, 0x1190B), + (0x11914, 0x11914), + (0x11917, 0x11917), + (0x11936, 0x11936), + (0x11939, 0x1193A), + (0x11947, 0x1194F), + (0x1195A, 0x1199F), + (0x119A8, 0x119A9), + (0x119D8, 0x119D9), + (0x119E5, 0x119FF), + (0x11A48, 0x11A4F), + (0x11AA3, 0x11AAF), + (0x11AF9, 0x11AFF), + (0x11B0A, 0x11BBF), + (0x11BE2, 0x11BEF), + (0x11BFA, 0x11BFF), + (0x11C09, 0x11C09), + (0x11C37, 0x11C37), + (0x11C46, 0x11C4F), + (0x11C6D, 0x11C6F), + (0x11C90, 0x11C91), + (0x11CA8, 0x11CA8), + (0x11CB7, 0x11CFF), + (0x11D07, 0x11D07), + (0x11D0A, 0x11D0A), + (0x11D37, 0x11D39), + (0x11D3B, 0x11D3B), + (0x11D3E, 0x11D3E), + (0x11D48, 0x11D4F), + (0x11D5A, 0x11D5F), + (0x11D66, 0x11D66), + (0x11D69, 0x11D69), + (0x11D8F, 0x11D8F), + (0x11D92, 0x11D92), + (0x11D99, 0x11D9F), + (0x11DAA, 0x11EDF), + (0x11EF9, 0x11EFF), + (0x11F11, 0x11F11), + (0x11F3B, 0x11F3D), + (0x11F5B, 0x11FAF), + (0x11FB1, 0x11FBF), + (0x11FF2, 0x11FFE), + (0x1239A, 0x123FF), + (0x1246F, 0x1246F), + (0x12475, 0x1247F), + (0x12544, 0x12F8F), + (0x12FF3, 0x12FFF), + (0x13430, 0x1343F), + (0x13456, 0x1345F), + (0x143FB, 0x143FF), + (0x14647, 0x160FF), + (0x1613A, 0x167FF), + (0x16A39, 0x16A3F), + (0x16A5F, 0x16A5F), + (0x16A6A, 0x16A6D), + (0x16ABF, 0x16ABF), + (0x16ACA, 0x16ACF), + (0x16AEE, 0x16AEF), + (0x16AF6, 0x16AFF), + (0x16B46, 0x16B4F), + (0x16B5A, 0x16B5A), + (0x16B62, 0x16B62), + (0x16B78, 0x16B7C), + (0x16B90, 0x16D3F), + (0x16D7A, 0x16E3F), + (0x16E9B, 0x16EFF), + (0x16F4B, 0x16F4E), + (0x16F88, 0x16F8E), + (0x16FA0, 0x16FDF), + (0x16FE5, 0x16FEF), + (0x16FF2, 0x16FFF), + (0x187F8, 0x187FF), + (0x18CD6, 0x18CFE), + (0x18D09, 0x1AFEF), + (0x1AFF4, 0x1AFF4), + (0x1AFFC, 0x1AFFC), + (0x1AFFF, 0x1AFFF), + (0x1B123, 0x1B131), + (0x1B133, 0x1B14F), + (0x1B153, 0x1B154), + (0x1B156, 0x1B163), + (0x1B168, 0x1B16F), + (0x1B2FC, 0x1BBFF), + (0x1BC6B, 0x1BC6F), + (0x1BC7D, 0x1BC7F), + (0x1BC89, 0x1BC8F), + (0x1BC9A, 0x1BC9B), + (0x1BCA0, 0x1CBFF), + (0x1CCFA, 0x1CCFF), + (0x1CEB4, 0x1CEFF), + (0x1CF2E, 0x1CF2F), + (0x1CF47, 0x1CF4F), + (0x1CFC4, 0x1CFFF), + (0x1D0F6, 0x1D0FF), + (0x1D127, 0x1D128), + (0x1D173, 0x1D17A), + (0x1D1EB, 0x1D1FF), + (0x1D246, 0x1D2BF), + (0x1D2D4, 0x1D2DF), + (0x1D2F4, 0x1D2FF), + (0x1D357, 0x1D35F), + (0x1D379, 0x1D3FF), + (0x1D455, 0x1D455), + (0x1D49D, 0x1D49D), + (0x1D4A0, 0x1D4A1), + (0x1D4A3, 0x1D4A4), + (0x1D4A7, 0x1D4A8), + (0x1D4AD, 0x1D4AD), + (0x1D4BA, 0x1D4BA), + (0x1D4BC, 0x1D4BC), + (0x1D4C4, 0x1D4C4), + (0x1D506, 0x1D506), + (0x1D50B, 0x1D50C), + (0x1D515, 0x1D515), + (0x1D51D, 0x1D51D), + (0x1D53A, 0x1D53A), + (0x1D53F, 0x1D53F), + (0x1D545, 0x1D545), + (0x1D547, 0x1D549), + (0x1D551, 0x1D551), + (0x1D6A6, 0x1D6A7), + (0x1D7CC, 0x1D7CD), + (0x1DA8C, 0x1DA9A), + (0x1DAA0, 0x1DAA0), + (0x1DAB0, 0x1DEFF), + (0x1DF1F, 0x1DF24), + (0x1DF2B, 0x1DFFF), + (0x1E007, 0x1E007), + (0x1E019, 0x1E01A), + (0x1E022, 0x1E022), + (0x1E025, 0x1E025), + (0x1E02B, 0x1E02F), + (0x1E06E, 0x1E08E), + (0x1E090, 0x1E0FF), + (0x1E12D, 0x1E12F), + (0x1E13E, 0x1E13F), + (0x1E14A, 0x1E14D), + (0x1E150, 0x1E28F), + (0x1E2AF, 0x1E2BF), + (0x1E2FA, 0x1E2FE), + (0x1E300, 0x1E4CF), + (0x1E4FA, 0x1E5CF), + (0x1E5FB, 0x1E5FE), + (0x1E600, 0x1E7DF), + (0x1E7E7, 0x1E7E7), + (0x1E7EC, 0x1E7EC), + (0x1E7EF, 0x1E7EF), + (0x1E7FF, 0x1E7FF), + (0x1E8C5, 0x1E8C6), + (0x1E8D7, 0x1E8FF), + (0x1E94C, 0x1E94F), + (0x1E95A, 0x1E95D), + (0x1E960, 0x1EC70), + (0x1ECB5, 0x1ED00), + (0x1ED3E, 0x1EDFF), + (0x1EE04, 0x1EE04), + (0x1EE20, 0x1EE20), + (0x1EE23, 0x1EE23), + (0x1EE25, 0x1EE26), + (0x1EE28, 0x1EE28), + (0x1EE33, 0x1EE33), + (0x1EE38, 0x1EE38), + (0x1EE3A, 0x1EE3A), + (0x1EE3C, 0x1EE41), + (0x1EE43, 0x1EE46), + (0x1EE48, 0x1EE48), + (0x1EE4A, 0x1EE4A), + (0x1EE4C, 0x1EE4C), + (0x1EE50, 0x1EE50), + (0x1EE53, 0x1EE53), + (0x1EE55, 0x1EE56), + (0x1EE58, 0x1EE58), + (0x1EE5A, 0x1EE5A), + (0x1EE5C, 0x1EE5C), + (0x1EE5E, 0x1EE5E), + (0x1EE60, 0x1EE60), + (0x1EE63, 0x1EE63), + (0x1EE65, 0x1EE66), + (0x1EE6B, 0x1EE6B), + (0x1EE73, 0x1EE73), + (0x1EE78, 0x1EE78), + (0x1EE7D, 0x1EE7D), + (0x1EE7F, 0x1EE7F), + (0x1EE8A, 0x1EE8A), + (0x1EE9C, 0x1EEA0), + (0x1EEA4, 0x1EEA4), + (0x1EEAA, 0x1EEAA), + (0x1EEBC, 0x1EEEF), + (0x1EEF2, 0x1EFFF), + (0x1F02C, 0x1F02F), + (0x1F094, 0x1F09F), + (0x1F0AF, 0x1F0B0), + (0x1F0C0, 0x1F0C0), + (0x1F0D0, 0x1F0D0), + (0x1F0F6, 0x1F0FF), + (0x1F1AE, 0x1F1E5), + (0x1F203, 0x1F20F), + (0x1F23C, 0x1F23F), + (0x1F249, 0x1F24F), + (0x1F252, 0x1F25F), + (0x1F266, 0x1F2FF), + (0x1F6D8, 0x1F6DB), + (0x1F6ED, 0x1F6EF), + (0x1F6FD, 0x1F6FF), + (0x1F777, 0x1F77A), + (0x1F7DA, 0x1F7DF), + (0x1F7EC, 0x1F7EF), + (0x1F7F1, 0x1F7FF), + (0x1F80C, 0x1F80F), + (0x1F848, 0x1F84F), + (0x1F85A, 0x1F85F), + (0x1F888, 0x1F88F), + (0x1F8AE, 0x1F8AF), + (0x1F8BC, 0x1F8BF), + (0x1F8C2, 0x1F8FF), + (0x1FA54, 0x1FA5F), + (0x1FA6E, 0x1FA6F), + (0x1FA7D, 0x1FA7F), + (0x1FA8A, 0x1FA8E), + (0x1FAC7, 0x1FACD), + (0x1FADD, 0x1FADE), + (0x1FAEA, 0x1FAEF), + (0x1FAF9, 0x1FAFF), + (0x1FB93, 0x1FB93), + (0x1FBFA, 0x1FFFF), + (0x2A6E0, 0x2A6FF), + (0x2B73A, 0x2B73F), + (0x2B81E, 0x2B81F), + (0x2CEA2, 0x2CEAF), + (0x2EBE1, 0x2EBEF), + (0x2EE5E, 0x2F7FF), + (0x2FA1E, 0x2FFFF), + (0x3134B, 0x3134F), + (0x323B0, 0xE00FF), + (0xE01F0, 0x10FFFF), +]; diff --git a/litellm-rust/crates/python-compat/generated/values.json b/litellm-rust/crates/python-compat/generated/values.json new file mode 100644 index 00000000000..7f7e1e35a7d --- /dev/null +++ b/litellm-rust/crates/python-compat/generated/values.json @@ -0,0 +1,2164 @@ +{ + "python": "3.14.7", + "rows": [ + { + "name": "None", + "source": "None", + "literal": true, + "plain": true, + "repr": "None", + "str": "None", + "truthy": false, + "json": "null", + "pickle": { + "0": "4e2e", + "1": "4e2e", + "2": "80024e2e", + "3": "80034e2e", + "4": "80044e2e", + "5": "80054e2e" + }, + "view": "None" + }, + { + "name": "True", + "source": "True", + "literal": true, + "plain": true, + "repr": "True", + "str": "True", + "truthy": true, + "json": "true", + "pickle": { + "0": "4930310a2e", + "1": "4930310a2e", + "2": "8002882e", + "3": "8003882e", + "4": "8004882e", + "5": "8005882e" + }, + "view": "True" + }, + { + "name": "False", + "source": "False", + "literal": true, + "plain": true, + "repr": "False", + "str": "False", + "truthy": false, + "json": "false", + "pickle": { + "0": "4930300a2e", + "1": "4930300a2e", + "2": "8002892e", + "3": "8003892e", + "4": "8004892e", + "5": "8005892e" + }, + "view": "False" + }, + { + "name": "0", + "source": "0", + "literal": true, + "plain": true, + "repr": "0", + "str": "0", + "truthy": false, + "json": "0", + "pickle": { + "0": "49300a2e", + "1": "4b002e", + "2": "80024b002e", + "3": "80034b002e", + "4": "80044b002e", + "5": "80054b002e" + }, + "view": "0" + }, + { + "name": "-7", + "source": "-7", + "literal": true, + "plain": true, + "repr": "-7", + "str": "-7", + "truthy": true, + "json": "-7", + "pickle": { + "0": "492d370a2e", + "1": "4af9ffffff2e", + "2": "80024af9ffffff2e", + "3": "80034af9ffffff2e", + "4": "80049506000000000000004af9ffffff2e", + "5": "80059506000000000000004af9ffffff2e" + }, + "view": "-7" + }, + { + "name": "2**63 - 1", + "source": "2**63 - 1", + "literal": true, + "plain": true, + "repr": "9223372036854775807", + "str": "9223372036854775807", + "truthy": true, + "json": "9223372036854775807", + "pickle": { + "0": "4c393232333337323033363835343737353830374c0a2e", + "1": "4c393232333337323033363835343737353830374c0a2e", + "2": "80028a08ffffffffffffff7f2e", + "3": "80038a08ffffffffffffff7f2e", + "4": "8004950b000000000000008a08ffffffffffffff7f2e", + "5": "8005950b000000000000008a08ffffffffffffff7f2e" + }, + "view": "9223372036854775807" + }, + { + "name": "-(2**63)", + "source": "-(2**63)", + "literal": true, + "plain": true, + "repr": "-9223372036854775808", + "str": "-9223372036854775808", + "truthy": true, + "json": "-9223372036854775808", + "pickle": { + "0": "4c2d393232333337323033363835343737353830384c0a2e", + "1": "4c2d393232333337323033363835343737353830384c0a2e", + "2": "80028a0800000000000000802e", + "3": "80038a0800000000000000802e", + "4": "8004950b000000000000008a0800000000000000802e", + "5": "8005950b000000000000008a0800000000000000802e" + }, + "view": "-9223372036854775808" + }, + { + "name": "2**64", + "source": "2**64", + "literal": true, + "plain": true, + "repr": "18446744073709551616", + "str": "18446744073709551616", + "truthy": true, + "json": "18446744073709551616", + "pickle": { + "0": "4c31383434363734343037333730393535313631364c0a2e", + "1": "4c31383434363734343037333730393535313631364c0a2e", + "2": "80028a090000000000000000012e", + "3": "80038a090000000000000000012e", + "4": "8004950c000000000000008a090000000000000000012e", + "5": "8005950c000000000000008a090000000000000000012e" + }, + "view": "18446744073709551616" + }, + { + "name": "-(2**70)", + "source": "-(2**70)", + "literal": true, + "plain": true, + "repr": "-1180591620717411303424", + "str": "-1180591620717411303424", + "truthy": true, + "json": "-1180591620717411303424", + "pickle": { + "0": "4c2d313138303539313632303731373431313330333432344c0a2e", + "1": "4c2d313138303539313632303731373431313330333432344c0a2e", + "2": "80028a090000000000000000c02e", + "3": "80038a090000000000000000c02e", + "4": "8004950c000000000000008a090000000000000000c02e", + "5": "8005950c000000000000008a090000000000000000c02e" + }, + "view": "-1180591620717411303424" + }, + { + "name": "0.0", + "source": "0.0", + "literal": true, + "plain": true, + "repr": "0.0", + "str": "0.0", + "truthy": false, + "json": "0.0", + "pickle": { + "0": "46302e300a2e", + "1": "4700000000000000002e", + "2": "80024700000000000000002e", + "3": "80034700000000000000002e", + "4": "8004950a000000000000004700000000000000002e", + "5": "8005950a000000000000004700000000000000002e" + }, + "view": "0.0" + }, + { + "name": "-0.0", + "source": "-0.0", + "literal": true, + "plain": true, + "repr": "-0.0", + "str": "-0.0", + "truthy": false, + "json": "-0.0", + "pickle": { + "0": "462d302e300a2e", + "1": "4780000000000000002e", + "2": "80024780000000000000002e", + "3": "80034780000000000000002e", + "4": "8004950a000000000000004780000000000000002e", + "5": "8005950a000000000000004780000000000000002e" + }, + "view": "-0.0" + }, + { + "name": "0.2", + "source": "0.2", + "literal": true, + "plain": true, + "repr": "0.2", + "str": "0.2", + "truthy": true, + "json": "0.2", + "pickle": { + "0": "46302e320a2e", + "1": "473fc999999999999a2e", + "2": "8002473fc999999999999a2e", + "3": "8003473fc999999999999a2e", + "4": "8004950a00000000000000473fc999999999999a2e", + "5": "8005950a00000000000000473fc999999999999a2e" + }, + "view": "0.2" + }, + { + "name": "1.0", + "source": "1.0", + "literal": true, + "plain": true, + "repr": "1.0", + "str": "1.0", + "truthy": true, + "json": "1.0", + "pickle": { + "0": "46312e300a2e", + "1": "473ff00000000000002e", + "2": "8002473ff00000000000002e", + "3": "8003473ff00000000000002e", + "4": "8004950a00000000000000473ff00000000000002e", + "5": "8005950a00000000000000473ff00000000000002e" + }, + "view": "1.0" + }, + { + "name": "-1.5", + "source": "-1.5", + "literal": true, + "plain": true, + "repr": "-1.5", + "str": "-1.5", + "truthy": true, + "json": "-1.5", + "pickle": { + "0": "462d312e350a2e", + "1": "47bff80000000000002e", + "2": "800247bff80000000000002e", + "3": "800347bff80000000000002e", + "4": "8004950a0000000000000047bff80000000000002e", + "5": "8005950a0000000000000047bff80000000000002e" + }, + "view": "-1.5" + }, + { + "name": "0.1 + 0.2", + "source": "0.1 + 0.2", + "literal": true, + "plain": true, + "repr": "0.30000000000000004", + "str": "0.30000000000000004", + "truthy": true, + "json": "0.30000000000000004", + "pickle": { + "0": "46302e33303030303030303030303030303030340a2e", + "1": "473fd33333333333342e", + "2": "8002473fd33333333333342e", + "3": "8003473fd33333333333342e", + "4": "8004950a00000000000000473fd33333333333342e", + "5": "8005950a00000000000000473fd33333333333342e" + }, + "view": "0.30000000000000004" + }, + { + "name": "123456789.123", + "source": "123456789.123", + "literal": true, + "plain": true, + "repr": "123456789.123", + "str": "123456789.123", + "truthy": true, + "json": "123456789.123", + "pickle": { + "0": "463132333435363738392e3132330a2e", + "1": "47419d6f34547df3b62e", + "2": "800247419d6f34547df3b62e", + "3": "800347419d6f34547df3b62e", + "4": "8004950a0000000000000047419d6f34547df3b62e", + "5": "8005950a0000000000000047419d6f34547df3b62e" + }, + "view": "123456789.123" + }, + { + "name": "1e15", + "source": "1e15", + "literal": true, + "plain": true, + "repr": "1000000000000000.0", + "str": "1000000000000000.0", + "truthy": true, + "json": "1000000000000000.0", + "pickle": { + "0": "46313030303030303030303030303030302e300a2e", + "1": "47430c6bf5263400002e", + "2": "800247430c6bf5263400002e", + "3": "800347430c6bf5263400002e", + "4": "8004950a0000000000000047430c6bf5263400002e", + "5": "8005950a0000000000000047430c6bf5263400002e" + }, + "view": "1000000000000000.0" + }, + { + "name": "1e16", + "source": "1e16", + "literal": true, + "plain": true, + "repr": "1e+16", + "str": "1e+16", + "truthy": true, + "json": "1e+16", + "pickle": { + "0": "4631652b31360a2e", + "1": "474341c37937e080002e", + "2": "8002474341c37937e080002e", + "3": "8003474341c37937e080002e", + "4": "8004950a00000000000000474341c37937e080002e", + "5": "8005950a00000000000000474341c37937e080002e" + }, + "view": "1e+16" + }, + { + "name": "1.5e16", + "source": "1.5e16", + "literal": true, + "plain": true, + "repr": "1.5e+16", + "str": "1.5e+16", + "truthy": true, + "json": "1.5e+16", + "pickle": { + "0": "46312e35652b31360a2e", + "1": "47434aa535d3d0c0002e", + "2": "800247434aa535d3d0c0002e", + "3": "800347434aa535d3d0c0002e", + "4": "8004950a0000000000000047434aa535d3d0c0002e", + "5": "8005950a0000000000000047434aa535d3d0c0002e" + }, + "view": "1.5e+16" + }, + { + "name": "9999999999999998.0", + "source": "9999999999999998.0", + "literal": true, + "plain": true, + "repr": "9999999999999998.0", + "str": "9999999999999998.0", + "truthy": true, + "json": "9999999999999998.0", + "pickle": { + "0": "46393939393939393939393939393939382e300a2e", + "1": "474341c37937e07fff2e", + "2": "8002474341c37937e07fff2e", + "3": "8003474341c37937e07fff2e", + "4": "8004950a00000000000000474341c37937e07fff2e", + "5": "8005950a00000000000000474341c37937e07fff2e" + }, + "view": "9999999999999998.0" + }, + { + "name": "0.0001", + "source": "0.0001", + "literal": true, + "plain": true, + "repr": "0.0001", + "str": "0.0001", + "truthy": true, + "json": "0.0001", + "pickle": { + "0": "46302e303030310a2e", + "1": "473f1a36e2eb1c432d2e", + "2": "8002473f1a36e2eb1c432d2e", + "3": "8003473f1a36e2eb1c432d2e", + "4": "8004950a00000000000000473f1a36e2eb1c432d2e", + "5": "8005950a00000000000000473f1a36e2eb1c432d2e" + }, + "view": "0.0001" + }, + { + "name": "1e-05", + "source": "1e-05", + "literal": true, + "plain": true, + "repr": "1e-05", + "str": "1e-05", + "truthy": true, + "json": "1e-05", + "pickle": { + "0": "4631652d30350a2e", + "1": "473ee4f8b588e368f12e", + "2": "8002473ee4f8b588e368f12e", + "3": "8003473ee4f8b588e368f12e", + "4": "8004950a00000000000000473ee4f8b588e368f12e", + "5": "8005950a00000000000000473ee4f8b588e368f12e" + }, + "view": "1e-05" + }, + { + "name": "1.25e-07", + "source": "1.25e-07", + "literal": true, + "plain": true, + "repr": "1.25e-07", + "str": "1.25e-07", + "truthy": true, + "json": "1.25e-07", + "pickle": { + "0": "46312e3235652d30370a2e", + "1": "473e80c6f7a0b5ed8d2e", + "2": "8002473e80c6f7a0b5ed8d2e", + "3": "8003473e80c6f7a0b5ed8d2e", + "4": "8004950a00000000000000473e80c6f7a0b5ed8d2e", + "5": "8005950a00000000000000473e80c6f7a0b5ed8d2e" + }, + "view": "1.25e-07" + }, + { + "name": "5e-324", + "source": "5e-324", + "literal": true, + "plain": true, + "repr": "5e-324", + "str": "5e-324", + "truthy": true, + "json": "5e-324", + "pickle": { + "0": "4635652d3332340a2e", + "1": "4700000000000000012e", + "2": "80024700000000000000012e", + "3": "80034700000000000000012e", + "4": "8004950a000000000000004700000000000000012e", + "5": "8005950a000000000000004700000000000000012e" + }, + "view": "5e-324" + }, + { + "name": "1.7976931348623157e308", + "source": "1.7976931348623157e308", + "literal": true, + "plain": true, + "repr": "1.7976931348623157e+308", + "str": "1.7976931348623157e+308", + "truthy": true, + "json": "1.7976931348623157e+308", + "pickle": { + "0": "46312e37393736393331333438363233313537652b3330380a2e", + "1": "477fefffffffffffff2e", + "2": "8002477fefffffffffffff2e", + "3": "8003477fefffffffffffff2e", + "4": "8004950a00000000000000477fefffffffffffff2e", + "5": "8005950a00000000000000477fefffffffffffff2e" + }, + "view": "1.7976931348623157e+308" + }, + { + "name": "1e22", + "source": "1e22", + "literal": true, + "plain": true, + "repr": "1e+22", + "str": "1e+22", + "truthy": true, + "json": "1e+22", + "pickle": { + "0": "4631652b32320a2e", + "1": "474480f0cf064dd5922e", + "2": "8002474480f0cf064dd5922e", + "3": "8003474480f0cf064dd5922e", + "4": "8004950a00000000000000474480f0cf064dd5922e", + "5": "8005950a00000000000000474480f0cf064dd5922e" + }, + "view": "1e+22" + }, + { + "name": "float('inf')", + "source": "float('inf')", + "literal": false, + "plain": true, + "repr": "inf", + "str": "inf", + "truthy": true, + "json": "Infinity", + "pickle": { + "0": "46696e660a2e", + "1": "477ff00000000000002e", + "2": "8002477ff00000000000002e", + "3": "8003477ff00000000000002e", + "4": "8004950a00000000000000477ff00000000000002e", + "5": "8005950a00000000000000477ff00000000000002e" + }, + "view": "inf" + }, + { + "name": "float('-inf')", + "source": "float('-inf')", + "literal": false, + "plain": true, + "repr": "-inf", + "str": "-inf", + "truthy": true, + "json": "-Infinity", + "pickle": { + "0": "462d696e660a2e", + "1": "47fff00000000000002e", + "2": "800247fff00000000000002e", + "3": "800347fff00000000000002e", + "4": "8004950a0000000000000047fff00000000000002e", + "5": "8005950a0000000000000047fff00000000000002e" + }, + "view": "-inf" + }, + { + "name": "float('nan')", + "source": "float('nan')", + "literal": false, + "plain": true, + "repr": "nan", + "str": "nan", + "truthy": true, + "json": "NaN", + "pickle": { + "0": "466e616e0a2e", + "1": "477ff80000000000002e", + "2": "8002477ff80000000000002e", + "3": "8003477ff80000000000002e", + "4": "8004950a00000000000000477ff80000000000002e", + "5": "8005950a00000000000000477ff80000000000002e" + }, + "view": "nan" + }, + { + "name": "1j", + "source": "1j", + "literal": true, + "plain": false, + "repr": "1j", + "str": "1j", + "truthy": true, + "json_error": "Object of type complex is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a636f6d706c65780a70300a2846302e300a46312e300a7470310a5270320a2e", + "1": "635f5f6275696c74696e5f5f0a636f6d706c65780a710028470000000000000000473ff00000000000007471015271022e", + "2": "8002635f5f6275696c74696e5f5f0a636f6d706c65780a7100470000000000000000473ff00000000000008671015271022e", + "3": "8003636275696c74696e730a636f6d706c65780a7100470000000000000000473ff00000000000008671015271022e", + "4": "8004952e000000000000008c086275696c74696e73948c07636f6d706c6578949394470000000000000000473ff0000000000000869452942e", + "5": "8005952e000000000000008c086275696c74696e73948c07636f6d706c6578949394470000000000000000473ff0000000000000869452942e" + }, + "view": "1j" + }, + { + "name": "-1j", + "source": "-1j", + "literal": false, + "plain": false, + "repr": "(-0-1j)", + "str": "(-0-1j)", + "truthy": true, + "json_error": "Object of type complex is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a636f6d706c65780a70300a28462d302e300a462d312e300a7470310a5270320a2e", + "1": "635f5f6275696c74696e5f5f0a636f6d706c65780a71002847800000000000000047bff00000000000007471015271022e", + "2": "8002635f5f6275696c74696e5f5f0a636f6d706c65780a710047800000000000000047bff00000000000008671015271022e", + "3": "8003636275696c74696e730a636f6d706c65780a710047800000000000000047bff00000000000008671015271022e", + "4": "8004952e000000000000008c086275696c74696e73948c07636f6d706c657894939447800000000000000047bff0000000000000869452942e", + "5": "8005952e000000000000008c086275696c74696e73948c07636f6d706c657894939447800000000000000047bff0000000000000869452942e" + }, + "view": "(-0-1j)" + }, + { + "name": "complex(0, -1)", + "source": "complex(0, -1)", + "literal": false, + "plain": false, + "repr": "-1j", + "str": "-1j", + "truthy": true, + "json_error": "Object of type complex is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a636f6d706c65780a70300a2846302e300a462d312e300a7470310a5270320a2e", + "1": "635f5f6275696c74696e5f5f0a636f6d706c65780a71002847000000000000000047bff00000000000007471015271022e", + "2": "8002635f5f6275696c74696e5f5f0a636f6d706c65780a710047000000000000000047bff00000000000008671015271022e", + "3": "8003636275696c74696e730a636f6d706c65780a710047000000000000000047bff00000000000008671015271022e", + "4": "8004952e000000000000008c086275696c74696e73948c07636f6d706c657894939447000000000000000047bff0000000000000869452942e", + "5": "8005952e000000000000008c086275696c74696e73948c07636f6d706c657894939447000000000000000047bff0000000000000869452942e" + }, + "view": "-1j" + }, + { + "name": "1+2j", + "source": "1+2j", + "literal": true, + "plain": false, + "repr": "(1+2j)", + "str": "(1+2j)", + "truthy": true, + "json_error": "Object of type complex is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a636f6d706c65780a70300a2846312e300a46322e300a7470310a5270320a2e", + "1": "635f5f6275696c74696e5f5f0a636f6d706c65780a710028473ff00000000000004740000000000000007471015271022e", + "2": "8002635f5f6275696c74696e5f5f0a636f6d706c65780a7100473ff00000000000004740000000000000008671015271022e", + "3": "8003636275696c74696e730a636f6d706c65780a7100473ff00000000000004740000000000000008671015271022e", + "4": "8004952e000000000000008c086275696c74696e73948c07636f6d706c6578949394473ff0000000000000474000000000000000869452942e", + "5": "8005952e000000000000008c086275696c74696e73948c07636f6d706c6578949394473ff0000000000000474000000000000000869452942e" + }, + "view": "(1+2j)" + }, + { + "name": "-1.5-0.5j", + "source": "-1.5-0.5j", + "literal": true, + "plain": false, + "repr": "(-1.5-0.5j)", + "str": "(-1.5-0.5j)", + "truthy": true, + "json_error": "Object of type complex is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a636f6d706c65780a70300a28462d312e350a462d302e350a7470310a5270320a2e", + "1": "635f5f6275696c74696e5f5f0a636f6d706c65780a71002847bff800000000000047bfe00000000000007471015271022e", + "2": "8002635f5f6275696c74696e5f5f0a636f6d706c65780a710047bff800000000000047bfe00000000000008671015271022e", + "3": "8003636275696c74696e730a636f6d706c65780a710047bff800000000000047bfe00000000000008671015271022e", + "4": "8004952e000000000000008c086275696c74696e73948c07636f6d706c657894939447bff800000000000047bfe0000000000000869452942e", + "5": "8005952e000000000000008c086275696c74696e73948c07636f6d706c657894939447bff800000000000047bfe0000000000000869452942e" + }, + "view": "(-1.5-0.5j)" + }, + { + "name": "complex(0.0, 1e16)", + "source": "complex(0.0, 1e16)", + "literal": true, + "plain": false, + "repr": "1e+16j", + "str": "1e+16j", + "truthy": true, + "json_error": "Object of type complex is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a636f6d706c65780a70300a2846302e300a4631652b31360a7470310a5270320a2e", + "1": "635f5f6275696c74696e5f5f0a636f6d706c65780a710028470000000000000000474341c37937e080007471015271022e", + "2": "8002635f5f6275696c74696e5f5f0a636f6d706c65780a7100470000000000000000474341c37937e080008671015271022e", + "3": "8003636275696c74696e730a636f6d706c65780a7100470000000000000000474341c37937e080008671015271022e", + "4": "8004952e000000000000008c086275696c74696e73948c07636f6d706c6578949394470000000000000000474341c37937e08000869452942e", + "5": "8005952e000000000000008c086275696c74696e73948c07636f6d706c6578949394470000000000000000474341c37937e08000869452942e" + }, + "view": "1e+16j" + }, + { + "name": "''", + "source": "''", + "literal": true, + "plain": true, + "repr": "''", + "str": "", + "truthy": false, + "json": "\"\"", + "pickle": { + "0": "560a70300a2e", + "1": "580000000071002e", + "2": "8002580000000071002e", + "3": "8003580000000071002e", + "4": "80049504000000000000008c00942e", + "5": "80059504000000000000008c00942e" + }, + "view": "''" + }, + { + "name": "'plain'", + "source": "'plain'", + "literal": true, + "plain": true, + "repr": "'plain'", + "str": "plain", + "truthy": true, + "json": "\"plain\"", + "pickle": { + "0": "56706c61696e0a70300a2e", + "1": "5805000000706c61696e71002e", + "2": "80025805000000706c61696e71002e", + "3": "80035805000000706c61696e71002e", + "4": "80049509000000000000008c05706c61696e942e", + "5": "80059509000000000000008c05706c61696e942e" + }, + "view": "'plain'" + }, + { + "name": "\"it's\"", + "source": "\"it's\"", + "literal": true, + "plain": true, + "repr": "\"it's\"", + "str": "it's", + "truthy": true, + "json": "\"it's\"", + "pickle": { + "0": "56697427730a70300a2e", + "1": "58040000006974277371002e", + "2": "800258040000006974277371002e", + "3": "800358040000006974277371002e", + "4": "80049508000000000000008c0469742773942e", + "5": "80059508000000000000008c0469742773942e" + }, + "view": "\"it's\"" + }, + { + "name": "'say \"hi\"'", + "source": "'say \"hi\"'", + "literal": true, + "plain": true, + "repr": "'say \"hi\"'", + "str": "say \"hi\"", + "truthy": true, + "json": "\"say \\\"hi\\\"\"", + "pickle": { + "0": "5673617920226869220a70300a2e", + "1": "5808000000736179202268692271002e", + "2": "80025808000000736179202268692271002e", + "3": "80035808000000736179202268692271002e", + "4": "8004950c000000000000008c087361792022686922942e", + "5": "8005950c000000000000008c087361792022686922942e" + }, + "view": "'say \"hi\"'" + }, + { + "name": "'both \\' and \"'", + "source": "'both \\' and \"'", + "literal": true, + "plain": true, + "repr": "'both \\' and \"'", + "str": "both ' and \"", + "truthy": true, + "json": "\"both ' and \\\"\"", + "pickle": { + "0": "56626f7468202720616e6420220a70300a2e", + "1": "580c000000626f7468202720616e64202271002e", + "2": "8002580c000000626f7468202720616e64202271002e", + "3": "8003580c000000626f7468202720616e64202271002e", + "4": "80049510000000000000008c0c626f7468202720616e642022942e", + "5": "80059510000000000000008c0c626f7468202720616e642022942e" + }, + "view": "'both \\' and \"'" + }, + { + "name": "'back\\\\slash'", + "source": "'back\\\\slash'", + "literal": true, + "plain": true, + "repr": "'back\\\\slash'", + "str": "back\\slash", + "truthy": true, + "json": "\"back\\\\slash\"", + "pickle": { + "0": "566261636b5c7530303563736c6173680a70300a2e", + "1": "580a0000006261636b5c736c61736871002e", + "2": "8002580a0000006261636b5c736c61736871002e", + "3": "8003580a0000006261636b5c736c61736871002e", + "4": "8004950e000000000000008c0a6261636b5c736c617368942e", + "5": "8005950e000000000000008c0a6261636b5c736c617368942e" + }, + "view": "'back\\\\slash'" + }, + { + "name": "'\\t\\n\\r'", + "source": "'\\t\\n\\r'", + "literal": true, + "plain": true, + "repr": "'\\t\\n\\r'", + "str": "\t\n\r", + "truthy": true, + "json": "\"\\t\\n\\r\"", + "pickle": { + "0": "56095c75303030615c75303030640a70300a2e", + "1": "5803000000090a0d71002e", + "2": "80025803000000090a0d71002e", + "3": "80035803000000090a0d71002e", + "4": "80049507000000000000008c03090a0d942e", + "5": "80059507000000000000008c03090a0d942e" + }, + "view": "'\\t\\n\\r'" + }, + { + "name": "'\\x00\\x1f\\x7f'", + "source": "'\\x00\\x1f\\x7f'", + "literal": true, + "plain": true, + "repr": "'\\x00\\x1f\\x7f'", + "str": "\u0000\u001f\u007f", + "truthy": true, + "json": "\"\\u0000\\u001f\\u007f\"", + "pickle": { + "0": "565c75303030301f7f0a70300a2e", + "1": "5803000000001f7f71002e", + "2": "80025803000000001f7f71002e", + "3": "80035803000000001f7f71002e", + "4": "80049507000000000000008c03001f7f942e", + "5": "80059507000000000000008c03001f7f942e" + }, + "view": "'\\x00\\x1f\\x7f'" + }, + { + "name": "'\\x85\\xa0\\xad'", + "source": "'\\x85\\xa0\\xad'", + "literal": true, + "plain": true, + "repr": "'\\x85\\xa0\\xad'", + "str": "\u0085\u00a0\u00ad", + "truthy": true, + "json": "\"\\u0085\\u00a0\\u00ad\"", + "pickle": { + "0": "5685a0ad0a70300a2e", + "1": "5806000000c285c2a0c2ad71002e", + "2": "80025806000000c285c2a0c2ad71002e", + "3": "80035806000000c285c2a0c2ad71002e", + "4": "8004950a000000000000008c06c285c2a0c2ad942e", + "5": "8005950a000000000000008c06c285c2a0c2ad942e" + }, + "view": "'\\x85\\xa0\\xad'" + }, + { + "name": "'caf\\xe9'", + "source": "'caf\\xe9'", + "literal": true, + "plain": true, + "repr": "'caf\u00e9'", + "str": "caf\u00e9", + "truthy": true, + "json": "\"caf\\u00e9\"", + "pickle": { + "0": "56636166e90a70300a2e", + "1": "5805000000636166c3a971002e", + "2": "80025805000000636166c3a971002e", + "3": "80035805000000636166c3a971002e", + "4": "80049509000000000000008c05636166c3a9942e", + "5": "80059509000000000000008c05636166c3a9942e" + }, + "view": "'caf\u00e9'" + }, + { + "name": "'\\u65e5\\u672c'", + "source": "'\\u65e5\\u672c'", + "literal": true, + "plain": true, + "repr": "'\u65e5\u672c'", + "str": "\u65e5\u672c", + "truthy": true, + "json": "\"\\u65e5\\u672c\"", + "pickle": { + "0": "565c75363565355c75363732630a70300a2e", + "1": "5806000000e697a5e69cac71002e", + "2": "80025806000000e697a5e69cac71002e", + "3": "80035806000000e697a5e69cac71002e", + "4": "8004950a000000000000008c06e697a5e69cac942e", + "5": "8005950a000000000000008c06e697a5e69cac942e" + }, + "view": "'\u65e5\u672c'" + }, + { + "name": "'\\u200b\\u2028\\u3000'", + "source": "'\\u200b\\u2028\\u3000'", + "literal": true, + "plain": true, + "repr": "'\\u200b\\u2028\\u3000'", + "str": "\u200b\u2028\u3000", + "truthy": true, + "json": "\"\\u200b\\u2028\\u3000\"", + "pickle": { + "0": "565c75323030625c75323032385c75333030300a70300a2e", + "1": "5809000000e2808be280a8e3808071002e", + "2": "80025809000000e2808be280a8e3808071002e", + "3": "80035809000000e2808be280a8e3808071002e", + "4": "8004950d000000000000008c09e2808be280a8e38080942e", + "5": "8005950d000000000000008c09e2808be280a8e38080942e" + }, + "view": "'\\u200b\\u2028\\u3000'" + }, + { + "name": "'\\U0001f600'", + "source": "'\\U0001f600'", + "literal": true, + "plain": true, + "repr": "'\ud83d\ude00'", + "str": "\ud83d\ude00", + "truthy": true, + "json": "\"\\ud83d\\ude00\"", + "pickle": { + "0": "565c5530303031663630300a70300a2e", + "1": "5804000000f09f988071002e", + "2": "80025804000000f09f988071002e", + "3": "80035804000000f09f988071002e", + "4": "80049508000000000000008c04f09f9880942e", + "5": "80059508000000000000008c04f09f9880942e" + }, + "view": "'\ud83d\ude00'" + }, + { + "name": "'\\U000e0001\\U0010ffff'", + "source": "'\\U000e0001\\U0010ffff'", + "literal": true, + "plain": true, + "repr": "'\\U000e0001\\U0010ffff'", + "str": "\udb40\udc01\udbff\udfff", + "truthy": true, + "json": "\"\\udb40\\udc01\\udbff\\udfff\"", + "pickle": { + "0": "565c5530303065303030315c5530303130666666660a70300a2e", + "1": "5808000000f3a08081f48fbfbf71002e", + "2": "80025808000000f3a08081f48fbfbf71002e", + "3": "80035808000000f3a08081f48fbfbf71002e", + "4": "8004950c000000000000008c08f3a08081f48fbfbf942e", + "5": "8005950c000000000000008c08f3a08081f48fbfbf942e" + }, + "view": "'\\U000e0001\\U0010ffff'" + }, + { + "name": "'\\b\\f'", + "source": "'\\b\\f'", + "literal": true, + "plain": true, + "repr": "'\\x08\\x0c'", + "str": "\b\f", + "truthy": true, + "json": "\"\\b\\f\"", + "pickle": { + "0": "56080c0a70300a2e", + "1": "5802000000080c71002e", + "2": "80025802000000080c71002e", + "3": "80035802000000080c71002e", + "4": "80049506000000000000008c02080c942e", + "5": "80059506000000000000008c02080c942e" + }, + "view": "'\\x08\\x0c'" + }, + { + "name": "b''", + "source": "b''", + "literal": true, + "plain": true, + "repr": "b''", + "str": "b''", + "truthy": false, + "json_error": "Object of type bytes is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a62797465730a70300a28745270310a2e", + "1": "635f5f6275696c74696e5f5f0a62797465730a7100295271012e", + "2": "8002635f5f6275696c74696e5f5f0a62797465730a7100295271012e", + "3": "8003430071002e", + "4": "80049504000000000000004300942e", + "5": "80059504000000000000004300942e" + }, + "view": "b''" + }, + { + "name": "b'abc'", + "source": "b'abc'", + "literal": true, + "plain": true, + "repr": "b'abc'", + "str": "b'abc'", + "truthy": true, + "json_error": "Object of type bytes is not JSON serializable", + "pickle": { + "0": "635f636f646563730a656e636f64650a70300a28566162630a70310a566c6174696e310a70320a7470330a5270340a2e", + "1": "635f636f646563730a656e636f64650a7100285803000000616263710158060000006c6174696e3171027471035271042e", + "2": "8002635f636f646563730a656e636f64650a71005803000000616263710158060000006c6174696e3171028671035271042e", + "3": "8003430361626371002e", + "4": "80049507000000000000004303616263942e", + "5": "80059507000000000000004303616263942e" + }, + "view": "b'abc'" + }, + { + "name": "b\"a'b\"", + "source": "b\"a'b\"", + "literal": true, + "plain": true, + "repr": "b\"a'b\"", + "str": "b\"a'b\"", + "truthy": true, + "json_error": "Object of type bytes is not JSON serializable", + "pickle": { + "0": "635f636f646563730a656e636f64650a70300a28566127620a70310a566c6174696e310a70320a7470330a5270340a2e", + "1": "635f636f646563730a656e636f64650a7100285803000000612762710158060000006c6174696e3171027471035271042e", + "2": "8002635f636f646563730a656e636f64650a71005803000000612762710158060000006c6174696e3171028671035271042e", + "3": "8003430361276271002e", + "4": "80049507000000000000004303612762942e", + "5": "80059507000000000000004303612762942e" + }, + "view": "b\"a'b\"" + }, + { + "name": "b'a\"b\\'c'", + "source": "b'a\"b\\'c'", + "literal": true, + "plain": true, + "repr": "b'a\"b\\'c'", + "str": "b'a\"b\\'c'", + "truthy": true, + "json_error": "Object of type bytes is not JSON serializable", + "pickle": { + "0": "635f636f646563730a656e636f64650a70300a285661226227630a70310a566c6174696e310a70320a7470330a5270340a2e", + "1": "635f636f646563730a656e636f64650a71002858050000006122622763710158060000006c6174696e3171027471035271042e", + "2": "8002635f636f646563730a656e636f64650a710058050000006122622763710158060000006c6174696e3171028671035271042e", + "3": "80034305612262276371002e", + "4": "800495090000000000000043056122622763942e", + "5": "800595090000000000000043056122622763942e" + }, + "view": "b'a\"b\\'c'" + }, + { + "name": "b'\\x00\\t\\n\\r\\x7f\\x80\\xff'", + "source": "b'\\x00\\t\\n\\r\\x7f\\x80\\xff'", + "literal": true, + "plain": true, + "repr": "b'\\x00\\t\\n\\r\\x7f\\x80\\xff'", + "str": "b'\\x00\\t\\n\\r\\x7f\\x80\\xff'", + "truthy": true, + "json_error": "Object of type bytes is not JSON serializable", + "pickle": { + "0": "635f636f646563730a656e636f64650a70300a28565c7530303030095c75303030615c75303030647f80ff0a70310a566c6174696e310a70320a7470330a5270340a2e", + "1": "635f636f646563730a656e636f64650a710028580900000000090a0d7fc280c3bf710158060000006c6174696e3171027471035271042e", + "2": "8002635f636f646563730a656e636f64650a7100580900000000090a0d7fc280c3bf710158060000006c6174696e3171028671035271042e", + "3": "8003430700090a0d7f80ff71002e", + "4": "8004950b00000000000000430700090a0d7f80ff942e", + "5": "8005950b00000000000000430700090a0d7f80ff942e" + }, + "view": "b'\\x00\\t\\n\\r\\x7f\\x80\\xff'" + }, + { + "name": "[]", + "source": "[]", + "literal": true, + "plain": true, + "repr": "[]", + "str": "[]", + "truthy": false, + "json": "[]", + "pickle": { + "0": "286c70300a2e", + "1": "5d71002e", + "2": "80025d71002e", + "3": "80035d71002e", + "4": "80045d942e", + "5": "80055d942e" + }, + "view": "[]" + }, + { + "name": "[1, 'a', None, True]", + "source": "[1, 'a', None, True]", + "literal": true, + "plain": true, + "repr": "[1, 'a', None, True]", + "str": "[1, 'a', None, True]", + "truthy": true, + "json": "[1, \"a\", null, true]", + "pickle": { + "0": "286c70300a49310a6156610a70310a614e614930310a612e", + "1": "5d7100284b0158010000006171014e4930310a652e", + "2": "80025d7100284b0158010000006171014e88652e", + "3": "80035d7100284b0158010000006171014e88652e", + "4": "8004950d000000000000005d94284b018c0161944e88652e", + "5": "8005950d000000000000005d94284b018c0161944e88652e" + }, + "view": "[1, 'a', None, True]" + }, + { + "name": "()", + "source": "()", + "literal": true, + "plain": true, + "repr": "()", + "str": "()", + "truthy": false, + "json": "[]", + "pickle": { + "0": "28742e", + "1": "292e", + "2": "8002292e", + "3": "8003292e", + "4": "8004292e", + "5": "8005292e" + }, + "view": "[]" + }, + { + "name": "(1,)", + "source": "(1,)", + "literal": true, + "plain": true, + "repr": "(1,)", + "str": "(1,)", + "truthy": true, + "json": "[1]", + "pickle": { + "0": "2849310a7470300a2e", + "1": "284b017471002e", + "2": "80024b018571002e", + "3": "80034b018571002e", + "4": "80049505000000000000004b0185942e", + "5": "80059505000000000000004b0185942e" + }, + "view": "[1]" + }, + { + "name": "(1, (2, 3))", + "source": "(1, (2, 3))", + "literal": true, + "plain": true, + "repr": "(1, (2, 3))", + "str": "(1, (2, 3))", + "truthy": true, + "json": "[1, [2, 3]]", + "pickle": { + "0": "2849310a2849320a49330a7470300a7470310a2e", + "1": "284b01284b024b037471007471012e", + "2": "80024b014b024b038671008671012e", + "3": "80034b014b024b038671008671012e", + "4": "8004950b000000000000004b014b024b03869486942e", + "5": "8005950b000000000000004b014b024b03869486942e" + }, + "view": "[1, [2, 3]]" + }, + { + "name": "{}", + "source": "{}", + "literal": true, + "plain": true, + "repr": "{}", + "str": "{}", + "truthy": false, + "json": "{}", + "pickle": { + "0": "286470300a2e", + "1": "7d71002e", + "2": "80027d71002e", + "3": "80037d71002e", + "4": "80047d942e", + "5": "80057d942e" + }, + "view": "{}" + }, + { + "name": "{'a': 1, 'b': [1.0, 2.5]}", + "source": "{'a': 1, 'b': [1.0, 2.5]}", + "literal": true, + "plain": true, + "repr": "{'a': 1, 'b': [1.0, 2.5]}", + "str": "{'a': 1, 'b': [1.0, 2.5]}", + "truthy": true, + "json": "{\"a\": 1, \"b\": [1.0, 2.5]}", + "pickle": { + "0": "286470300a56610a70310a49310a7356620a70320a286c70330a46312e300a6146322e350a61732e", + "1": "7d71002858010000006171014b0158010000006271025d710328473ff000000000000047400400000000000065752e", + "2": "80027d71002858010000006171014b0158010000006271025d710328473ff000000000000047400400000000000065752e", + "3": "80037d71002858010000006171014b0158010000006271025d710328473ff000000000000047400400000000000065752e", + "4": "80049525000000000000007d94288c0161944b018c0162945d9428473ff000000000000047400400000000000065752e", + "5": "80059525000000000000007d94288c0161944b018c0162945d9428473ff000000000000047400400000000000065752e" + }, + "view": "{'a': 1, 'b': [1.0, 2.5]}" + }, + { + "name": "{'z': 1, 'a': 2, 'm': 3}", + "source": "{'z': 1, 'a': 2, 'm': 3}", + "literal": true, + "plain": true, + "repr": "{'z': 1, 'a': 2, 'm': 3}", + "str": "{'z': 1, 'a': 2, 'm': 3}", + "truthy": true, + "json": "{\"z\": 1, \"a\": 2, \"m\": 3}", + "pickle": { + "0": "286470300a567a0a70310a49310a7356610a70320a49320a73566d0a70330a49330a732e", + "1": "7d71002858010000007a71014b0158010000006171024b0258010000006d71034b03752e", + "2": "80027d71002858010000007a71014b0158010000006171024b0258010000006d71034b03752e", + "3": "80037d71002858010000007a71014b0158010000006171024b0258010000006d71034b03752e", + "4": "80049517000000000000007d94288c017a944b018c0161944b028c016d944b03752e", + "5": "80059517000000000000007d94288c017a944b018c0161944b028c016d944b03752e" + }, + "view": "{'z': 1, 'a': 2, 'm': 3}" + }, + { + "name": "{1: 'int', 2.5: 'float', True: 'bool', None: 'none'}", + "source": "{1: 'int', 2.5: 'float', True: 'bool', None: 'none'}", + "literal": true, + "plain": true, + "repr": "{1: 'bool', 2.5: 'float', None: 'none'}", + "str": "{1: 'bool', 2.5: 'float', None: 'none'}", + "truthy": true, + "json": "{\"1\": \"bool\", \"2.5\": \"float\", \"null\": \"none\"}", + "pickle": { + "0": "286470300a49310a56626f6f6c0a70310a7346322e350a56666c6f61740a70320a734e566e6f6e650a70330a732e", + "1": "7d7100284b015804000000626f6f6c71014740040000000000005805000000666c6f617471024e58040000006e6f6e657103752e", + "2": "80027d7100284b015804000000626f6f6c71014740040000000000005805000000666c6f617471024e58040000006e6f6e657103752e", + "3": "80037d7100284b015804000000626f6f6c71014740040000000000005805000000666c6f617471024e58040000006e6f6e657103752e", + "4": "80049527000000000000007d94284b018c04626f6f6c944740040000000000008c05666c6f6174944e8c046e6f6e6594752e", + "5": "80059527000000000000007d94284b018c04626f6f6c944740040000000000008c05666c6f6174944e8c046e6f6e6594752e" + }, + "view": "{1: 'bool', 2.5: 'float', None: 'none'}" + }, + { + "name": "{(1, 2): 'tuple key'}", + "source": "{(1, 2): 'tuple key'}", + "literal": true, + "plain": true, + "repr": "{(1, 2): 'tuple key'}", + "str": "{(1, 2): 'tuple key'}", + "truthy": true, + "json_error": "keys must be str, int, float, bool or None, not tuple", + "pickle": { + "0": "286470300a2849310a49320a7470310a567475706c65206b65790a70320a732e", + "1": "7d7100284b014b0274710158090000007475706c65206b65797102732e", + "2": "80027d71004b014b0286710158090000007475706c65206b65797102732e", + "3": "80037d71004b014b0286710158090000007475706c65206b65797102732e", + "4": "80049516000000000000007d944b014b0286948c097475706c65206b657994732e", + "5": "80059516000000000000007d944b014b0286948c097475706c65206b657994732e" + }, + "view": "{[1, 2]: 'tuple key'}" + }, + { + "name": "{'nested': {'deeper': {'deepest': [{}]}}}", + "source": "{'nested': {'deeper': {'deepest': [{}]}}}", + "literal": true, + "plain": true, + "repr": "{'nested': {'deeper': {'deepest': [{}]}}}", + "str": "{'nested': {'deeper': {'deepest': [{}]}}}", + "truthy": true, + "json": "{\"nested\": {\"deeper\": {\"deepest\": [{}]}}}", + "pickle": { + "0": "286470300a566e65737465640a70310a286470320a566465657065720a70330a286470340a56646565706573740a70350a286c70360a286470370a617373732e", + "1": "7d710058060000006e657374656471017d7102580600000064656570657271037d710458070000006465657065737471055d71067d7107617373732e", + "2": "80027d710058060000006e657374656471017d7102580600000064656570657271037d710458070000006465657065737471055d71067d7107617373732e", + "3": "80037d710058060000006e657374656471017d7102580600000064656570657271037d710458070000006465657065737471055d71067d7107617373732e", + "4": "8004952b000000000000007d948c066e6573746564947d948c06646565706572947d948c0764656570657374945d947d94617373732e", + "5": "8005952b000000000000007d948c066e6573746564947d948c06646565706572947d948c0764656570657374945d947d94617373732e" + }, + "view": "{'nested': {'deeper': {'deepest': [{}]}}}" + }, + { + "name": "{1}", + "source": "{1}", + "literal": true, + "plain": true, + "repr": "{1}", + "str": "{1}", + "truthy": true, + "json_error": "Object of type set is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a7365740a70300a28286c70310a49310a617470320a5270330a2e", + "1": "635f5f6275696c74696e5f5f0a7365740a7100285d71014b01617471025271032e", + "2": "8002635f5f6275696c74696e5f5f0a7365740a71005d71014b01618571025271032e", + "3": "8003636275696c74696e730a7365740a71005d71014b01618571025271032e", + "4": "80049507000000000000008f94284b01902e", + "5": "80059507000000000000008f94284b01902e" + }, + "view": "[1]" + }, + { + "name": "set()", + "source": "set()", + "literal": true, + "plain": true, + "repr": "set()", + "str": "set()", + "truthy": false, + "json_error": "Object of type set is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a7365740a70300a28286c70310a7470320a5270330a2e", + "1": "635f5f6275696c74696e5f5f0a7365740a7100285d71017471025271032e", + "2": "8002635f5f6275696c74696e5f5f0a7365740a71005d71018571025271032e", + "3": "8003636275696c74696e730a7365740a71005d71018571025271032e", + "4": "80048f942e", + "5": "80058f942e" + }, + "view": "[]" + }, + { + "name": "frozenset({1})", + "source": "frozenset({1})", + "literal": false, + "plain": true, + "repr": "frozenset({1})", + "str": "frozenset({1})", + "truthy": true, + "json_error": "Object of type frozenset is not JSON serializable", + "pickle": { + "0": "635f5f6275696c74696e5f5f0a66726f7a656e7365740a70300a28286c70310a49310a617470320a5270330a2e", + "1": "635f5f6275696c74696e5f5f0a66726f7a656e7365740a7100285d71014b01617471025271032e", + "2": "8002635f5f6275696c74696e5f5f0a66726f7a656e7365740a71005d71014b01618571025271032e", + "3": "8003636275696c74696e730a66726f7a656e7365740a71005d71014b01618571025271032e", + "4": "8004950600000000000000284b0191942e", + "5": "8005950600000000000000284b0191942e" + }, + "view": "[1]" + }, + { + "name": "[[[[[[[[[[1]]]]]]]]]]", + "source": "[[[[[[[[[[1]]]]]]]]]]", + "literal": true, + "plain": true, + "repr": "[[[[[[[[[[1]]]]]]]]]]", + "str": "[[[[[[[[[[1]]]]]]]]]]", + "truthy": true, + "json": "[[[[[[[[[[1]]]]]]]]]]", + "pickle": { + "0": "286c70300a286c70310a286c70320a286c70330a286c70340a286c70350a286c70360a286c70370a286c70380a286c70390a49310a616161616161616161612e", + "1": "5d71005d71015d71025d71035d71045d71055d71065d71075d71085d71094b01616161616161616161612e", + "2": "80025d71005d71015d71025d71035d71045d71055d71065d71075d71085d71094b01616161616161616161612e", + "3": "80035d71005d71015d71025d71035d71045d71055d71065d71075d71085d71094b01616161616161616161612e", + "4": "80049521000000000000005d945d945d945d945d945d945d945d945d945d944b01616161616161616161612e", + "5": "80059521000000000000005d945d945d945d945d945d945d945d945d945d944b01616161616161616161612e" + }, + "view": "[[[[[[[[[[1]]]]]]]]]]" + }, + { + "name": "nested_150", + "source": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", + "literal": true, + "plain": true, + "repr": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", + "str": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", + "truthy": true, + "json": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", + "pickle": { + "0": "286c70300a286c70310a286c70320a286c70330a286c70340a286c70350a286c70360a286c70370a286c70380a286c70390a286c7031300a286c7031310a286c7031320a286c7031330a286c7031340a286c7031350a286c7031360a286c7031370a286c7031380a286c7031390a286c7032300a286c7032310a286c7032320a286c7032330a286c7032340a286c7032350a286c7032360a286c7032370a286c7032380a286c7032390a286c7033300a286c7033310a286c7033320a286c7033330a286c7033340a286c7033350a286c7033360a286c7033370a286c7033380a286c7033390a286c7034300a286c7034310a286c7034320a286c7034330a286c7034340a286c7034350a286c7034360a286c7034370a286c7034380a286c7034390a286c7035300a286c7035310a286c7035320a286c7035330a286c7035340a286c7035350a286c7035360a286c7035370a286c7035380a286c7035390a286c7036300a286c7036310a286c7036320a286c7036330a286c7036340a286c7036350a286c7036360a286c7036370a286c7036380a286c7036390a286c7037300a286c7037310a286c7037320a286c7037330a286c7037340a286c7037350a286c7037360a286c7037370a286c7037380a286c7037390a286c7038300a286c7038310a286c7038320a286c7038330a286c7038340a286c7038350a286c7038360a286c7038370a286c7038380a286c7038390a286c7039300a286c7039310a286c7039320a286c7039330a286c7039340a286c7039350a286c7039360a286c7039370a286c7039380a286c7039390a286c703130300a286c703130310a286c703130320a286c703130330a286c703130340a286c703130350a286c703130360a286c703130370a286c703130380a286c703130390a286c703131300a286c703131310a286c703131320a286c703131330a286c703131340a286c703131350a286c703131360a286c703131370a286c703131380a286c703131390a286c703132300a286c703132310a286c703132320a286c703132330a286c703132340a286c703132350a286c703132360a286c703132370a286c703132380a286c703132390a286c703133300a286c703133310a286c703133320a286c703133330a286c703133340a286c703133350a286c703133360a286c703133370a286c703133380a286c703133390a286c703134300a286c703134310a286c703134320a286c703134330a286c703134340a286c703134350a286c703134360a286c703134370a286c703134380a286c703134390a49310a6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e", + "1": "5d71005d71015d71025d71035d71045d71055d71065d71075d71085d71095d710a5d710b5d710c5d710d5d710e5d710f5d71105d71115d71125d71135d71145d71155d71165d71175d71185d71195d711a5d711b5d711c5d711d5d711e5d711f5d71205d71215d71225d71235d71245d71255d71265d71275d71285d71295d712a5d712b5d712c5d712d5d712e5d712f5d71305d71315d71325d71335d71345d71355d71365d71375d71385d71395d713a5d713b5d713c5d713d5d713e5d713f5d71405d71415d71425d71435d71445d71455d71465d71475d71485d71495d714a5d714b5d714c5d714d5d714e5d714f5d71505d71515d71525d71535d71545d71555d71565d71575d71585d71595d715a5d715b5d715c5d715d5d715e5d715f5d71605d71615d71625d71635d71645d71655d71665d71675d71685d71695d716a5d716b5d716c5d716d5d716e5d716f5d71705d71715d71725d71735d71745d71755d71765d71775d71785d71795d717a5d717b5d717c5d717d5d717e5d717f5d71805d71815d71825d71835d71845d71855d71865d71875d71885d71895d718a5d718b5d718c5d718d5d718e5d718f5d71905d71915d71925d71935d71945d71954b016161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e", + "2": "80025d71005d71015d71025d71035d71045d71055d71065d71075d71085d71095d710a5d710b5d710c5d710d5d710e5d710f5d71105d71115d71125d71135d71145d71155d71165d71175d71185d71195d711a5d711b5d711c5d711d5d711e5d711f5d71205d71215d71225d71235d71245d71255d71265d71275d71285d71295d712a5d712b5d712c5d712d5d712e5d712f5d71305d71315d71325d71335d71345d71355d71365d71375d71385d71395d713a5d713b5d713c5d713d5d713e5d713f5d71405d71415d71425d71435d71445d71455d71465d71475d71485d71495d714a5d714b5d714c5d714d5d714e5d714f5d71505d71515d71525d71535d71545d71555d71565d71575d71585d71595d715a5d715b5d715c5d715d5d715e5d715f5d71605d71615d71625d71635d71645d71655d71665d71675d71685d71695d716a5d716b5d716c5d716d5d716e5d716f5d71705d71715d71725d71735d71745d71755d71765d71775d71785d71795d717a5d717b5d717c5d717d5d717e5d717f5d71805d71815d71825d71835d71845d71855d71865d71875d71885d71895d718a5d718b5d718c5d718d5d718e5d718f5d71905d71915d71925d71935d71945d71954b016161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e", + "3": "80035d71005d71015d71025d71035d71045d71055d71065d71075d71085d71095d710a5d710b5d710c5d710d5d710e5d710f5d71105d71115d71125d71135d71145d71155d71165d71175d71185d71195d711a5d711b5d711c5d711d5d711e5d711f5d71205d71215d71225d71235d71245d71255d71265d71275d71285d71295d712a5d712b5d712c5d712d5d712e5d712f5d71305d71315d71325d71335d71345d71355d71365d71375d71385d71395d713a5d713b5d713c5d713d5d713e5d713f5d71405d71415d71425d71435d71445d71455d71465d71475d71485d71495d714a5d714b5d714c5d714d5d714e5d714f5d71505d71515d71525d71535d71545d71555d71565d71575d71585d71595d715a5d715b5d715c5d715d5d715e5d715f5d71605d71615d71625d71635d71645d71655d71665d71675d71685d71695d716a5d716b5d716c5d716d5d716e5d716f5d71705d71715d71725d71735d71745d71755d71765d71775d71785d71795d717a5d717b5d717c5d717d5d717e5d717f5d71805d71815d71825d71835d71845d71855d71865d71875d71885d71895d718a5d718b5d718c5d718d5d718e5d718f5d71905d71915d71925d71935d71945d71954b016161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e", + "4": "800495c5010000000000005d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d944b016161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e", + "5": "800595c5010000000000005d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d945d944b016161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e" + }, + "view": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + }, + { + "name": "{'timestamp': 1726000000.123, 'response': '{\"id\": \"chatcmpl-1\", \"object\": \"chat.completion\"}'}", + "source": "{'timestamp': 1726000000.123, 'response': '{\"id\": \"chatcmpl-1\", \"object\": \"chat.completion\"}'}", + "literal": true, + "plain": true, + "repr": "{'timestamp': 1726000000.123, 'response': '{\"id\": \"chatcmpl-1\", \"object\": \"chat.completion\"}'}", + "str": "{'timestamp': 1726000000.123, 'response': '{\"id\": \"chatcmpl-1\", \"object\": \"chat.completion\"}'}", + "truthy": true, + "json": "{\"timestamp\": 1726000000.123, \"response\": \"{\\\"id\\\": \\\"chatcmpl-1\\\", \\\"object\\\": \\\"chat.completion\\\"}\"}", + "pickle": { + "0": "286470300a5674696d657374616d700a70310a46313732363030303030302e3132330a7356726573706f6e73650a70320a567b226964223a202263686174636d706c2d31222c20226f626a656374223a2022636861742e636f6d706c6574696f6e227d0a70330a732e", + "1": "7d710028580900000074696d657374616d7071014741d9b82ae007df3b5808000000726573706f6e7365710258310000007b226964223a202263686174636d706c2d31222c20226f626a656374223a2022636861742e636f6d706c6574696f6e227d7103752e", + "2": "80027d710028580900000074696d657374616d7071014741d9b82ae007df3b5808000000726573706f6e7365710258310000007b226964223a202263686174636d706c2d31222c20226f626a656374223a2022636861742e636f6d706c6574696f6e227d7103752e", + "3": "80037d710028580900000074696d657374616d7071014741d9b82ae007df3b5808000000726573706f6e7365710258310000007b226964223a202263686174636d706c2d31222c20226f626a656374223a2022636861742e636f6d706c6574696f6e227d7103752e", + "4": "80049559000000000000007d94288c0974696d657374616d70944741d9b82ae007df3b8c08726573706f6e7365948c317b226964223a202263686174636d706c2d31222c20226f626a656374223a2022636861742e636f6d706c6574696f6e227d94752e", + "5": "80059559000000000000007d94288c0974696d657374616d70944741d9b82ae007df3b8c08726573706f6e7365948c317b226964223a202263686174636d706c2d31222c20226f626a656374223a2022636861742e636f6d706c6574696f6e227d94752e" + }, + "view": "{'timestamp': 1726000000.123, 'response': '{\"id\": \"chatcmpl-1\", \"object\": \"chat.completion\"}'}" + }, + { + "name": "{'model': 'gpt-4o', 'messages': [{'role': 'user', 'content': 'hi'}], 'temperature': 0.2, 'stream': False}", + "source": "{'model': 'gpt-4o', 'messages': [{'role': 'user', 'content': 'hi'}], 'temperature': 0.2, 'stream': False}", + "literal": true, + "plain": true, + "repr": "{'model': 'gpt-4o', 'messages': [{'role': 'user', 'content': 'hi'}], 'temperature': 0.2, 'stream': False}", + "str": "{'model': 'gpt-4o', 'messages': [{'role': 'user', 'content': 'hi'}], 'temperature': 0.2, 'stream': False}", + "truthy": true, + "json": "{\"model\": \"gpt-4o\", \"messages\": [{\"role\": \"user\", \"content\": \"hi\"}], \"temperature\": 0.2, \"stream\": false}", + "pickle": { + "0": "286470300a566d6f64656c0a70310a566770742d346f0a70320a73566d657373616765730a70330a286c70340a286470350a56726f6c650a70360a56757365720a70370a7356636f6e74656e740a70380a5668690a70390a7361735674656d70657261747572650a7031300a46302e320a735673747265616d0a7031310a4930300a732e", + "1": "7d71002858050000006d6f64656c710158060000006770742d346f710258080000006d6573736167657371035d71047d7105285804000000726f6c65710658040000007573657271075807000000636f6e74656e7471085802000000686971097561580b00000074656d7065726174757265710a473fc999999999999a580600000073747265616d710b4930300a752e", + "2": "80027d71002858050000006d6f64656c710158060000006770742d346f710258080000006d6573736167657371035d71047d7105285804000000726f6c65710658040000007573657271075807000000636f6e74656e7471085802000000686971097561580b00000074656d7065726174757265710a473fc999999999999a580600000073747265616d710b89752e", + "3": "80037d71002858050000006d6f64656c710158060000006770742d346f710258080000006d6573736167657371035d71047d7105285804000000726f6c65710658040000007573657271075807000000636f6e74656e7471085802000000686971097561580b00000074656d7065726174757265710a473fc999999999999a580600000073747265616d710b89752e", + "4": "80049566000000000000007d94288c056d6f64656c948c066770742d346f948c086d65737361676573945d947d94288c04726f6c65948c0475736572948c07636f6e74656e74948c0268699475618c0b74656d706572617475726594473fc999999999999a8c0673747265616d9489752e", + "5": "80059566000000000000007d94288c056d6f64656c948c066770742d346f948c086d65737361676573945d947d94288c04726f6c65948c0475736572948c07636f6e74656e74948c0268699475618c0b74656d706572617475726594473fc999999999999a8c0673747265616d9489752e" + }, + "view": "{'model': 'gpt-4o', 'messages': [{'role': 'user', 'content': 'hi'}], 'temperature': 0.2, 'stream': False}" + } + ], + "sources": [ + { + "name": "1", + "source": "1", + "repr": "1" + }, + { + "name": " 1", + "source": " 1", + "repr": "1" + }, + { + "name": "\t1", + "source": "\t1", + "repr": "1" + }, + { + "name": "\n1", + "source": "\n1", + "repr": "1" + }, + { + "name": " \n 1", + "source": " \n 1", + "error": "IndentationError" + }, + { + "name": "1\n", + "source": "1\n", + "repr": "1" + }, + { + "name": "1 # comment", + "source": "1 # comment", + "repr": "1" + }, + { + "name": "# c\n1", + "source": "# c\n1", + "repr": "1" + }, + { + "name": "1 \\\n", + "source": "1 \\\n", + "error": "SyntaxError" + }, + { + "name": "1,", + "source": "1,", + "repr": "(1,)" + }, + { + "name": "1, 2", + "source": "1, 2", + "repr": "(1, 2)" + }, + { + "name": "1,\n2", + "source": "1,\n2", + "error": "SyntaxError" + }, + { + "name": "(1,\n2)", + "source": "(1,\n2)", + "repr": "(1, 2)" + }, + { + "name": "[1,\n 2,\n]", + "source": "[1,\n 2,\n]", + "repr": "[1, 2]" + }, + { + "name": "()", + "source": "()", + "repr": "()" + }, + { + "name": "(1)", + "source": "(1)", + "repr": "1" + }, + { + "name": "((1,))", + "source": "((1,))", + "repr": "(1,)" + }, + { + "name": "(,)", + "source": "(,)", + "error": "SyntaxError" + }, + { + "name": "[,]", + "source": "[,]", + "error": "SyntaxError" + }, + { + "name": "{,}", + "source": "{,}", + "error": "SyntaxError" + }, + { + "name": "[1,]", + "source": "[1,]", + "repr": "[1]" + }, + { + "name": "{'a': 1,}", + "source": "{'a': 1,}", + "repr": "{'a': 1}" + }, + { + "name": "{1,}", + "source": "{1,}", + "repr": "{1}" + }, + { + "name": "{'a': 1 'b': 2}", + "source": "{'a': 1 'b': 2}", + "error": "SyntaxError" + }, + { + "name": "{1: 'a', True: 'b'}", + "source": "{1: 'a', True: 'b'}", + "repr": "{1: 'b'}" + }, + { + "name": "{1, True, 1.0}", + "source": "{1, True, 1.0}", + "repr": "{1}" + }, + { + "name": "{(1, 2): 'x', (1.0, 2): 'y'}", + "source": "{(1, 2): 'x', (1.0, 2): 'y'}", + "repr": "{(1, 2): 'y'}" + }, + { + "name": "{[1]: 2}", + "source": "{[1]: 2}", + "error": "TypeError" + }, + { + "name": "{{1}}", + "source": "{{1}}", + "error": "TypeError" + }, + { + "name": "{(1, [2])}", + "source": "{(1, [2])}", + "error": "TypeError" + }, + { + "name": "set()", + "source": "set()", + "repr": "set()" + }, + { + "name": "set( )", + "source": "set( )", + "repr": "set()" + }, + { + "name": "set([1])", + "source": "set([1])", + "error": "ValueError" + }, + { + "name": "frozenset()", + "source": "frozenset()", + "error": "ValueError" + }, + { + "name": "True", + "source": "True", + "repr": "True" + }, + { + "name": "False", + "source": "False", + "repr": "False" + }, + { + "name": "None", + "source": "None", + "repr": "None" + }, + { + "name": "Truex", + "source": "Truex", + "error": "ValueError" + }, + { + "name": "true", + "source": "true", + "error": "ValueError" + }, + { + "name": "...", + "source": "...", + "repr": "Ellipsis" + }, + { + "name": "0", + "source": "0", + "repr": "0" + }, + { + "name": "00", + "source": "00", + "repr": "0" + }, + { + "name": "0_0", + "source": "0_0", + "repr": "0" + }, + { + "name": "01", + "source": "01", + "error": "SyntaxError" + }, + { + "name": "007", + "source": "007", + "error": "SyntaxError" + }, + { + "name": "1_000", + "source": "1_000", + "repr": "1000" + }, + { + "name": "1_", + "source": "1_", + "error": "SyntaxError" + }, + { + "name": "1__0", + "source": "1__0", + "error": "SyntaxError" + }, + { + "name": "_1", + "source": "_1", + "error": "ValueError" + }, + { + "name": "0x1F", + "source": "0x1F", + "repr": "31" + }, + { + "name": "0X_1f", + "source": "0X_1f", + "repr": "31" + }, + { + "name": "0o17", + "source": "0o17", + "repr": "15" + }, + { + "name": "0b101", + "source": "0b101", + "repr": "5" + }, + { + "name": "0b102", + "source": "0b102", + "error": "SyntaxError" + }, + { + "name": "0x", + "source": "0x", + "error": "SyntaxError" + }, + { + "name": "1e3", + "source": "1e3", + "repr": "1000.0" + }, + { + "name": "1E-3", + "source": "1E-3", + "repr": "0.001" + }, + { + "name": "1e", + "source": "1e", + "error": "SyntaxError" + }, + { + "name": "1.e5", + "source": "1.e5", + "repr": "100000.0" + }, + { + "name": ".5", + "source": ".5", + "repr": "0.5" + }, + { + "name": "5.", + "source": "5.", + "repr": "5.0" + }, + { + "name": "1..", + "source": "1..", + "error": "SyntaxError" + }, + { + "name": "1.5.2", + "source": "1.5.2", + "error": "SyntaxError" + }, + { + "name": "1_0.0_1e1_0", + "source": "1_0.0_1e1_0", + "repr": "100100000000.0" + }, + { + "name": "1e999", + "source": "1e999", + "repr": "inf" + }, + { + "name": "-1e999", + "source": "-1e999", + "repr": "-inf" + }, + { + "name": "1j", + "source": "1j", + "repr": "1j" + }, + { + "name": "1.5J", + "source": "1.5J", + "repr": "1.5j" + }, + { + "name": "010j", + "source": "010j", + "repr": "10j" + }, + { + "name": "010.5", + "source": "010.5", + "repr": "10.5" + }, + { + "name": "1a", + "source": "1a", + "error": "SyntaxError" + }, + { + "name": "0x1g", + "source": "0x1g", + "error": "SyntaxError" + }, + { + "name": "-1", + "source": "-1", + "repr": "-1" + }, + { + "name": "+1", + "source": "+1", + "repr": "1" + }, + { + "name": "- 1", + "source": "- 1", + "repr": "-1" + }, + { + "name": "--1", + "source": "--1", + "error": "ValueError" + }, + { + "name": "-+1", + "source": "-+1", + "error": "ValueError" + }, + { + "name": "-(1)", + "source": "-(1)", + "repr": "-1" + }, + { + "name": "-(-1)", + "source": "-(-1)", + "error": "ValueError" + }, + { + "name": "-(1+2j)", + "source": "-(1+2j)", + "error": "ValueError" + }, + { + "name": "-True", + "source": "-True", + "error": "ValueError" + }, + { + "name": "-'a'", + "source": "-'a'", + "error": "ValueError" + }, + { + "name": "-[1]", + "source": "-[1]", + "error": "ValueError" + }, + { + "name": "1+2j", + "source": "1+2j", + "repr": "(1+2j)" + }, + { + "name": "1-2j", + "source": "1-2j", + "repr": "(1-2j)" + }, + { + "name": "1 + 2j", + "source": "1 + 2j", + "repr": "(1+2j)" + }, + { + "name": "(1)+(2j)", + "source": "(1)+(2j)", + "repr": "(1+2j)" + }, + { + "name": "1+2", + "source": "1+2", + "error": "ValueError" + }, + { + "name": "1+2+3j", + "source": "1+2+3j", + "error": "ValueError" + }, + { + "name": "1+-2j", + "source": "1+-2j", + "error": "ValueError" + }, + { + "name": "2j+1", + "source": "2j+1", + "error": "ValueError" + }, + { + "name": "True+1j", + "source": "True+1j", + "error": "ValueError" + }, + { + "name": "1-0j", + "source": "1-0j", + "repr": "(1-0j)" + }, + { + "name": "0.0-0j", + "source": "0.0-0j", + "repr": "-0j" + }, + { + "name": "-0.0+1j", + "source": "-0.0+1j", + "repr": "1j" + }, + { + "name": "-0.0", + "source": "-0.0", + "repr": "-0.0" + }, + { + "name": "-0", + "source": "-0", + "repr": "0" + }, + { + "name": "(-0.0)", + "source": "(-0.0)", + "repr": "-0.0" + }, + { + "name": "[-0.0, (-0.0)]", + "source": "[-0.0, (-0.0)]", + "repr": "[-0.0, -0.0]" + }, + { + "name": "2**3", + "source": "2**3", + "error": "ValueError" + }, + { + "name": "1*2", + "source": "1*2", + "error": "ValueError" + }, + { + "name": "1 if 1 else 2", + "source": "1 if 1 else 2", + "error": "ValueError" + }, + { + "name": "(1,)(2)", + "source": "(1,)(2)", + "error": "ValueError" + }, + { + "name": "''", + "source": "''", + "repr": "''" + }, + { + "name": "\"\"", + "source": "\"\"", + "repr": "''" + }, + { + "name": "'a' 'b'", + "source": "'a' 'b'", + "repr": "'ab'" + }, + { + "name": "'a' \"b\" '''c'''", + "source": "'a' \"b\" '''c'''", + "repr": "'abc'" + }, + { + "name": "'a' b'b'", + "source": "'a' b'b'", + "error": "SyntaxError" + }, + { + "name": "b'a' b'b'", + "source": "b'a' b'b'", + "repr": "b'ab'" + }, + { + "name": "u'x'", + "source": "u'x'", + "repr": "'x'" + }, + { + "name": "U'x'", + "source": "U'x'", + "repr": "'x'" + }, + { + "name": "r'x'", + "source": "r'x'", + "repr": "'x'" + }, + { + "name": "R'x'", + "source": "R'x'", + "repr": "'x'" + }, + { + "name": "b'x'", + "source": "b'x'", + "repr": "b'x'" + }, + { + "name": "B'x'", + "source": "B'x'", + "repr": "b'x'" + }, + { + "name": "br'x'", + "source": "br'x'", + "repr": "b'x'" + }, + { + "name": "Rb'x'", + "source": "Rb'x'", + "repr": "b'x'" + }, + { + "name": "rB'x'", + "source": "rB'x'", + "repr": "b'x'" + }, + { + "name": "ur'x'", + "source": "ur'x'", + "error": "SyntaxError" + }, + { + "name": "bu'x'", + "source": "bu'x'", + "error": "SyntaxError" + }, + { + "name": "f'x'", + "source": "f'x'", + "error": "ValueError" + }, + { + "name": "rf'x'", + "source": "rf'x'", + "error": "ValueError" + }, + { + "name": "'''a\nb'''", + "source": "'''a\nb'''", + "repr": "'a\\nb'" + }, + { + "name": "\"\"\"a\\\"\"\"\"", + "source": "\"\"\"a\\\"\"\"\"", + "repr": "'a\"'" + }, + { + "name": "'a\nb'", + "source": "'a\nb'", + "error": "SyntaxError" + }, + { + "name": "'a\\\nb'", + "source": "'a\\\nb'", + "repr": "'ab'" + }, + { + "name": "r'a\\\nb'", + "source": "r'a\\\nb'", + "repr": "'a\\\\\\nb'" + }, + { + "name": "'unterminated", + "source": "'unterminated", + "error": "SyntaxError" + }, + { + "name": "'\\a\\b\\f\\n\\r\\t\\v'", + "source": "'\\a\\b\\f\\n\\r\\t\\v'", + "repr": "'\\x07\\x08\\x0c\\n\\r\\t\\x0b'" + }, + { + "name": "'\\0\\12\\101\\1011'", + "source": "'\\0\\12\\101\\1011'", + "repr": "'\\x00\\nAA1'" + }, + { + "name": "'\\777'", + "source": "'\\777'", + "repr": "'\u01ff'" + }, + { + "name": "b'\\777'", + "source": "b'\\777'", + "repr": "b'\\xff'" + }, + { + "name": "b'\\400'", + "source": "b'\\400'", + "repr": "b'\\x00'" + }, + { + "name": "'\\x41'", + "source": "'\\x41'", + "repr": "'A'" + }, + { + "name": "'\\x4'", + "source": "'\\x4'", + "error": "SyntaxError" + }, + { + "name": "'\\u00e9'", + "source": "'\\u00e9'", + "repr": "'\u00e9'" + }, + { + "name": "'\\u00e'", + "source": "'\\u00e'", + "error": "SyntaxError" + }, + { + "name": "'\\U0001F600'", + "source": "'\\U0001F600'", + "repr": "'\ud83d\ude00'" + }, + { + "name": "'\\U00110000'", + "source": "'\\U00110000'", + "error": "SyntaxError" + }, + { + "name": "'\\ud800'", + "source": "'\\ud800'", + "repr": "'\\ud800'" + }, + { + "name": "'\\N{BULLET}'", + "source": "'\\N{BULLET}'", + "repr": "'\u2022'" + }, + { + "name": "'\\q'", + "source": "'\\q'", + "repr": "'\\\\q'" + }, + { + "name": "'\\\\'", + "source": "'\\\\'", + "repr": "'\\\\'" + }, + { + "name": "'\\''", + "source": "'\\''", + "repr": "\"'\"" + }, + { + "name": "\"\\\"\"", + "source": "\"\\\"\"", + "repr": "'\"'" + }, + { + "name": "b'\\u0041'", + "source": "b'\\u0041'", + "repr": "b'\\\\u0041'" + }, + { + "name": "b'\\x41\\xff'", + "source": "b'\\x41\\xff'", + "repr": "b'A\\xff'" + }, + { + "name": "b'caf\u00e9'", + "source": "b'caf\u00e9'", + "error": "SyntaxError" + }, + { + "name": "'caf\u00e9'", + "source": "'caf\u00e9'", + "repr": "'caf\u00e9'" + }, + { + "name": "r'\\d'", + "source": "r'\\d'", + "repr": "'\\\\d'" + }, + { + "name": "r'\\''", + "source": "r'\\''", + "repr": "\"\\\\'\"" + }, + { + "name": "rb'\\d'", + "source": "rb'\\d'", + "repr": "b'\\\\d'" + }, + { + "name": "r'\\'", + "source": "r'\\'", + "error": "SyntaxError" + }, + { + "name": "'\u65e5\ud83d\ude00'", + "source": "'\u65e5\ud83d\ude00'", + "repr": "'\u65e5\ud83d\ude00'" + } + ] +} diff --git a/litellm-rust/crates/python-compat/scripts/generate_fixtures.py b/litellm-rust/crates/python-compat/scripts/generate_fixtures.py new file mode 100644 index 00000000000..26456a638f6 --- /dev/null +++ b/litellm-rust/crates/python-compat/scripts/generate_fixtures.py @@ -0,0 +1,352 @@ +"""Regenerate generated/values.json: what CPython produces for each value in CORPUS. + + python scripts/generate_fixtures.py > generated/values.json + +Each row records `repr`, `str`, `json.dumps` (or its error), `bool`, and `pickle.dumps` at +every protocol. `literal` says whether `ast.literal_eval(repr(value))` gives the value back, +which is how Python reads `str(dict)` text back from a cache; the Rust tests reach the other +rows only through pickle. `view` is `repr` of the value as `pickle::loads` decodes it, with +tuples, sets, and frozensets rendered as lists. `sources` records `ast.literal_eval` on raw +source texts: its result, or the exception it raises. +""" + +import ast +import json +import pickle +import sys +import warnings + +# Entries are source texts, or `(name, source)` when the source is too long to read in a +# test report. `name` is what the Rust `KNOWN` table keys on. +CORPUS = [ + # Scalars + "None", + "True", + "False", + "0", + "-7", + "2**63 - 1", + "-(2**63)", + "2**64", + "-(2**70)", + # Floats around CPython's repr thresholds + "0.0", + "-0.0", + "0.2", + "1.0", + "-1.5", + "0.1 + 0.2", + "123456789.123", + "1e15", + "1e16", + "1.5e16", + "9999999999999998.0", + "0.0001", + "1e-05", + "1.25e-07", + "5e-324", + "1.7976931348623157e308", + "1e22", + "float('inf')", + "float('-inf')", + "float('nan')", + # Complex + "1j", + "-1j", + "complex(0, -1)", + "1+2j", + "-1.5-0.5j", + "complex(0.0, 1e16)", + # Strings: quote selection, escapes, printable and non-printable non-ASCII + "''", + "'plain'", + '"it\'s"', + "'say \"hi\"'", + "'both \\' and \"'", + "'back\\\\slash'", + "'\\t\\n\\r'", + "'\\x00\\x1f\\x7f'", + "'\\x85\\xa0\\xad'", + "'caf\\xe9'", + "'\\u65e5\\u672c'", + "'\\u200b\\u2028\\u3000'", + "'\\U0001f600'", + "'\\U000e0001\\U0010ffff'", + "'\\b\\f'", + # Bytes + "b''", + "b'abc'", + 'b"a\'b"', + "b'a\"b\\'c'", + "b'\\x00\\t\\n\\r\\x7f\\x80\\xff'", + # Containers + "[]", + "[1, 'a', None, True]", + "()", + "(1,)", + "(1, (2, 3))", + "{}", + "{'a': 1, 'b': [1.0, 2.5]}", + "{'z': 1, 'a': 2, 'm': 3}", + "{1: 'int', 2.5: 'float', True: 'bool', None: 'none'}", + "{(1, 2): 'tuple key'}", + "{'nested': {'deeper': {'deepest': [{}]}}}", + "{1}", + "set()", + "frozenset({1})", + "[[[[[[[[[[1]]]]]]]]]]", + # Deeper than the Rust decoders allow: CPython's parser accepts ~200 nested brackets + # and its unpickler has no limit, so this row records a deliberate divergence. + ("nested_150", "[" * 150 + "1" + "]" * 150), + # The shape LiteLLM caches + '{\'timestamp\': 1726000000.123, \'response\': \'{"id": "chatcmpl-1", "object": "chat.completion"}\'}', + "{'model': 'gpt-4o', 'messages': [{'role': 'user', 'content': 'hi'}], 'temperature': 0.2, 'stream': False}", +] + +# Source texts for `literal_eval` itself: tokenizer and evaluator edge cases, recorded with +# CPython's result or the exception it raises. Raw strings keep backslashes literal. +SOURCES = [ + # Layout: leading/trailing whitespace, comments, newlines, continuations + "1", + " 1", + "\t1", + "\n1", + " \n 1", + "1\n", + "1 # comment", + "# c\n1", + "1 \\\n", + "1,", + "1, 2", + "1,\n2", + "(1,\n2)", + "[1,\n 2,\n]", + # Containers and grouping + "()", + "(1)", + "((1,))", + "(,)", + "[,]", + "{,}", + "[1,]", + "{'a': 1,}", + "{1,}", + "{'a': 1 'b': 2}", + "{1: 'a', True: 'b'}", + "{1, True, 1.0}", + "{(1, 2): 'x', (1.0, 2): 'y'}", + "{[1]: 2}", + "{{1}}", + "{(1, [2])}", + "set()", + "set( )", + "set([1])", + "frozenset()", + # Names + "True", + "False", + "None", + "Truex", + "true", + "...", + # Integers and floats + "0", + "00", + "0_0", + "01", + "007", + "1_000", + "1_", + "1__0", + "_1", + "0x1F", + "0X_1f", + "0o17", + "0b101", + "0b102", + "0x", + "1e3", + "1E-3", + "1e", + "1.e5", + ".5", + "5.", + "1..", + "1.5.2", + "1_0.0_1e1_0", + "1e999", + "-1e999", + "1j", + "1.5J", + "010j", + "010.5", + "1a", + "0x1g", + # Signs and complex sums + "-1", + "+1", + "- 1", + "--1", + "-+1", + "-(1)", + "-(-1)", + "-(1+2j)", + "-True", + "-'a'", + "-[1]", + "1+2j", + "1-2j", + "1 + 2j", + "(1)+(2j)", + "1+2", + "1+2+3j", + "1+-2j", + "2j+1", + "True+1j", + "1-0j", + "0.0-0j", + "-0.0+1j", + "-0.0", + "-0", + "(-0.0)", + "[-0.0, (-0.0)]", + "2**3", + "1*2", + "1 if 1 else 2", + "(1,)(2)", + # String prefixes, quoting, and concatenation + "''", + '""', + "'a' 'b'", + "'a' \"b\" '''c'''", + "'a' b'b'", + "b'a' b'b'", + "u'x'", + "U'x'", + "r'x'", + "R'x'", + "b'x'", + "B'x'", + "br'x'", + "Rb'x'", + "rB'x'", + "ur'x'", + "bu'x'", + "f'x'", + "rf'x'", + "'''a\nb'''", + '"""a\\""""', + "'a\nb'", + "'a\\\nb'", + "r'a\\\nb'", + "'unterminated", + # Escapes + r"'\a\b\f\n\r\t\v'", + r"'\0\12\101\1011'", + r"'\777'", + r"b'\777'", + r"b'\400'", + r"'\x41'", + r"'\x4'", + r"'\u00e9'", + r"'\u00e'", + r"'\U0001F600'", + r"'\U00110000'", + r"'\ud800'", + r"'\N{BULLET}'", + r"'\q'", + r"'\\'", + r"'\''", + r'"\""', + r"b'\u0041'", + r"b'\x41\xff'", + "b'café'", + "'café'", + r"r'\d'", + r"r'\''", + r"rb'\d'", + r"r'\'", + "'日\U0001f600'", +] + + +def named(entry): + """Split a corpus entry into its report name and its source text.""" + if isinstance(entry, tuple): + return entry + return entry, entry + + +def evaluate(entry): + name, source = named(entry) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + return {"name": name, "source": source, "repr": repr(ast.literal_eval(source))} + except Exception as error: # noqa: BLE001 - recorded, not raised + return {"name": name, "source": source, "error": type(error).__name__} + + +def view(value): + if isinstance(value, (list, tuple, set, frozenset)): + return "[" + ", ".join(view(item) for item in value) + "]" + if isinstance(value, dict): + return "{" + ", ".join(f"{view(key)}: {view(item)}" for key, item in value.items()) + "}" + return repr(value) + + +def plain(value): + """Whether pickle can encode the value without a class reference such as `complex`.""" + if isinstance(value, complex): + return False + if isinstance(value, (list, tuple, set, frozenset)): + return all(plain(item) for item in value) + if isinstance(value, dict): + return all(plain(key) and plain(item) for key, item in value.items()) + return True + + +def is_literal(value): + try: + parsed = ast.literal_eval(repr(value)) + except (ValueError, SyntaxError): + return False + return repr(parsed) == repr(value) + + +def row(entry): + name, source = named(entry) + value = eval(source) + entry = { + "name": name, + "source": source, + "literal": is_literal(value), + "plain": plain(value), + "repr": repr(value), + "str": str(value), + "truthy": bool(value), + } + try: + entry["json"] = json.dumps(value) + except (TypeError, ValueError) as error: + entry["json_error"] = str(error) + try: + entry["pickle"] = {str(protocol): pickle.dumps(value, protocol=protocol).hex() for protocol in range(6)} + entry["view"] = view(value) + except Exception as error: # noqa: BLE001 - recorded, not raised + entry["pickle_error"] = f"{type(error).__name__}: {error}" + return entry + + +if __name__ == "__main__": + json.dump( + { + "python": sys.version.split()[0], + "rows": [row(entry) for entry in CORPUS], + "sources": [evaluate(entry) for entry in SOURCES], + }, + sys.stdout, + indent=2, + ensure_ascii=True, + ) + sys.stdout.write("\n") diff --git a/litellm-rust/crates/python-compat/scripts/generate_nonprintable.py b/litellm-rust/crates/python-compat/scripts/generate_nonprintable.py new file mode 100644 index 00000000000..70d1fbd2dd1 --- /dev/null +++ b/litellm-rust/crates/python-compat/scripts/generate_nonprintable.py @@ -0,0 +1,35 @@ +"""Regenerate generated/nonprintable.rs from this interpreter's `str.isprintable`. + +`repr(str)` escapes exactly the characters for which `str.isprintable()` is false, so the +table must come from the Python version the gateway interoperates with. + + python scripts/generate_nonprintable.py > generated/nonprintable.rs +""" + +import sys +import unicodedata + +ranges = [] +start = None +for code in range(0x110000): + printable = chr(code).isprintable() + if not printable and start is None: + start = code + elif printable and start is not None: + ranges.append((start, code - 1)) + start = None +if start is not None: + ranges.append((start, 0x10FFFF)) + +lines = [ + f"// Generated by scripts/generate_nonprintable.py from Python {sys.version.split()[0]}", + f"// (Unicode {unicodedata.unidata_version}). Do not edit by hand.", + "", + f'pub(crate) const UNICODE_VERSION: &str = "{unicodedata.unidata_version}";', + "", + "/// Inclusive code point ranges for which Python's `str.isprintable()` is false.", + f"pub(crate) const NONPRINTABLE: [(u32, u32); {len(ranges)}] = [", + *(f" (0x{low:04X}, 0x{high:04X})," for low, high in ranges), + "];", +] +sys.stdout.write("\n".join(lines) + "\n") diff --git a/litellm-rust/crates/python-compat/scripts/verify_rust_pickles.py b/litellm-rust/crates/python-compat/scripts/verify_rust_pickles.py new file mode 100644 index 00000000000..793acad61f9 --- /dev/null +++ b/litellm-rust/crates/python-compat/scripts/verify_rust_pickles.py @@ -0,0 +1,43 @@ +"""Check that CPython unpickles what `pickle::dumps` writes, to the value it was given. + + PYTHON_COMPAT_RUST_PICKLES=rust.tsv cargo test -p litellm-python-compat --test fixtures + python scripts/verify_rust_pickles.py rust.tsv + +The rows are plain data by construction, so this refuses to resolve any class rather than +handing file-controlled bytes to an unrestricted `pickle.loads`. +""" + +import ast +import io +import pickle +import sys + + +class PlainDataUnpickler(pickle.Unpickler): + """An unpickler with `GLOBAL`/`REDUCE` disabled, mirroring `pickle::loads` in Rust.""" + + def find_class(self, module, name): + raise pickle.UnpicklingError(f"refusing to resolve {module}.{name}") + + +def loads(data): + return PlainDataUnpickler(io.BytesIO(data)).load() + + +def main(path): + failures = 0 + rows = 0 + with open(path, encoding="utf-8") as lines: + for line in lines: + data, expected = line.rstrip("\n").split("\t", 1) + rows += 1 + actual = repr(loads(bytes.fromhex(data))) + if actual != repr(ast.literal_eval(expected)): + failures += 1 + sys.stdout.write(f"mismatch: expected {expected}, got {actual}\n") + sys.stdout.write(f"{rows} Rust pickles checked, {failures} mismatches\n") + return 1 if failures or not rows else 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1])) diff --git a/litellm-rust/crates/python-compat/src/error.rs b/litellm-rust/crates/python-compat/src/error.rs new file mode 100644 index 00000000000..43990eb7602 --- /dev/null +++ b/litellm-rust/crates/python-compat/src/error.rs @@ -0,0 +1,25 @@ +use crate::MAX_DEPTH; + +/// A failure to read or write a Python format. Messages quote CPython's own wording where +/// the Python side raises (`TypeError`, `ValueError`), so callers can log them as is. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("malformed Python literal at byte {0}")] + InvalidLiteral(usize), + #[error("unhashable type: '{0}'")] + Unhashable(&'static str), + #[error("value nests deeper than {MAX_DEPTH} levels")] + TooDeep, + #[error("invalid pickle: {0}")] + InvalidPickle(String), + #[error("Object of type {0} is not JSON serializable")] + NotJsonSerializable(&'static str), + #[error("keys must be str, int, float, bool or None, not {0}")] + InvalidJsonKey(&'static str), + #[error("Out of range float values are not JSON compliant")] + NonFiniteFloat, + #[error("integer does not fit in the JSON number range")] + IntegerOutOfRange, + #[error("Object of type {0} cannot be pickled as plain data")] + NotPicklable(&'static str), +} diff --git a/litellm-rust/crates/python-compat/src/json.rs b/litellm-rust/crates/python-compat/src/json.rs new file mode 100644 index 00000000000..d3436519ac4 --- /dev/null +++ b/litellm-rust/crates/python-compat/src/json.rs @@ -0,0 +1,168 @@ +//! `json.dumps` with CPython's default options, and the JSON value `json.loads` returns. +//! +//! Defaults are `ensure_ascii=True`, `allow_nan=True`, separators `(", ", ": ")`, and no key +//! sorting. Python's `json.loads` is mapped by [`from_json`]; a `serde_json` number that does +//! not fit `i64` or `u64` arrives as a float, where Python would keep an `int`. + +use std::fmt::Write; + +use serde_json::{Map, Number}; + +use crate::{Error, Value, repr::float_repr}; + +/// `json.dumps(value)`. +pub fn dumps(value: &Value) -> Result { + let mut out = String::new(); + write_value(&mut out, value)?; + Ok(out) +} + +/// The Python value `json.loads` returns for a JSON document. +pub fn from_json(value: serde_json::Value) -> Value { + match value { + serde_json::Value::Null => Value::None, + serde_json::Value::Bool(value) => Value::Bool(value), + serde_json::Value::Number(number) => { + if let Some(value) = number.as_i64() { + Value::Int(value.into()) + } else if let Some(value) = number.as_u64() { + Value::Int(value.into()) + } else { + Value::Float(number.as_f64().unwrap_or(f64::NAN)) + } + } + serde_json::Value::String(text) => Value::Str(text), + serde_json::Value::Array(values) => { + Value::List(values.into_iter().map(from_json).collect()) + } + serde_json::Value::Object(entries) => Value::Dict( + entries + .into_iter() + .map(|(key, value)| (Value::Str(key), from_json(value))) + .collect(), + ), + } +} + +/// `json.loads(json.dumps(value))` as a `serde_json` value: tuples become arrays and dict +/// keys are coerced to strings as `json.dumps` does. Non-finite floats, which Python writes +/// as `NaN` and `Infinity`, have no `serde_json` form and fail with [`Error::NonFiniteFloat`]. +pub fn to_json(value: &Value) -> Result { + Ok(match value { + Value::None => serde_json::Value::Null, + Value::Bool(value) => serde_json::Value::Bool(*value), + Value::Int(value) => { + let text = value.to_string(); + serde_json::Value::Number( + text.parse::() + .map(Number::from) + .or_else(|_| text.parse::().map(Number::from)) + .map_err(|_| Error::IntegerOutOfRange)?, + ) + } + Value::Float(value) => { + serde_json::Value::Number(Number::from_f64(*value).ok_or(Error::NonFiniteFloat)?) + } + Value::Str(text) => serde_json::Value::String(text.clone()), + Value::List(values) | Value::Tuple(values) => { + serde_json::Value::Array(values.iter().map(to_json).collect::, _>>()?) + } + Value::Dict(entries) => serde_json::Value::Object( + entries + .iter() + .map(|(key, value)| Ok((json_key(key)?, to_json(value)?))) + .collect::, Error>>()?, + ), + value @ (Value::Bytes(_) | Value::Set(_) | Value::Complex { .. }) => { + return Err(Error::NotJsonSerializable(value.type_name())); + } + }) +} + +fn write_value(out: &mut String, value: &Value) -> Result<(), Error> { + match value { + Value::None => out.push_str("null"), + Value::Bool(true) => out.push_str("true"), + Value::Bool(false) => out.push_str("false"), + Value::Int(value) => { + let _ = write!(out, "{value}"); + } + Value::Float(value) => out.push_str(&float_text(*value)), + Value::Str(text) => write_string(out, text), + Value::List(values) | Value::Tuple(values) => { + out.push('['); + for (index, value) in values.iter().enumerate() { + if index > 0 { + out.push_str(", "); + } + write_value(out, value)?; + } + out.push(']'); + } + Value::Dict(entries) => { + out.push('{'); + for (index, (key, value)) in entries.iter().enumerate() { + if index > 0 { + out.push_str(", "); + } + write_string(out, &json_key(key)?); + out.push_str(": "); + write_value(out, value)?; + } + out.push('}'); + } + value @ (Value::Bytes(_) | Value::Set(_) | Value::Complex { .. }) => { + return Err(Error::NotJsonSerializable(value.type_name())); + } + } + Ok(()) +} + +/// `json.encoder.JSONEncoder.iterencode`'s `floatstr` with `allow_nan=True`. +fn float_text(value: f64) -> String { + if value.is_nan() { + "NaN".to_owned() + } else if value.is_infinite() { + if value > 0.0 { "Infinity" } else { "-Infinity" }.to_owned() + } else { + float_repr(value) + } +} + +/// Dict key coercion in `json.dumps`: scalars become their JSON text, other keys fail. +fn json_key(key: &Value) -> Result { + Ok(match key { + Value::Str(text) => text.clone(), + Value::Int(value) => value.to_string(), + Value::Float(value) => float_text(*value), + Value::Bool(true) => "true".to_owned(), + Value::Bool(false) => "false".to_owned(), + Value::None => "null".to_owned(), + key => return Err(Error::InvalidJsonKey(key.type_name())), + }) +} + +/// `py_encode_basestring_ascii`: escape `"`, `\`, control characters, and everything outside +/// printable ASCII as `\uXXXX`, with surrogate pairs above the BMP. +fn write_string(out: &mut String, text: &str) { + out.push('"'); + for ch in text.chars() { + match ch { + '"' => out.push_str("\\\""), + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\u{8}' => out.push_str("\\b"), + '\u{c}' => out.push_str("\\f"), + ' '..='~' => out.push(ch), + ch => { + let mut units = [0u16; 2]; + for unit in ch.encode_utf16(&mut units) { + let _ = write!(out, "\\u{unit:04x}"); + } + } + } + } + out.push('"'); +} diff --git a/litellm-rust/crates/python-compat/src/lib.rs b/litellm-rust/crates/python-compat/src/lib.rs new file mode 100644 index 00000000000..b23aff409f2 --- /dev/null +++ b/litellm-rust/crates/python-compat/src/lib.rs @@ -0,0 +1,39 @@ +//! Python data formats reproduced in Rust, for state that Python LiteLLM writes and reads. +//! +//! Each module mirrors one Python operation over plain data values, and its tests replay +//! fixtures generated by that operation in CPython (`scripts/generate_fixtures.py`): +//! +//! | Module | Python operation | +//! |---|---| +//! | [`literal`] | `ast.literal_eval(text)` | +//! | [`repr`] | `repr(value)` and `str(value)` | +//! | [`json`] | `json.dumps(value)`, and `json.loads(json.dumps(value))` as a JSON value | +//! | [`pickle`] | `pickle.loads(data)` and `pickle.dumps(value)` for plain data | +//! | [`truthy`] | `bool(value)` | +//! +//! [`Value`] is the closed data model these formats share. Live Python objects +//! (descriptors, `__bool__`, `__str__`, callbacks) are out of scope: those belong to the +//! PyO3 boundary in `litellm-python-bridge`, which runs the real protocol. +//! +//! Known limits, each pinned by a test: +//! - `set` iteration order follows Python's hash order, which this crate does not model +//! (string hashes are randomized per process). Sets keep their literal order. +//! - `str` values are Rust `String`s, so lone surrogates cannot be represented. +//! - [`pickle::loads`] decodes `tuple`, `set`, and `frozenset` as lists. + +mod error; +pub mod json; +pub mod literal; +pub mod pickle; +pub mod repr; +pub mod truthy; +mod value; + +pub use error::Error; +pub use num_bigint::BigInt; +pub use value::Value; + +/// Nesting limit for the decoders, which recurse. It keeps untrusted persisted data from +/// overflowing the Rust stack, and is deliberately stricter than CPython, whose parser takes +/// about 200 nested brackets and whose unpickler has no limit. +pub const MAX_DEPTH: usize = 128; diff --git a/litellm-rust/crates/python-compat/src/literal.rs b/litellm-rust/crates/python-compat/src/literal.rs new file mode 100644 index 00000000000..12e9036572f --- /dev/null +++ b/litellm-rust/crates/python-compat/src/literal.rs @@ -0,0 +1,745 @@ +//! `ast.literal_eval(text)`, as a single-pass recursive-descent parser. +//! +//! Tokens follow CPython's tokenizer (string prefixes, escapes, implicit concatenation, +//! numeric underscores and radixes, comments, line continuations). Expressions follow +//! `ast.literal_eval`'s evaluator: +//! +//! - one unary `+`/`-`, applied only to a numeric constant (`-(1)` is fine, `--1` is not) +//! - `a + b` / `a - b` only as a signed real plus or minus a complex constant, with 3.14's +//! mixed-mode rules (`1 - 0j` is `(1-0j)`) +//! - parentheses group without making a tuple; `set()` is the only call +//! - dict keys and set members are deduplicated with Python equality (`1 == 1.0 == True`) +//! +//! Not supported, each pinned by a fixture: `\N{NAME}` escapes, `...`, and escapes that +//! produce lone surrogates. + +use std::collections::{HashMap, hash_map::Entry}; + +use num_bigint::BigInt; +use num_traits::{FromPrimitive, ToPrimitive}; + +use crate::{Error, MAX_DEPTH, Value}; + +pub fn literal_eval(text: &str) -> Result { + let mut parser = Parser { + bytes: text.trim_start_matches([' ', '\t']).as_bytes(), + offset: text.len() - text.trim_start_matches([' ', '\t']).len(), + pos: 0, + depth: 0, + brackets: 0, + }; + parser.skip_leading_lines()?; + let value = parser.top_level()?.value; + parser.skip_trivia(true); + if parser.pos != parser.bytes.len() { + return Err(parser.error()); + } + Ok(value) +} + +/// How a parsed term may take part in `+`/`-`, per `ast.literal_eval`'s `_convert_num` +/// (`Constant`) and `_convert_signed_num` (`Signed`). `Other` is any other node. +#[derive(Clone, Copy, PartialEq)] +enum Kind { + Constant, + Signed, + Other, +} + +struct Term { + value: Value, + kind: Kind, +} + +impl Term { + fn other(value: Value) -> Self { + Self { + value, + kind: Kind::Other, + } + } + + fn is_number(&self) -> bool { + matches!( + self.value, + Value::Int(_) | Value::Float(_) | Value::Complex { .. } + ) + } +} + +struct Parser<'a> { + bytes: &'a [u8], + /// Bytes stripped before `bytes` starts, so errors report offsets into the input. + offset: usize, + pos: usize, + depth: usize, + /// Open brackets: newlines are insignificant only inside them. + brackets: usize, +} + +impl Parser<'_> { + fn error(&self) -> Error { + Error::InvalidLiteral(self.offset + self.pos) + } + + fn peek(&self) -> Option { + self.bytes.get(self.pos).copied() + } + + fn peek_at(&self, ahead: usize) -> Option { + self.bytes.get(self.pos + ahead).copied() + } + + fn expect(&mut self, byte: u8) -> Result<(), Error> { + if self.peek() != Some(byte) { + return Err(self.error()); + } + self.pos += 1; + Ok(()) + } + + fn enter(&mut self) -> Result<(), Error> { + self.depth += 1; + if self.depth > MAX_DEPTH { + return Err(Error::TooDeep); + } + Ok(()) + } + + /// Whitespace, comments, and backslash continuations; newlines too when `newlines`. + fn skip_trivia(&mut self, newlines: bool) { + while let Some(byte) = self.peek() { + match byte { + b' ' | b'\t' | b'\x0c' => self.pos += 1, + b'#' => { + while !matches!(self.peek(), None | Some(b'\n' | b'\r')) { + self.pos += 1; + } + } + // A continuation joins two lines; one that ends the input is an EOF error. + b'\\' if matches!(self.peek_at(1), Some(b'\n' | b'\r')) => { + let len = match (self.peek_at(1), self.peek_at(2)) { + (Some(b'\r'), Some(b'\n')) => 3, + _ => 2, + }; + if self.pos + len >= self.bytes.len() { + break; + } + self.pos += len; + } + b'\n' | b'\r' if newlines => self.pos += 1, + _ => break, + } + } + } + + /// Blank and comment-only lines may precede the expression, whose own line must not be + /// indented (CPython raises `IndentationError`). + fn skip_leading_lines(&mut self) -> Result<(), Error> { + loop { + let line_start = self.pos; + self.skip_trivia(false); + match self.peek() { + Some(b'\n' | b'\r') => self.pos += 1, + Some(_) if line_start > 0 && self.pos > line_start => { + self.pos = line_start; + return Err(self.error()); + } + _ => return Ok(()), + } + } + } + + fn at_logical_line_end(&self) -> bool { + matches!(self.peek(), None | Some(b'\n' | b'\r')) + } + + /// The `eval` input: an expression, or a tuple without parentheses. + fn top_level(&mut self) -> Result { + let first = self.expression()?; + self.skip_trivia(false); + if self.peek() != Some(b',') { + return Ok(first); + } + let mut values = vec![first.value]; + while self.peek() == Some(b',') { + self.pos += 1; + self.skip_trivia(false); + if self.at_logical_line_end() { + break; + } + values.push(self.expression()?.value); + self.skip_trivia(false); + } + Ok(Term::other(Value::Tuple(values))) + } + + /// A sum of unary terms, checked as `ast.literal_eval` checks `BinOp`. + fn expression(&mut self) -> Result { + let mut left = self.unary()?; + loop { + self.skip_trivia(self.brackets > 0); + let subtract = match self.peek() { + Some(b'+') => false, + Some(b'-') => true, + _ => return Ok(left), + }; + let at = self.pos; + self.pos += 1; + let right = self.unary()?; + left = complex_sum(left, subtract, right) + .ok_or(Error::InvalidLiteral(self.offset + at))?; + } + } + + fn unary(&mut self) -> Result { + self.skip_trivia(self.brackets > 0); + let negative = match self.peek() { + Some(b'+') => false, + Some(b'-') => true, + _ => return self.primary(), + }; + let at = self.pos; + self.pos += 1; + self.enter()?; + let operand = self.unary()?; + self.depth -= 1; + if operand.kind != Kind::Constant || !operand.is_number() { + return Err(Error::InvalidLiteral(self.offset + at)); + } + let value = if !negative { + operand.value + } else { + match operand.value { + Value::Int(value) => Value::Int(-value), + Value::Float(value) => Value::Float(-value), + Value::Complex { re, im } => Value::Complex { re: -re, im: -im }, + _ => unreachable!("checked numeric above"), + } + }; + Ok(Term { + value, + kind: Kind::Signed, + }) + } + + fn primary(&mut self) -> Result { + match self.peek() { + Some(b'(') => self.parenthesized(), + Some(b'[') => self.list(), + Some(b'{') => self.braced(), + Some(b'0'..=b'9') => self.number(), + Some(b'.') if matches!(self.peek_at(1), Some(b'0'..=b'9')) => self.number(), + Some(b'\'' | b'"') => self.strings(), + Some(byte) if byte.is_ascii_alphabetic() || byte == b'_' => { + if self.string_prefix_len().is_some() { + return self.strings(); + } + self.name() + } + _ => Err(self.error()), + } + } + + fn open(&mut self) -> Result<(), Error> { + self.enter()?; + self.brackets += 1; + self.pos += 1; + Ok(()) + } + + fn close(&mut self, byte: u8) -> Result<(), Error> { + self.skip_trivia(true); + self.expect(byte)?; + self.brackets -= 1; + self.depth -= 1; + Ok(()) + } + + /// Comma-separated expressions up to `close`, with an optional trailing comma. + fn elements(&mut self, close: u8) -> Result, Error> { + let mut values = Vec::new(); + loop { + self.skip_trivia(true); + if self.peek() == Some(close) { + return Ok(values); + } + values.push(self.expression()?.value); + self.skip_trivia(true); + if self.peek() != Some(b',') { + return Ok(values); + } + self.pos += 1; + } + } + + fn parenthesized(&mut self) -> Result { + self.open()?; + self.skip_trivia(true); + if self.peek() == Some(b')') { + self.close(b')')?; + return Ok(Term::other(Value::Tuple(Vec::new()))); + } + let first = self.expression()?; + self.skip_trivia(true); + if self.peek() != Some(b',') { + self.close(b')')?; + return Ok(first); + } + self.pos += 1; + let mut values = vec![first.value]; + values.extend(self.elements(b')')?); + self.close(b')')?; + Ok(Term::other(Value::Tuple(values))) + } + + fn list(&mut self) -> Result { + self.open()?; + let values = self.elements(b']')?; + self.close(b']')?; + Ok(Term::other(Value::List(values))) + } + + fn braced(&mut self) -> Result { + self.open()?; + self.skip_trivia(true); + if self.peek() == Some(b'}') { + self.close(b'}')?; + return Ok(Term::other(Value::Dict(Vec::new()))); + } + let first = self.expression()?.value; + self.skip_trivia(true); + if self.peek() != Some(b':') { + let mut members = UniqueValues::default(); + members.insert(first, None)?; + if self.peek() == Some(b',') { + self.pos += 1; + for member in self.elements(b'}')? { + members.insert(member, None)?; + } + } + self.close(b'}')?; + return Ok(Term::other(Value::Set(members.keys))); + } + let mut entries = UniqueValues::default(); + let mut key = first; + loop { + self.expect(b':')?; + let value = self.expression()?.value; + entries.insert(key, Some(value))?; + self.skip_trivia(true); + if self.peek() != Some(b',') { + break; + } + self.pos += 1; + self.skip_trivia(true); + if self.peek() == Some(b'}') { + break; + } + key = self.expression()?.value; + self.skip_trivia(true); + } + self.close(b'}')?; + Ok(Term::other(Value::Dict(entries.into_entries()))) + } + + fn identifier(&mut self) -> &[u8] { + let start = self.pos; + while matches!(self.peek(), Some(byte) if byte.is_ascii_alphanumeric() || byte == b'_') { + self.pos += 1; + } + &self.bytes[start..self.pos] + } + + fn name(&mut self) -> Result { + let at = self.pos; + let value = match self.identifier() { + b"True" => Value::Bool(true), + b"False" => Value::Bool(false), + b"None" => Value::None, + b"set" => { + self.skip_trivia(self.brackets > 0); + self.expect(b'(')?; + self.skip_trivia(true); + self.expect(b')')?; + return Ok(Term::other(Value::Set(Vec::new()))); + } + _ => return Err(Error::InvalidLiteral(self.offset + at)), + }; + Ok(Term { + value, + kind: Kind::Constant, + }) + } + + /// Digits with single underscores between them, as CPython's `digitpart`. + fn digits(&mut self, radix: u32, out: &mut String) -> Result<(), Error> { + let start = out.len(); + loop { + match self.peek() { + Some(byte) if (byte as char).is_digit(radix) => { + out.push(byte as char); + self.pos += 1; + } + Some(b'_') + if out.len() > start + && matches!(self.peek_at(1), Some(next) if (next as char).is_digit(radix)) => + { + self.pos += 1; + } + _ => break, + } + } + if out.len() == start { + return Err(self.error()); + } + Ok(()) + } + + fn number(&mut self) -> Result { + let start = self.pos; + let radix = match ( + self.peek(), + self.peek_at(1).map(|byte| byte.to_ascii_lowercase()), + ) { + (Some(b'0'), Some(b'x')) => Some(16), + (Some(b'0'), Some(b'o')) => Some(8), + (Some(b'0'), Some(b'b')) => Some(2), + _ => None, + }; + let mut text = String::new(); + let value = if let Some(radix) = radix { + self.pos += 2; + if self.peek() == Some(b'_') { + self.pos += 1; + } + self.digits(radix, &mut text)?; + Value::Int(BigInt::parse_bytes(text.as_bytes(), radix).ok_or(self.error())?) + } else { + let mut is_float = false; + if self.peek() != Some(b'.') { + self.digits(10, &mut text)?; + } + let integer_digits = text.clone(); + if self.peek() == Some(b'.') { + is_float = true; + self.pos += 1; + text.push('.'); + if matches!(self.peek(), Some(b'0'..=b'9')) { + self.digits(10, &mut text)?; + } + } + if matches!(self.peek(), Some(b'e' | b'E')) { + is_float = true; + self.pos += 1; + text.push('e'); + if let Some(sign @ (b'+' | b'-')) = self.peek() { + text.push(sign as char); + self.pos += 1; + } + self.digits(10, &mut text)?; + } + if matches!(self.peek(), Some(b'j' | b'J')) { + self.pos += 1; + let im = text.parse::().map_err(|_| self.error())?; + Value::Complex { re: 0.0, im } + } else if is_float { + Value::Float(text.parse::().map_err(|_| self.error())?) + } else { + if integer_digits.len() > 1 + && integer_digits.starts_with('0') + && integer_digits.bytes().any(|digit| digit != b'0') + { + return Err(Error::InvalidLiteral(self.offset + start)); + } + Value::Int(BigInt::parse_bytes(integer_digits.as_bytes(), 10).ok_or(self.error())?) + } + }; + if matches!(self.peek(), Some(byte) if byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'.') + { + return Err(self.error()); + } + Ok(Term { + value, + kind: Kind::Constant, + }) + } + + /// The length of a valid string prefix (`r`, `u`, `b`, `br`, `rb`, any case) directly + /// followed by a quote. + fn string_prefix_len(&self) -> Option { + let mut len = 0; + while matches!(self.peek_at(len), Some(byte) if byte.is_ascii_alphabetic()) && len < 3 { + len += 1; + } + if !matches!(self.peek_at(len), Some(b'\'' | b'"')) { + return None; + } + let prefix: Vec = self.bytes[self.pos..self.pos + len] + .iter() + .map(u8::to_ascii_lowercase) + .collect(); + matches!(prefix.as_slice(), b"" | b"r" | b"u" | b"b" | b"br" | b"rb").then_some(len) + } + + /// Adjacent string literals concatenate; `str` and `bytes` cannot mix. + fn strings(&mut self) -> Result { + let mut text: Option = None; + let mut bytes: Option> = None; + loop { + let at = self.pos; + let Some(prefix_len) = self.string_prefix_len() else { + break; + }; + let prefix = &self.bytes[self.pos..self.pos + prefix_len]; + let raw = prefix.iter().any(|byte| byte.eq_ignore_ascii_case(&b'r')); + let is_bytes = prefix.iter().any(|byte| byte.eq_ignore_ascii_case(&b'b')); + self.pos += prefix_len; + let mut out = Vec::new(); + self.string_body(raw, is_bytes, &mut out)?; + if is_bytes { + if text.is_some() { + return Err(Error::InvalidLiteral(self.offset + at)); + } + bytes.get_or_insert_with(Vec::new).extend(out); + } else { + if bytes.is_some() { + return Err(Error::InvalidLiteral(self.offset + at)); + } + let piece = + String::from_utf8(out).map_err(|_| Error::InvalidLiteral(self.offset + at))?; + text.get_or_insert_with(String::new).push_str(&piece); + } + self.skip_trivia(self.brackets > 0); + } + let value = match (text, bytes) { + (Some(text), None) => Value::Str(text), + (None, Some(bytes)) => Value::Bytes(bytes), + _ => return Err(self.error()), + }; + Ok(Term { + value, + kind: Kind::Constant, + }) + } + + /// One quoted body, decoded into UTF-8 (`str`) or raw bytes (`bytes`). + fn string_body(&mut self, raw: bool, is_bytes: bool, out: &mut Vec) -> Result<(), Error> { + let quote = self.bytes[self.pos]; + let triple = self.peek_at(1) == Some(quote) && self.peek_at(2) == Some(quote); + self.pos += if triple { 3 } else { 1 }; + loop { + let Some(byte) = self.peek() else { + return Err(self.error()); + }; + if byte == quote + && (!triple || (self.peek_at(1) == Some(quote) && self.peek_at(2) == Some(quote))) + { + self.pos += if triple { 3 } else { 1 }; + return Ok(()); + } + match byte { + b'\n' | b'\r' if !triple => return Err(self.error()), + b'\\' if raw => { + let Some(next) = self.peek_at(1) else { + return Err(self.error()); + }; + out.push(b'\\'); + self.pos += 1; + if next == b'\n' || next == b'\r' || next == quote || next == b'\\' { + out.push(next); + self.pos += 1; + } + } + b'\\' => { + self.pos += 1; + self.escape(is_bytes, out)?; + } + byte if is_bytes && !byte.is_ascii() => return Err(self.error()), + byte => { + out.push(byte); + self.pos += 1; + } + } + } + } + + fn escape(&mut self, is_bytes: bool, out: &mut Vec) -> Result<(), Error> { + let Some(byte) = self.peek() else { + return Err(self.error()); + }; + self.pos += 1; + let simple = match byte { + b'\n' => return Ok(()), + b'\r' => { + if self.peek() == Some(b'\n') { + self.pos += 1; + } + return Ok(()); + } + b'\\' | b'\'' | b'"' => byte, + b'a' => 0x07, + b'b' => 0x08, + b'f' => 0x0c, + b'n' => b'\n', + b'r' => b'\r', + b't' => b'\t', + b'v' => 0x0b, + b'0'..=b'7' => { + let mut code = u32::from(byte - b'0'); + for _ in 0..2 { + match self.peek() { + Some(digit @ b'0'..=b'7') => { + code = code * 8 + u32::from(digit - b'0'); + self.pos += 1; + } + _ => break, + } + } + // Bytes keep the low eight bits of `\400`-`\777`, as CPython does. + return self.push_code(if is_bytes { code & 0xff } else { code }, is_bytes, out); + } + b'x' => { + let code = self.hex(2)?; + return self.push_code(code, is_bytes, out); + } + b'u' if !is_bytes => { + let code = self.hex(4)?; + return self.push_code(code, is_bytes, out); + } + b'U' if !is_bytes => { + let code = self.hex(8)?; + return self.push_code(code, is_bytes, out); + } + b'N' if !is_bytes => return Err(self.error()), + _ => { + // Unknown escapes keep the backslash (a `SyntaxWarning` in CPython). + out.push(b'\\'); + self.pos -= 1; + return Ok(()); + } + }; + out.push(simple); + Ok(()) + } + + fn hex(&mut self, count: usize) -> Result { + let mut code = 0u32; + for _ in 0..count { + let digit = self + .peek() + .and_then(|byte| (byte as char).to_digit(16)) + .ok_or(self.error())?; + code = code * 16 + digit; + self.pos += 1; + } + Ok(code) + } + + fn push_code(&self, code: u32, is_bytes: bool, out: &mut Vec) -> Result<(), Error> { + if is_bytes { + out.push(u8::try_from(code).map_err(|_| self.error())?); + return Ok(()); + } + let ch = char::from_u32(code).ok_or(self.error())?; + let mut buffer = [0u8; 4]; + out.extend_from_slice(ch.encode_utf8(&mut buffer).as_bytes()); + Ok(()) + } +} + +/// `left + right` or `left - right` as `ast.literal_eval` permits: a signed real on the +/// left and an unsigned complex constant on the right, combined with CPython 3.14's +/// mixed-mode rules, which leave the imaginary part untouched by the real operand. +fn complex_sum(left: Term, subtract: bool, right: Term) -> Option { + if left.kind == Kind::Other || right.kind != Kind::Constant { + return None; + } + let real = match &left.value { + Value::Int(value) => value.to_f64().filter(|value| value.is_finite())?, + Value::Float(value) => *value, + _ => return None, + }; + let Value::Complex { re, im } = right.value else { + return None; + }; + let value = if subtract { + Value::Complex { + re: real - re, + im: -im, + } + } else { + Value::Complex { re: real + re, im } + }; + Some(Term::other(value)) +} + +/// Python equality for hashable literal values: numbers compare by value across `bool`, +/// `int`, `float`, and `complex`, so `1`, `1.0`, `True`, and `(1+0j)` are one key. +#[derive(Hash, PartialEq, Eq)] +enum KeyId { + None, + Int(BigInt), + Float(u64), + Complex(u64, u64), + Str(String), + Bytes(Vec), + Tuple(Vec), +} + +fn float_key(value: f64) -> KeyId { + if value.fract() == 0.0 + && let Some(integer) = BigInt::from_f64(value) + { + return KeyId::Int(integer); + } + KeyId::Float(value.to_bits()) +} + +fn key_id(value: &Value) -> Result { + Ok(match value { + Value::None => KeyId::None, + Value::Bool(value) => KeyId::Int(BigInt::from(u8::from(*value))), + Value::Int(value) => KeyId::Int(value.clone()), + Value::Float(value) => float_key(*value), + Value::Complex { re, im } if *im == 0.0 => float_key(*re), + Value::Complex { re, im } => KeyId::Complex((re + 0.0).to_bits(), (im + 0.0).to_bits()), + Value::Str(text) => KeyId::Str(text.clone()), + Value::Bytes(bytes) => KeyId::Bytes(bytes.clone()), + Value::Tuple(values) => KeyId::Tuple(values.iter().map(key_id).collect::>()?), + value @ (Value::List(_) | Value::Dict(_) | Value::Set(_)) => { + return Err(Error::Unhashable(value.type_name())); + } + }) +} + +/// Dict entries or set members in first-seen order: a repeated key keeps its first +/// position and, for dicts, takes the latest value. +#[derive(Default)] +struct UniqueValues { + keys: Vec, + values: Vec, + index: HashMap, +} + +impl UniqueValues { + fn insert(&mut self, key: Value, value: Option) -> Result<(), Error> { + match self.index.entry(key_id(&key)?) { + Entry::Occupied(slot) => { + if let Some(value) = value { + self.values[*slot.get()] = value; + } + } + Entry::Vacant(slot) => { + slot.insert(self.keys.len()); + self.keys.push(key); + self.values.extend(value); + } + } + Ok(()) + } + + fn into_entries(self) -> Vec<(Value, Value)> { + self.keys.into_iter().zip(self.values).collect() + } +} diff --git a/litellm-rust/crates/python-compat/src/pickle.rs b/litellm-rust/crates/python-compat/src/pickle.rs new file mode 100644 index 00000000000..bff7442dc6b --- /dev/null +++ b/litellm-rust/crates/python-compat/src/pickle.rs @@ -0,0 +1,187 @@ +//! `pickle.loads` and `pickle.dumps` for plain data, as diskcache stores LiteLLM values. +//! +//! Both directions go through `serde-pickle`'s serde interface rather than +//! `serde_pickle::Value`, because that value type keeps dicts in a `BTreeMap` and would +//! reorder keys. The serde interface keeps insertion order, at the cost of reporting +//! `tuple`, `set`, and `frozenset` as sequences: [`loads`] decodes all three as lists. +//! Python objects that need a class (`GLOBAL`/`REDUCE`) and recursive structures fail. + +use std::fmt; + +use serde::{ + Deserializer, Serialize, Serializer, + de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor}, + ser::{SerializeMap, SerializeSeq, SerializeTuple}, +}; +use serde_pickle::{DeOptions, SerOptions}; + +use crate::{Error, MAX_DEPTH, Value}; + +/// `pickle.loads(data)` for any protocol from 0 to 5. +pub fn loads(data: &[u8]) -> Result { + let mut deserializer = serde_pickle::Deserializer::new(data, DeOptions::new()); + let value = Seed { depth: 0 } + .deserialize(&mut deserializer) + .map_err(|error| Error::InvalidPickle(error.to_string()))?; + deserializer + .end() + .map_err(|error| Error::InvalidPickle(error.to_string()))?; + Ok(value) +} + +/// `pickle.dumps(value, protocol=3)`. Every Python 3 reads protocol 3, whatever its own +/// default. Sets and complex numbers are rejected rather than silently changing type. +pub fn dumps(value: &Value) -> Result, Error> { + check_picklable(value, 0)?; + serde_pickle::to_vec(&Pickled(value), SerOptions::new()) + .map_err(|error| Error::InvalidPickle(error.to_string())) +} + +fn check_picklable(value: &Value, depth: usize) -> Result<(), Error> { + if depth > MAX_DEPTH { + return Err(Error::TooDeep); + } + match value { + Value::Int(value) if i64::try_from(value).is_err() => Err(Error::IntegerOutOfRange), + value @ (Value::Set(_) | Value::Complex { .. }) => { + Err(Error::NotPicklable(value.type_name())) + } + Value::List(values) | Value::Tuple(values) => values + .iter() + .try_for_each(|value| check_picklable(value, depth + 1)), + Value::Dict(entries) => entries.iter().try_for_each(|(key, value)| { + check_picklable(key, depth + 1)?; + check_picklable(value, depth + 1) + }), + _ => Ok(()), + } +} + +struct Pickled<'a>(&'a Value); + +impl Serialize for Pickled<'_> { + fn serialize(&self, serializer: S) -> Result { + match self.0 { + Value::None => serializer.serialize_unit(), + Value::Bool(value) => serializer.serialize_bool(*value), + Value::Int(value) => { + let value = i64::try_from(value) + .map_err(|_| serde::ser::Error::custom("integer out of i64 range"))?; + serializer.serialize_i64(value) + } + Value::Float(value) => serializer.serialize_f64(*value), + Value::Str(text) => serializer.serialize_str(text), + Value::Bytes(bytes) => serializer.serialize_bytes(bytes), + Value::List(values) => { + let mut seq = serializer.serialize_seq(Some(values.len()))?; + for value in values { + seq.serialize_element(&Pickled(value))?; + } + seq.end() + } + Value::Tuple(values) => { + let mut tuple = serializer.serialize_tuple(values.len())?; + for value in values { + tuple.serialize_element(&Pickled(value))?; + } + tuple.end() + } + Value::Dict(entries) => { + let mut map = serializer.serialize_map(Some(entries.len()))?; + for (key, value) in entries { + map.serialize_entry(&Pickled(key), &Pickled(value))?; + } + map.end() + } + value @ (Value::Set(_) | Value::Complex { .. }) => Err(serde::ser::Error::custom( + format!("{} cannot be pickled as plain data", value.type_name()), + )), + } + } +} + +#[derive(Clone, Copy)] +struct Seed { + depth: usize, +} + +impl Seed { + fn child(self) -> Result { + if self.depth >= MAX_DEPTH { + return Err(E::custom(Error::TooDeep)); + } + Ok(Self { + depth: self.depth + 1, + }) + } +} + +impl<'de> DeserializeSeed<'de> for Seed { + type Value = Value; + + fn deserialize>(self, deserializer: D) -> Result { + deserializer.deserialize_any(self) + } +} + +impl<'de> Visitor<'de> for Seed { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a plain Python data value") + } + + fn visit_unit(self) -> Result { + Ok(Value::None) + } + + fn visit_bool(self, value: bool) -> Result { + Ok(Value::Bool(value)) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(Value::Int(value.into())) + } + + fn visit_u64(self, value: u64) -> Result { + Ok(Value::Int(value.into())) + } + + fn visit_f64(self, value: f64) -> Result { + Ok(Value::Float(value)) + } + + fn visit_str(self, value: &str) -> Result { + Ok(Value::Str(value.to_owned())) + } + + fn visit_string(self, value: String) -> Result { + Ok(Value::Str(value)) + } + + fn visit_bytes(self, value: &[u8]) -> Result { + Ok(Value::Bytes(value.to_vec())) + } + + fn visit_byte_buf(self, value: Vec) -> Result { + Ok(Value::Bytes(value)) + } + + fn visit_seq>(self, mut seq: A) -> Result { + let child = self.child()?; + let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0).min(4096)); + while let Some(value) = seq.next_element_seed(child)? { + values.push(value); + } + Ok(Value::List(values)) + } + + fn visit_map>(self, mut map: A) -> Result { + let child = self.child()?; + let mut entries = Vec::with_capacity(map.size_hint().unwrap_or(0).min(4096)); + while let Some(key) = map.next_key_seed(child)? { + entries.push((key, map.next_value_seed(child)?)); + } + Ok(Value::Dict(entries)) + } +} diff --git a/litellm-rust/crates/python-compat/src/repr.rs b/litellm-rust/crates/python-compat/src/repr.rs new file mode 100644 index 00000000000..1cf20d87716 --- /dev/null +++ b/litellm-rust/crates/python-compat/src/repr.rs @@ -0,0 +1,237 @@ +//! `repr(value)` and `str(value)` byte for byte. +//! +//! LiteLLM hashes `str(value)` into cache keys and writes `str(dict)` into Redis, so these +//! strings are persisted identifiers rather than display text: every quote choice, escape, +//! and float digit must match CPython. + +use std::fmt::Write; + +use crate::Value; + +/// Generated by `scripts/generate_nonprintable.py`; see `generated/nonprintable.rs`. +mod nonprintable { + include!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/generated/nonprintable.rs" + )); +} + +/// Unicode version of the printable-character table, from the Python that generated it. +pub const UNICODE_VERSION: &str = nonprintable::UNICODE_VERSION; + +/// `repr(value)`. +pub fn repr(value: &Value) -> String { + let mut out = String::new(); + write_repr(&mut out, value); + out +} + +/// `str(value)`: a string's own contents, and `repr` for every other value. +pub fn to_str(value: &Value) -> String { + match value { + Value::Str(text) => text.clone(), + value => repr(value), + } +} + +/// `repr(float)`, the shortest round-trip form with CPython's exponent thresholds. +pub fn float_repr(value: f64) -> String { + format_float(value, true) +} + +fn write_repr(out: &mut String, value: &Value) { + match value { + Value::None => out.push_str("None"), + Value::Bool(true) => out.push_str("True"), + Value::Bool(false) => out.push_str("False"), + Value::Int(value) => { + let _ = write!(out, "{value}"); + } + Value::Float(value) => out.push_str(&float_repr(*value)), + Value::Complex { re, im } => write_complex(out, *re, *im), + Value::Str(text) => write_str(out, text), + Value::Bytes(bytes) => write_bytes(out, bytes), + Value::List(values) => write_sequence(out, '[', values, ']'), + Value::Tuple(values) if values.len() == 1 => { + out.push('('); + write_repr(out, &values[0]); + out.push_str(",)"); + } + Value::Tuple(values) => write_sequence(out, '(', values, ')'), + Value::Set(values) if values.is_empty() => out.push_str("set()"), + Value::Set(values) => write_sequence(out, '{', values, '}'), + Value::Dict(entries) => { + out.push('{'); + for (index, (key, value)) in entries.iter().enumerate() { + if index > 0 { + out.push_str(", "); + } + write_repr(out, key); + out.push_str(": "); + write_repr(out, value); + } + out.push('}'); + } + } +} + +fn write_sequence(out: &mut String, open: char, values: &[Value], close: char) { + out.push(open); + for (index, value) in values.iter().enumerate() { + if index > 0 { + out.push_str(", "); + } + write_repr(out, value); + } + out.push(close); +} + +/// `complex.__repr__`: a `+0.0` real part prints only the imaginary part, without parens. +fn write_complex(out: &mut String, re: f64, im: f64) { + if re == 0.0 && re.is_sign_positive() { + out.push_str(&format_float(im, false)); + out.push('j'); + return; + } + out.push('('); + out.push_str(&format_float(re, false)); + let im_text = format_float(im, false); + if !im_text.starts_with('-') { + out.push('+'); + } + out.push_str(&im_text); + out.push_str("j)"); +} + +/// `PyOS_double_to_string(value, 'r', 0, flags)`: scientific notation below 1e-4 and from +/// 1e16 up, with a signed exponent of at least two digits. `add_dot_0` is +/// `Py_DTSF_ADD_DOT_0`, which `float` sets and `complex` does not. +fn format_float(value: f64, add_dot_0: bool) -> String { + if value.is_nan() { + return "nan".to_owned(); + } + if value.is_infinite() { + return if value > 0.0 { "inf" } else { "-inf" }.to_owned(); + } + // Rust's `{:e}` prints the shortest round-trip digits, like CPython's 'r' mode. + let scientific = format!("{value:e}"); + let (mantissa, exponent) = scientific + .split_once('e') + .expect("`{:e}` always prints an exponent"); + let exponent: i32 = exponent.parse().expect("`{:e}` exponent is an integer"); + let (sign, mantissa) = match mantissa.strip_prefix('-') { + Some(mantissa) => ("-", mantissa), + None => ("", mantissa), + }; + let digits: String = mantissa.chars().filter(|ch| *ch != '.').collect(); + + let mut out = String::from(sign); + if !(-4..16).contains(&exponent) { + out.push_str(&digits[..1]); + if digits.len() > 1 { + out.push('.'); + out.push_str(&digits[1..]); + } + let _ = write!( + out, + "e{}{:02}", + if exponent < 0 { '-' } else { '+' }, + exponent.unsigned_abs() + ); + } else if exponent < 0 { + out.push_str("0."); + out.extend(std::iter::repeat_n('0', (-exponent - 1) as usize)); + out.push_str(&digits); + } else { + let integer_digits = exponent as usize + 1; + if digits.len() > integer_digits { + out.push_str(&digits[..integer_digits]); + out.push('.'); + out.push_str(&digits[integer_digits..]); + } else { + out.push_str(&digits); + out.extend(std::iter::repeat_n('0', integer_digits - digits.len())); + if add_dot_0 { + out.push_str(".0"); + } + } + } + out +} + +/// `unicode_repr`: single quotes unless the text has a `'` and no `"`. Printable non-ASCII +/// stays literal; everything `str.isprintable()` rejects is escaped. +fn write_str(out: &mut String, text: &str) { + let quote = if text.contains('\'') && !text.contains('"') { + '"' + } else { + '\'' + }; + out.push(quote); + for ch in text.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\t' => out.push_str("\\t"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + ch if ch == quote => { + out.push('\\'); + out.push(ch); + } + ch if is_printable(ch) => out.push(ch), + ch => { + let code = ch as u32; + let _ = match code { + 0..=0xff => write!(out, "\\x{code:02x}"), + 0x100..=0xffff => write!(out, "\\u{code:04x}"), + _ => write!(out, "\\U{code:08x}"), + }; + } + } + } + out.push(quote); +} + +/// `bytes.__repr__`: the same quote rule as `str`, with every byte outside printable ASCII +/// escaped as `\xhh`. +fn write_bytes(out: &mut String, bytes: &[u8]) { + let quote = if bytes.contains(&b'\'') && !bytes.contains(&b'"') { + b'"' + } else { + b'\'' + }; + out.push('b'); + out.push(quote as char); + for &byte in bytes { + match byte { + b'\\' => out.push_str("\\\\"), + b'\t' => out.push_str("\\t"), + b'\n' => out.push_str("\\n"), + b'\r' => out.push_str("\\r"), + byte if byte == quote => { + out.push('\\'); + out.push(byte as char); + } + 0x20..=0x7e => out.push(byte as char), + byte => { + let _ = write!(out, "\\x{byte:02x}"); + } + } + } + out.push(quote as char); +} + +fn is_printable(ch: char) -> bool { + let code = ch as u32; + nonprintable::NONPRINTABLE + .binary_search_by(|&(low, high)| { + if high < code { + std::cmp::Ordering::Less + } else if low > code { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Equal + } + }) + .is_err() +} diff --git a/litellm-rust/crates/python-compat/src/truthy.rs b/litellm-rust/crates/python-compat/src/truthy.rs new file mode 100644 index 00000000000..af747202572 --- /dev/null +++ b/litellm-rust/crates/python-compat/src/truthy.rs @@ -0,0 +1,18 @@ +use num_bigint::Sign; + +use crate::Value; + +/// `bool(value)` for plain data: `None`, `False`, zero, and empty containers are false. +pub fn truthy(value: &Value) -> bool { + match value { + Value::None => false, + Value::Bool(value) => *value, + Value::Int(value) => value.sign() != Sign::NoSign, + Value::Float(value) => *value != 0.0, + Value::Complex { re, im } => *re != 0.0 || *im != 0.0, + Value::Str(value) => !value.is_empty(), + Value::Bytes(value) => !value.is_empty(), + Value::Tuple(values) | Value::List(values) | Value::Set(values) => !values.is_empty(), + Value::Dict(entries) => !entries.is_empty(), + } +} diff --git a/litellm-rust/crates/python-compat/src/value.rs b/litellm-rust/crates/python-compat/src/value.rs new file mode 100644 index 00000000000..b1397190638 --- /dev/null +++ b/litellm-rust/crates/python-compat/src/value.rs @@ -0,0 +1,53 @@ +use num_bigint::BigInt; + +/// A Python value built only from literals: what `ast.literal_eval` can return. +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + None, + Bool(bool), + Int(BigInt), + Float(f64), + Complex { + re: f64, + im: f64, + }, + Str(String), + Bytes(Vec), + Tuple(Vec), + List(Vec), + /// Insertion-ordered, with Python's key equality already applied. + Dict(Vec<(Value, Value)>), + /// Literal order, with Python's member equality already applied. + Set(Vec), +} + +impl Value { + /// Python's type name, as it appears in `TypeError` messages. + pub fn type_name(&self) -> &'static str { + match self { + Value::None => "NoneType", + Value::Bool(_) => "bool", + Value::Int(_) => "int", + Value::Float(_) => "float", + Value::Complex { .. } => "complex", + Value::Str(_) => "str", + Value::Bytes(_) => "bytes", + Value::Tuple(_) => "tuple", + Value::List(_) => "list", + Value::Dict(_) => "dict", + Value::Set(_) => "set", + } + } +} + +impl From for Value { + fn from(value: i64) -> Self { + Value::Int(value.into()) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Value::Str(value.to_owned()) + } +} diff --git a/litellm-rust/crates/python-compat/tests/fixtures.rs b/litellm-rust/crates/python-compat/tests/fixtures.rs new file mode 100644 index 00000000000..9c202fdc5f1 --- /dev/null +++ b/litellm-rust/crates/python-compat/tests/fixtures.rs @@ -0,0 +1,343 @@ +//! Replays `generated/values.json`, which CPython wrote with `scripts/generate_fixtures.py`. + +use std::{collections::BTreeMap, fs::File, io::Write}; + +use litellm_python_compat::{ + Value, json, + literal::literal_eval, + pickle, + repr::{repr, to_str}, + truthy::truthy, +}; +use rstest::{fixture, rstest}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Fixtures { + rows: Vec, + sources: Vec, +} + +/// `ast.literal_eval(source)`: the `repr` of its result, or the exception class it raised. +#[derive(Deserialize)] +struct Source { + name: String, + source: String, + repr: Option, + error: Option, +} + +#[derive(Deserialize)] +struct Row { + name: String, + source: String, + literal: bool, + plain: bool, + repr: String, + str: String, + truthy: bool, + json: Option, + json_error: Option, + pickle: Option>, + view: Option, +} + +/// Parsed once for the whole test binary. +#[fixture] +#[once] +fn fixtures() -> Fixtures { + serde_json::from_str(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/generated/values.json" + ))) + .expect("values.json matches the fixture schema") +} + +/// Accepted differences from CPython: `(fixture source, check prefix, reason)`. Each entry +/// must still differ, so a dependency fix that removes one fails the test until it is deleted. +const KNOWN: &[(&str, &str, &str)] = &[ + ( + "...", + "literal_eval source", + "`Ellipsis` is not part of the data model", + ), + ( + r"'\ud800'", + "literal_eval source", + "a Rust `String` cannot hold a lone surrogate", + ), + ( + r"'\N{BULLET}'", + "literal_eval source", + "`\\N{NAME}` needs the Unicode name table", + ), + ( + "nested_150", + "literal_eval", + "deeper than MAX_DEPTH: rejected for stack safety, where CPython still parses it", + ), + ( + "nested_150", + "pickle.loads", + "deeper than MAX_DEPTH: rejected for stack safety, where CPython has no limit", + ), + ( + "2**64", + "pickle.loads", + "serde-pickle's serde interface stops at i64", + ), + ( + "-(2**70)", + "pickle.loads", + "serde-pickle's serde interface stops at i64", + ), + ( + "2**64", + "pickle.dumps", + "serde-pickle's serde interface stops at i64", + ), + ( + "-(2**70)", + "pickle.dumps", + "serde-pickle's serde interface stops at i64", + ), + ( + "2**64", + "to_json", + "serde_json has no exact form for integers beyond u64", + ), + ( + "-(2**70)", + "to_json", + "serde_json has no exact form for integers beyond i64", + ), + ( + "b''", + "pickle.loads protocol 0", + "protocols 0-2 pickle `b''` as a `bytes()` call", + ), + ( + "b''", + "pickle.loads protocol 1", + "protocols 0-2 pickle `b''` as a `bytes()` call", + ), + ( + "b''", + "pickle.loads protocol 2", + "protocols 0-2 pickle `b''` as a `bytes()` call", + ), +]; + +/// Collects every mismatch so one run reports the whole divergence set. +#[derive(Default)] +struct Mismatches { + unexpected: Vec, + known_seen: Vec, + known_scope: Vec, +} + +impl Mismatches { + fn known(source: &str, what: &str) -> Option { + KNOWN + .iter() + .position(|(known, prefix, _)| *known == source && what.starts_with(prefix)) + } + + fn check(&mut self, row: &Row, what: &str, expected: &str, actual: &str) { + self.check_source(&row.name, what, expected, actual); + } + + /// `name` identifies the fixture row in reports and in [`KNOWN`]. + fn check_source(&mut self, name: &str, what: &str, expected: &str, actual: &str) { + let known = Self::known(name, what); + if let Some(index) = known { + self.known_scope.push(index); + } + if expected == actual { + return; + } + match known { + Some(index) => self.known_seen.push(index), + None => self.unexpected.push(format!( + "{name:?} [{what}]\n python: {expected}\n rust: {actual}" + )), + } + } + + fn finish(self) { + let resolved: Vec<_> = self + .known_scope + .iter() + .filter(|index| !self.known_seen.contains(index)) + .map(|&index| format!("{} [{}]", KNOWN[index].0, KNOWN[index].1)) + .collect(); + assert!( + self.unexpected.is_empty() && resolved.is_empty(), + "{} mismatches with CPython:\n{}\nknown divergences that now match (delete them \ + from KNOWN): {resolved:?}", + self.unexpected.len(), + self.unexpected.join("\n"), + ); + } +} + +fn check_value(mismatches: &mut Mismatches, row: &Row, value: &Value) { + mismatches.check(row, "repr", &row.repr, &repr(value)); + mismatches.check(row, "str", &row.str, &to_str(value)); + mismatches.check( + row, + "bool", + &row.truthy.to_string(), + &truthy(value).to_string(), + ); + let expected = row.json.clone().or_else(|| { + row.json_error + .clone() + .map(|error| format!("error: {error}")) + }); + let actual = match json::dumps(value) { + Ok(text) => text, + Err(error) => format!("error: {error}"), + }; + mismatches.check( + row, + "json.dumps", + expected.as_deref().unwrap_or(""), + &actual, + ); +} + +#[rstest] +fn literal_rows_match_python_repr_str_bool_and_json(fixtures: &Fixtures) { + let mut mismatches = Mismatches::default(); + for row in fixtures.rows.iter().filter(|row| row.literal) { + match literal_eval(&row.repr) { + Ok(value) => check_value(&mut mismatches, row, &value), + Err(error) => mismatches.check(row, "literal_eval", &row.repr, &error.to_string()), + } + } + mismatches.finish(); +} + +/// Errors compare by outcome only: CPython's exception class is not part of the contract. +#[rstest] +fn literal_eval_matches_python_on_source_texts(fixtures: &Fixtures) { + let mut mismatches = Mismatches::default(); + for case in &fixtures.sources { + let expected = match (&case.repr, &case.error) { + (Some(repr), None) => repr.clone(), + (None, Some(_)) => "an error".to_owned(), + _ => panic!("{:?}: a source records a repr or an error", case.source), + }; + let actual = match literal_eval(&case.source) { + Ok(value) => repr(&value), + Err(_) => "an error".to_owned(), + }; + mismatches.check_source(&case.name, "literal_eval source", &expected, &actual); + } + mismatches.finish(); +} + +#[rstest] +fn pickle_loads_matches_python_at_every_protocol(fixtures: &Fixtures) { + let mut mismatches = Mismatches::default(); + for row in &fixtures.rows { + let Some(pickles) = &row.pickle else { continue }; + for (protocol, data) in pickles { + let data = hex::decode(data).expect("fixture pickle is hex"); + let what = format!("pickle.loads protocol {protocol}"); + match (pickle::loads(&data), row.plain) { + (Ok(value), true) => { + let view = row.view.as_deref().expect("picklable rows have a view"); + mismatches.check(row, &what, view, &repr(&value)); + } + (Err(error), true) => { + mismatches.check(row, &what, "a value", &format!("error: {error}")) + } + (Ok(value), false) => { + mismatches.check(row, &what, "a class-reference error", &repr(&value)) + } + (Err(pickle_error), false) => assert!( + matches!(pickle_error, litellm_python_compat::Error::InvalidPickle(_)), + "{}: {pickle_error}", + row.source + ), + } + } + } + mismatches.finish(); +} + +/// Non-finite floats have no literal form; pickle is how Rust receives them. +#[rstest] +fn values_reached_only_through_pickle_match_python(fixtures: &Fixtures) { + let mut mismatches = Mismatches::default(); + for row in fixtures + .rows + .iter() + .filter(|row| !row.literal && row.plain && row.view.as_deref() == Some(&row.repr)) + { + let data = hex::decode(&row.pickle.as_ref().expect("plain rows pickle")["5"]) + .expect("fixture pickle is hex"); + let value = pickle::loads(&data).expect("plain pickle decodes"); + check_value(&mut mismatches, row, &value); + } + mismatches.finish(); +} + +/// Byte equality with CPython is not the contract: CPython adds memo opcodes and picks the +/// smallest integer opcode. `scripts/verify_rust_pickles.py` checks that CPython reads these +/// back; set `PYTHON_COMPAT_RUST_PICKLES` to a file path to export them. +#[rstest] +fn pickle_dumps_round_trips_every_plain_literal(fixtures: &Fixtures) { + // Truncate up front: the verifier must read this run's rows and nothing else. + let mut export = std::env::var_os("PYTHON_COMPAT_RUST_PICKLES") + .map(|path| File::create(path).expect("the export path is writable")); + let mut mismatches = Mismatches::default(); + for row in fixtures + .rows + .iter() + .filter(|row| row.literal && row.plain && row.view.as_deref() == Some(&row.repr)) + { + // Rows past `MAX_DEPTH` are covered by the literal test's own KNOWN entry. + let Ok(value) = literal_eval(&row.repr) else { + continue; + }; + let data = match pickle::dumps(&value) { + Ok(data) => data, + Err(error) => { + mismatches.check(row, "pickle.dumps", "a pickle", &format!("error: {error}")); + continue; + } + }; + let decoded = pickle::loads(&data).expect("rust pickle decodes"); + mismatches.check(row, "pickle round trip", &row.repr, &repr(&decoded)); + if let Some(file) = &mut export { + writeln!(file, "{}\t{}", hex::encode(&data), repr(&value)) + .expect("the export file is writable"); + } + } + mismatches.finish(); +} + +#[rstest] +fn to_json_matches_python_json_round_trip(fixtures: &Fixtures) { + let mut mismatches = Mismatches::default(); + for row in fixtures.rows.iter().filter(|row| row.literal) { + let Some(expected) = &row.json else { continue }; + let Ok(value) = literal_eval(&row.repr) else { + continue; + }; + let expected: serde_json::Value = + serde_json::from_str(expected).expect("python json.dumps output parses"); + match json::to_json(&value) { + Ok(actual) => { + mismatches.check(row, "to_json", &expected.to_string(), &actual.to_string()) + } + Err(error) => { + mismatches.check(row, "to_json", &expected.to_string(), &error.to_string()) + } + } + } + mismatches.finish(); +} diff --git a/litellm-rust/crates/python-compat/tests/limits.rs b/litellm-rust/crates/python-compat/tests/limits.rs new file mode 100644 index 00000000000..c4d931034c4 --- /dev/null +++ b/litellm-rust/crates/python-compat/tests/limits.rs @@ -0,0 +1,122 @@ +use std::time::{Duration, Instant}; + +use litellm_python_compat::{Error, MAX_DEPTH, Value, json, literal::literal_eval, pickle}; +use rstest::{fixture, rstest}; + +/// The bracket pair of one container shape, as `(open, close)`. +#[fixture] +fn shapes() -> [(&'static str, &'static str); 3] { + [("[", "]"), ("{'a': ", "}"), ("(", ",)")] +} + +fn nested_text(open: &str, close: &str, depth: usize) -> String { + format!("{}1{}", open.repeat(depth), close.repeat(depth)) +} + +fn nested_list(depth: usize) -> Value { + (0..depth).fold(Value::from(1), |value, _| Value::List(vec![value])) +} + +/// A protocol 3 pickle of `depth` nested lists around `1`: `EMPTY_LIST` per level, then +/// `BININT1 1`, then `APPEND` per level. Written by hand because `dumps` refuses the depth. +fn nested_list_pickle(depth: usize) -> Vec { + let mut data = vec![0x80, 3]; + data.extend(std::iter::repeat_n(b']', depth)); + data.extend([b'K', 1]); + data.extend(std::iter::repeat_n(b'a', depth)); + data.push(b'.'); + data +} + +#[rstest] +fn literal_eval_accepts_the_limit_and_rejects_past_it(shapes: [(&'static str, &'static str); 3]) { + for (open, close) in shapes { + assert!(literal_eval(&nested_text(open, close, MAX_DEPTH)).is_ok()); + assert!(matches!( + literal_eval(&nested_text(open, close, MAX_DEPTH + 1)), + Err(Error::TooDeep) + )); + } +} + +/// A backtracking parser (the `py_literal` grammar this replaced) doubles per nested level +/// and takes minutes here; the bound is loose enough to survive a slow debug build. +#[rstest] +fn literal_eval_stays_linear_in_depth(shapes: [(&'static str, &'static str); 3]) { + for (open, close) in shapes { + let text = nested_text(open, close, MAX_DEPTH); + let start = Instant::now(); + assert!(literal_eval(&text).is_ok()); + let elapsed = start.elapsed(); + assert!( + elapsed < Duration::from_millis(50), + "{open} nested {MAX_DEPTH} deep took {elapsed:?}" + ); + } +} + +#[rstest] +fn literal_eval_ignores_brackets_inside_strings() { + let text = format!("'{}'", "[".repeat(MAX_DEPTH + 1)); + assert!(matches!(literal_eval(&text), Ok(Value::Str(_)))); +} + +#[rstest] +fn pickle_nesting_is_bounded_in_both_directions() { + assert_eq!( + pickle::loads(&nested_list_pickle(MAX_DEPTH)).unwrap(), + nested_list(MAX_DEPTH) + ); + assert!(matches!( + pickle::loads(&nested_list_pickle(MAX_DEPTH + 1)), + Err(Error::InvalidPickle(_)) + )); + assert!(pickle::dumps(&nested_list(MAX_DEPTH)).is_ok()); + assert!(matches!( + pickle::dumps(&nested_list(MAX_DEPTH + 2)), + Err(Error::TooDeep) + )); +} + +#[rstest] +#[case("{1, 2}", "set")] +#[case("1+2j", "complex")] +fn pickle_dumps_refuses_types_it_would_change(#[case] source: &str, #[case] type_name: &str) { + let value = literal_eval(source).expect("source is a literal"); + assert!(matches!(pickle::dumps(&value), Err(Error::NotPicklable(name)) if name == type_name)); +} + +#[rstest] +#[case(b"\x80\x02c__builtin__\ncomplex\nq\x00.".to_vec(), "a class reference")] +#[case({ let mut data = pickle::dumps(&Value::from(1)).unwrap(); data.push(b'.'); data }, "trailing data")] +fn pickle_loads_rejects(#[case] data: Vec, #[case] what: &str) { + assert!( + matches!(pickle::loads(&data), Err(Error::InvalidPickle(_))), + "{what} must not decode" + ); +} + +#[rstest] +#[case(Value::Bytes(b"x".to_vec()), "Object of type bytes is not JSON serializable")] +#[case(Value::Set(vec![Value::from(1)]), "Object of type set is not JSON serializable")] +#[case(Value::Complex { re: 1.0, im: 2.0 }, "Object of type complex is not JSON serializable")] +#[case( + Value::Float(f64::NAN), + "Out of range float values are not JSON compliant" +)] +fn to_json_reports_what_python_json_dumps_would_reject( + #[case] value: Value, + #[case] message: &str, +) { + let error = json::to_json(&value).expect_err("value has no serde_json form"); + assert_eq!(error.to_string(), message); +} + +/// `json.dumps` writes the non-finite floats that `to_json` cannot represent. +#[rstest] +#[case(f64::NAN, "NaN")] +#[case(f64::INFINITY, "Infinity")] +#[case(f64::NEG_INFINITY, "-Infinity")] +fn json_dumps_writes_non_finite_floats(#[case] value: f64, #[case] text: &str) { + assert_eq!(json::dumps(&Value::Float(value)).unwrap(), text); +}