litellm/litellm-rust/crates/python-compat/scripts/generate_nonprintable.py
devin-ai-integration[bot] 05d7fb24bd
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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:16:56 -07:00

35 lines
1.2 KiB
Python

"""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")