Skip to main content

tors

Fast, GIL-free text and document operations for Python, backed by Rust: normalization, Unicode segmentation, diffing, fuzzy and phonetic matching, multi-pattern search and redaction, chunking, and lightweight retrieval (TF-IDF, BM25, SimHash, Merkle integrity). An optional extra adds cross-format document extraction (PDF, Office, RTF/ODF/EPUB, CSV, HTML to markdown or plain text). In the spirit of orjson for JSON or polars for dataframes.

Full documentation lives at azx-pbc-oss.github.io/tors and in docs/ in this repo: the API reference, Documents, Performance, Async use, Design and scope, Dependencies and licensing, and three worked recipes.

Why

Python's re module and str methods never release the GIL, no matter how large the input is: a multi-megabyte text transform runs as one long GIL-held call that stalls every other thread and the asyncio event loop for its whole duration. tors does the same kind of transform as a single native Rust pass, wrapped in py.detach (PyO3's GIL-release call) for the entire computation, so the GIL is free for the rest of your program while it runs.

That covers the functions with a stdlib equivalent (normalize's pipeline mirrors unicodedata.normalize + a few re.sub calls; quote/unquote mirror urllib.parse; b64_encode_bytes/b64_decode mirror base64; the one-shot hashing family mirrors hashlib/hmac; the random-generation family mirrors secrets/uuid). Where the stdlib has no equivalent at all (Unicode text segmentation: grapheme clusters, word and sentence boundaries; leftmost-longest multi-pattern search; edit-distance and phonetic matching; content-defined chunking; SimHash near-duplicate detection; Merkle tree integrity; encoding detection), tors supplies it over maintained Rust crates, GIL-released the same way, so async services and threaded pipelines don't pay a blocking tax for text work.

Install

pip install tors

Building from source (a Rust toolchain and maturin):

pip install maturin
maturin develop --release

The document-extraction surface is a second wheel behind an extra: pip install "tors[documents]". From a checkout, uv sync --locked --extra documents builds and installs the payload wheel from this tree. See Documents.

The underlying Rust crate is also on crates.io, published separately as tors-core (the plain tors name belongs to an unrelated, dormant crate). cargo add tors-core, then use tors::... in code: [lib] name in Cargo.toml keeps the importable crate name tors regardless of the published package name.

Pyodide / WebAssembly

The Rust cores are OS-free, so tors also builds for Pyodide as a PEP 783 pyemscripten wheel: the abi3-py310 story carries over unchanged, so one wheel covers every Pyodide Python >= 3.10. The WASM workflow builds it on every push (artifact only; publishing to PyPI's Emscripten platform is not wired up). To build one locally:

rustup target add wasm32-unknown-emscripten
uvx --python 3.14 --from pyodide-build pyodide build . -o dist

The driving interpreter matters: Python 3.14 targets Pyodide 314.x / pyemscripten_2026_0 with a stable Rust toolchain; driving from 3.13 pins a Rust nightly older than this crate's MSRV. SIMD-dependent dependencies (base64, simdutf8, memchr, aho-corasick) compile their scalar fallbacks for wasm; the extension has been smoke-tested (import + representative calls across every core family) in Pyodide under node.

Quickstart

import tors

tors.normalize("line one  \n\n\n\nline two\r\n")
# 'line one\n\nline two'

tors.finalize("line one  \n\n\n\nline two\r\n")
# ('line one\n\nline two', 'e986ba083c7c1a9361143d2d8ccd8477d1d5eeef8b94b67c6ad4693f8f7b942a')

tors.diff_opcodes_lines("l1\nl2\nl3\n", "l1\nX\nl3\nl4\n")
# [("equal", 0, 1, 0, 1), ("replace", 1, 2, 1, 2), ("equal", 2, 3, 2, 3),
#  ("insert", 3, 3, 3, 4)]
thread = (
    "Ana: kickoff at nine.\n"
    "Ben: We briefed the U.S. team on the numbers. They asked for a follow-up.\n"
    "Ana: done."
)
chunks = tors.chunk_hierarchical(thread, 40, ["\n", None])
# [(0, 21), (22, 59), (59, 95), (96, 106)]

[thread[s:e] for s, e in chunks]
# ['Ana: kickoff at nine.',
#  'Ben: We briefed the U.S. team on the ',
#  'numbers. They asked for a follow-up.',
#  'Ana: done.']
corpus = [
    "the quick brown fox jumps over the lazy dog",
    "a lazy cat sleeps all day",
    "the fox and the dog are friends",
]
tors.bm25_rank("quick fox", corpus)
# [(0, 1.3162195220480066), (2, 0.4798180901812613), (1, 0.0)]

All of these run against the built extension; the output above is what they actually return.

What's inside

105 functions plus two small helper classes and six pinned constants (five charset alphabets and the key-family tuple), grouped by what they do; the documents extra adds seven document-extraction functions and its own helper types. Full signatures, argument contracts, and edge cases are in the API reference.

family functions
Unicode normalization & forms normalize, finalize, nfc/nfd/nfkc/nfkd, html_unescape, strip_controls
Contact & credential scrub scrub_pii(+_report), KEY_FAMILIES
UTF-8 / UTF-16 / base64 codecs decode_utf8, finalize_utf8, utf8_is_valid, decode_utf16, utf16_is_valid, b64_encode_bytes, b64_decode, detect_encoding
Hashing & request signing md5_hex, sha1_hex, sha256_hex, sha512_hex, hmac_sha256_hex + raw-digest _digest twins (md5/sha1: checksum/legacy-interop only, broken for security since the 2000s — never signatures, certificates, or passwords)
Random generation (keys, tokens, ids) random_string, random_hex, random_b62, random_b64url, uuid4(+_bytes), uuid7(+_bytes)
Text segmentation (UAX #29) grapheme_count, word_bounds(+_iter), word_count, sentence_bounds(+_iter), sentence_count
Diffing (difflib-compatible) diff_opcodes, diff_opcodes_lines
Fuzzy & phonetic matching similarity_ratio, get_close_matches, levenshtein, jaro, jaro_winkler, soundex, metaphone, double_metaphone, nysiis, daitch_mokotoff, refined_soundex
Multi-pattern search & redaction find_patterns(+_iter), count_matches, replace_many, replace_many_masked, scrub_log_text, CompiledPatterns
Escape-parity byte scan contains_unescaped, find_unescaped
Byte lengths without the encode copy utf8_byte_len, utf16_byte_len
Batch charset validation first_invalid_charset, first_invalid_offender (the same scan's offender detail — item index, codepoint position, character — for rejection messages), CHARSET_B62/_B64URL/_HEX_LOWER/_HEX_UPPER/_HEX_MIXED (pinned alphabets that pair with the validator as data)
Markdown / code-fence extraction extract_code_blocks, strip_code_fences, dedent
JSON repair (json_repair port) repair_json, repair_json_loads, repair_json_diagnostics
Truncation & lexical grounding truncate_to_bounds, truncate_ellipsis, is_grounded
URL encoding quote, quote_plus, unquote, unquote_plus
Text chunking chunk_cdc, chunk_text(+_iter), chunk_by_words/_sentences/_paragraphs/_lines(+_iter), chunk_hierarchical
Information retrieval & integrity tf_idf, bm25_rank, simhash64, simhash128, minhash_signature, merkle_root, merkle_diff, content_hash
UUIDv7 field operations uuid7_timestamp_ms, uuid_version, uuid_parse
Text-processing pipelines apply_pipeline, CompiledLemmaDict
Document-format extraction (tors.documents) to_markdown, to_text, sniff, pdf_extract, pdf_page_count, pdf_classify, pdf_link_uris

Highlights

  • GIL release on every call. 12 MiB of prose through tors.finalize in a background thread holds the event loop's worst heartbeat gap to 10-14 ms; the pure-Python equivalent holds it for 92-108 ms. Full measured tables: Performance.
  • Async where it matters. tors.aio wraps exactly the large-input functions in asyncio.to_thread, unconditionally, with no size-based branch; everything else keeps one sync spelling. Details: Async use.
  • Stateless by design. No pipeline objects, no caches, no handles to manage; the build-once CompiledLemmaDict and CompiledPatterns handles are the measured exceptions, and the scope cuts (no regex engine, no bundled lemma data, no search index) are decisions, not oversights. Details: Design and scope.
  • Permissive-only dependency tree, machine-checked by cargo deny on every push. The full table and license accounting: Dependencies and licensing.

Contributing

See CONTRIBUTING.md for setup, the pre-PR checklist (tests, clippy, fmt, ruff, the license gate), fuzzing, release process, and commit-message conventions. For security vulnerabilities, see SECURITY.md instead of filing a public issue.

Release files for tors 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tors 0.7.0
File Size Uploaded
tors-0.7.0.tar.gz 1.9 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for tors 0.7.0
File
tors-0.7.0-cp310-abi3-win_arm64.whl CPython 3.10 abi3 Windows ARM64 Details
tors-0.7.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
tors-0.7.0-cp310-abi3-musllinux_1_2_x86_64.whl CPython 3.10 abi3 Linux musl 1.2+ x86-64 Details
tors-0.7.0-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
tors-0.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
tors-0.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
tors-0.7.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
tors-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 29.2 MB

Release files / tors-0.7.0.tar.gz

Download URL tors-0.7.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
e3946e9297d4c005b00ed1ff272cd2f03bab265d1e0fb01112b50d5b2771adf6
BLAKE2b-256 checksum
How to use checksums
c33ad649a3821bb4c3ca8989bfc32164a12874e819b15bac313768c5d5bc6be0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-win_arm64.whl

Download URL tors-0.7.0-cp310-abi3-win_arm64.whl
Size 2.9 MB
Tags CPython 3.10 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
fa501ab71780aaf1f88d8046a8cc0215ea82e4ff00ec9e086ee2fab30b7330fe
BLAKE2b-256 checksum
How to use checksums
f9d4cc407bdb321bfce9a829980f99203ccdb957d928eefb522a8d2322c4e0bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-win_amd64.whl

Download URL tors-0.7.0-cp310-abi3-win_amd64.whl
Size 3.1 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
bf43c613e937f0184f5c237cb8fcca410b7a4217fa318bc57a92b08ae3a39160
BLAKE2b-256 checksum
How to use checksums
a9dbaca28b484b57fc559d7507b9cba1bd7144a5d2fb4ca42bbd0b5b8a9fb7c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-musllinux_1_2_x86_64.whl

Download URL tors-0.7.0-cp310-abi3-musllinux_1_2_x86_64.whl
Size 3.9 MB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
379187f0c126fc7d30b8537314bb8a8451482b8e899126a6032219304a8f23ec
BLAKE2b-256 checksum
How to use checksums
0c129ff66a5a025e9c6102ac6ceb4457502a9cf868b2703759cb570aefa940bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-musllinux_1_2_aarch64.whl

Download URL tors-0.7.0-cp310-abi3-musllinux_1_2_aarch64.whl
Size 3.7 MB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
b715d75103c2f9323504e6f7e20bd537de9e8c7943e511539311b468183c8dba
BLAKE2b-256 checksum
How to use checksums
23c813ef4e66e2f947796bea781475d4ef89e068e8c0e387b8d5ffd338b5213b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL tors-0.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.6 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
7a094f79fed02bc1cbb70775cb417ec3e426ae682094c774bb71fc9d37021a82
BLAKE2b-256 checksum
How to use checksums
869ff7d338e9ce8296c5fda90a6465f540869e03fb21802998ee86d6a59752bc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL tors-0.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.5 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
835ea6fae5b040fdcffeff175f332da5c433fd4ed37cd840afac45f9a89beb15
BLAKE2b-256 checksum
How to use checksums
043e0daad3806a879a77dc3b0ce05bd726a8d2d0c35e0977860e0ca57452a9e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL tors-0.7.0-cp310-abi3-macosx_11_0_arm64.whl
Size 3.1 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
fc99ee3fc2686a7133d2941a5dfa86b365e28713b5f1cf6aee9e667f13392707
BLAKE2b-256 checksum
How to use checksums
c76f7da1acd198703ff5889757444d20f2be53e6609d5222198e480e77cd8e58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tors-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL tors-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 3.3 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
7257f5205942351ea16998c924e40e11d38d2034eacefbf9a6efc39f25bcc794
BLAKE2b-256 checksum
How to use checksums
a3a239e0e66746db509731fb74b076295826027427a131502bd4188d0f7125c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.11.0

9 release files

0.10.1

9 release files

0.10.0

9 release files

This release

0.7.0 This release

9 release files

0.6.1

9 release files

0.6.0

9 release files

0.5.0

9 release files

0.4.1

9 release files

0.4.0

9 release files

0.3.1

9 release files

0.3.0

9 release files

0.2.0

9 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page