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.

GIL-release is not the same as free-threaded-build support: tors wheels target the standard CPython ABI (cp310-abi3), which a free-threaded 3.13t interpreter cannot load — free-threaded support starts at 3.14t (source build; PyO3's floor), and GIL-release is what makes that floor irrelevant for the standard build: the GIL is already free for the rest of your program on every CPython tors installs into.

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

107 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
Secret-token scrub scrub_secrets(+_report)
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 (five named rules, the extended credential-key set included), CompiledPatterns
Escape-parity byte scan contains_unescaped, find_unescaped
JSON validity gate (parse-and-discard) json_is_valid
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, highlight, ground_sentences, grounding_coverage, grounding_report (per-sentence verdicts + aggregate, the lexical Grounded-in-Context shape)
URL encoding quote, quote_plus, unquote, unquote_plus
Text chunking chunk_cdc, chunk_text(+_iter), chunk_by_words/_sentences/_paragraphs/_lines(+_iter), chunk_hierarchical, chunk_to_budget, chunk_to_offsets, chunk_overlap_cost (the 1/(1-overlap) index-inflation factor), chunk_quality (integrity + cohesion, the LREC 2026 adaptive-chunking metrics)
Rank fusion & IR metrics rank_fuse (Reciprocal Rank Fusion, Cormack/Clarke/Buüttcher SIGIR 2009, ranks only, never raw scores, optional per-list weights: weighted RRF), score_fuse (score-based fusion over (id, score) pairs: combmnz (Fox & Shaw TREC-2 1994, CombSUM × the containing-list count), borda (rank-based counts), linear (Elasticsearch's linear retriever); optional per-list weights, first-appearance tie-break), ndcg_at_k (Järvelin & Kekäläinen TOIS 2002), mrr, recall_at_k, precision_at_k
Information retrieval & integrity tf_idf, bm25_rank, simhash64, simhash128, minhash_signature (classic k-permutation or SuperMinHash engine, optional b-bit compression), minhash_jaccard, weighted_minhash_signature (Consistent Weighted Sampling over token counts), minhash_weighted_jaccard, merkle_root, merkle_diff, content_hash
Near-duplicate detection & dedup simhash_distance, shingle_jaccard, shingle_dice, dedup_near_dup (greedy keep-first over small candidate sets), lsh_candidates/lsh_probability/lsh_threshold (stateless MinHash banding over minhash_signature output; no persistent LSH index)
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. (The one exception: utf8_byte_len deliberately does not detach — its whole body is the borrow, and a detach bracketing no work starves a co-resident loop's heartbeat; see API reference.) 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.

Metadata

Release files for tors 0.16.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.16.0
File Size Uploaded Provenance
tors-0.16.0.tar.gz 2.6 MB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for tors 0.16.0
File
tors-0.16.0-cp310-abi3-win_arm64.whl CPython 3.10 abi3 Windows ARM64 Details
tors-0.16.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
tors-0.16.0-cp310-abi3-musllinux_1_2_x86_64.whl CPython 3.10 abi3 Linux musl 1.2+ x86-64 Details
tors-0.16.0-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
tors-0.16.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.16.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
tors-0.16.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
tors-0.16.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 31.5 MB

Release files / tors-0.16.0.tar.gz

Download URL tors-0.16.0.tar.gz
Size 2.6 MB
Tags Source
SHA-256 checksum
How to use checksums
4b1c5999b636c7e4e7c56ab2f71f2db7387ff815e93bd685cd1f4eabf89426bf
BLAKE2b-256 checksum
How to use checksums
73e92107078ae2903ad4b0152212c0bc03d3839922d453ad4d6e52227aa55b3f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-win_arm64.whl

Download URL tors-0.16.0-cp310-abi3-win_arm64.whl
Size 3.1 MB
Tags CPython 3.10 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
c5210a777f99089f4ac73d053447723dd3509a020f3690759950120d7200ced4
BLAKE2b-256 checksum
How to use checksums
a3f73015b2b598fbc68c428914bf68119ef5a9692026a3b3ff978ebfe48ed13e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-win_amd64.whl

Download URL tors-0.16.0-cp310-abi3-win_amd64.whl
Size 3.3 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
34276b6f9339ed4824d38eac8018b5ac6c67a23bbac43a21aacb3abe3314eac8
BLAKE2b-256 checksum
How to use checksums
4747fe4ed9048abb408e993a65fab5d55d86d158b6abc94f35c0a3bad276c49f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-musllinux_1_2_x86_64.whl

Download URL tors-0.16.0-cp310-abi3-musllinux_1_2_x86_64.whl
Size 4.1 MB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
fd75648259e10d05f8ba1a0c536ddb295e63528b65b09e1b4b1386c5b6a89331
BLAKE2b-256 checksum
How to use checksums
8f3c48b4cd13b7c94968b8816b40a5700c6a87ce9c64f2b1dd6583242a77d844
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-musllinux_1_2_aarch64.whl

Download URL tors-0.16.0-cp310-abi3-musllinux_1_2_aarch64.whl
Size 4.0 MB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
f1bbecfd04e426d9af158b86d3f2fe5ecf7866a0e6ab2a4842a2075489590747
BLAKE2b-256 checksum
How to use checksums
c1edd77e7bbe4480aa552510b5aaa57c3f81004454f0b01e7f1443897f66267e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL tors-0.16.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.8 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
6f54a7bb212392c7e9d4243a7097a6b86b58c909d89579f7c39211ef06f9057f
BLAKE2b-256 checksum
How to use checksums
1a651f63b50e30ebfc88ccc321150cd1066ba7541514aca78ef74497bcc3a2fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL tors-0.16.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.8 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
b5e42246c136508c0bf58af7d86c15125e78104b29f1181b3b3f5615976b3ca4
BLAKE2b-256 checksum
How to use checksums
3443ff6cdd9eb4da1a6c5141a040d1b12a82c3392883057c172e3f199bd96d60
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL tors-0.16.0-cp310-abi3-macosx_11_0_arm64.whl
Size 3.3 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
b31f0c489bb37c9c89abe892c30de00b7f6cf6ad699b36470f324d4cf94b9ed4
BLAKE2b-256 checksum
How to use checksums
0f96ea647b00411861dcf90747c8ef5e93cb649c8b4d054f356bd9fff1cae0f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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.16.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL tors-0.16.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 3.5 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
2f4c15fa603a062ba5d0afe5e902c67e74e857afe81af81fb48fe281c5763cf3
BLAKE2b-256 checksum
How to use checksums
d08a8e9e59448a90d9d830287e50ff3292f4e8da0b59d1a743817dd7dcce1665
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","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}

Security for tors 0.16.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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