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),
in the spirit of orjson for JSON or polars for dataframes.
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). 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; and 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 either
way.
Install
pip install tors
Building from source (a Rust toolchain and maturin):
pip install maturin
maturin develop --release
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 (CPython compiled to WebAssembly) as a
PEP 783 pyemscripten wheel — the abi3-py310 story carries over unchanged,
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.
What's in it
67 functions plus two small helper classes, grouped by what they do. Each entry is a one-line description; full signatures, argument contracts, and edge cases are in the API reference.
Unicode normalization & forms: clean up messy extracted text, or apply a single normalization form directly.
normalize: NFC + CRLF folding + blank-line collapsing + strip, the common PDF/OCR-extraction cleanup pipelinefinalize:normalizeplus a SHA-256 of the result, in one passnfc/nfd/nfkc/nfkd: the four Unicode normalization forms standalonehtml_unescape:html.unescape, full HTML5 entity tablestrip_controls: every C0/DEL control run becomes one space (model-output scrub)
UTF-8 / UTF-16 / base64 codecs: validate and decode bytes without holding the GIL for the whole buffer.
decode_utf8/finalize_utf8: UTF-8 decode, and decode+normalize+hash fusedutf8_is_valid: SIMD UTF-8 validity check, nostrmaterialized, no exception flowdecode_utf16/utf16_is_valid: the same pair for UTF-16, with BOM sniffingb64_encode_bytes/b64_decode: RFC 4648 base64, byte-exactbase64module paritydetect_encoding: heuristic legacy-encoding guesser (chardetng, Firefox's detector)
Text segmentation: Unicode-correct boundaries the stdlib has no segmenter for at all.
grapheme_count: extended grapheme cluster count (UAX #29)word_bounds/word_bounds_iter/word_count: word boundaries, list, iterator, and count formssentence_bounds/sentence_bounds_iter/sentence_count: sentence boundaries, same three forms
Diffing: difflib-compatible opcodes at native speed.
diff_opcodes: character-levelSequenceMatcher.get_opcodes()shapediff_opcodes_lines: the line-level spelling for document/version diffs
Fuzzy string matching & phonetic matching: edit-distance and sound-alike matching, none of which the stdlib ships.
similarity_ratio/get_close_matches:difflib's ratio and closest-match searchlevenshtein/jaro/jaro_winkler: classic edit-distance and similarity metricssoundex/metaphone/double_metaphone/nysiis/daitch_mokotoff/refined_soundex: classic English/Latin-script phonetic codes, five distinct mapping tables for the same name-matching/dedup lane
Multi-pattern search & redaction: leftmost-longest search and simultaneous replace, a combination no stdlib or maintained GIL-free binding offers.
find_patterns/find_patterns_iter/count_matches: multi-pattern search, list, iterator, and count formsreplace_many: simultaneous multi-pattern replace, one pass, no re-scanningreplace_many_masked: the same, length-preserving, for offset-safe redactionCompiledPatterns: a build-once handle for a fixed pattern list, reused across calls
Markdown / code-fence extraction: pull structured content out of model output.
extract_code_blocks: every fenced code block, per CommonMark's fence grammarstrip_code_fences: unwrap a whole response wrapped in exactly one fencededent:textwrap.dedent, byte-exact
Truncation & lexical grounding: fit text to a budget, or sanity-check a claim against its source.
truncate_to_bounds: cut to a character budget at a word/sentence boundary, never mid-graphemetruncate_ellipsis: hard cut to a character budget plus a…marker, never mid-grapheme (the DB-column shape)is_grounded: exact or fuzzy substring check of a claim against its source
URL encoding: urllib.parse's percent-encoding quartet, GIL-released.
quote/quote_plus/unquote/unquote_plus
Text chunking: split text or bytes for embedding, indexing, or context-window packing.
chunk_cdc: FastCDC content-defined byte chunking (edit-local, for dedup/sync)chunk_text/chunk_text_iter: character-budget chunking, word/sentence-boundary aware, optional overlapchunk_by_words/chunk_by_words_iter: fixed word-count chunkschunk_by_sentences/chunk_by_sentences_iter: fixed sentence-count chunkschunk_by_paragraphs: fixed paragraph-count chunks (blank-line heuristic)chunk_hierarchical: priority-ordered fallback chunking (RecursiveCharacterTextSplitterpattern)
Information retrieval: lexical/statistical primitives for small-corpus search and integrity, without an embeddings dependency.
tf_idf: stateless TF-IDF term scoring per documentbm25_rank: Okapi BM25 reranking of a corpus against a querysimhash64/simhash128: SimHash near-duplicate fingerprintsmerkle_root/merkle_diff: domain-separated Merkle root and per-index chunk diff
Text-processing pipelines: batch preprocessing without a stateful pipeline object.
apply_pipeline: fused NFD/lowercase/accent-fold/stem/lemma/whitespace-collapse pass over a whole text listCompiledLemmaDict: a build-once handle for a largelemma_dict, reused across calls
Examples
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')
text = (
"This is sentence one. This is sentence two. "
"This is sentence three. This is sentence four."
)
chunks = tors.chunk_by_sentences(text, 2)
# [(0, 44), (44, 90)]
[text[s:e] for s, e in chunks]
# ['This is sentence one. This is sentence two. ', 'This is sentence three. This is sentence four.']
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 three run against the built extension; the output above is what they actually return. For every function's full argument contract, error behavior, and more examples, see:
docs/api.md: the full API referencedocs/recipe-ingest.md: bytes to clean, chunked text, end to enddocs/recipe-retrieval.md: a small-corpus, no-embeddings search pipeline
Design philosophy and non-goals
tors is a stateless library: every call does its own work from scratch, with nothing
cached or built up across calls. That keeps every function simple to reason about and
safe to call from anywhere: no handle to manage, no invalidation to think about, no
surprise from a stale cache.
CompiledLemmaDict is the one narrow exception, and it's worth being
precise about why. tf_idf, bm25_rank, and apply_pipeline accept an optional
caller-supplied lemma_dict: a word -> lemma map. A raw dict[str, str] is
re-materialized into a Rust HashMap on every call, and for a realistic multi-thousand-
entry lemma table that cost is measured, not theoretical: roughly 1.4ms per call on a
20,000-entry map, independent of how much text the call actually processes, which can
make repeated small calls slower than the equivalent pure-Python loop.
CompiledLemmaDict builds that HashMap once and hands back an immutable, cheaply
cloned handle, the same re.compile() shape the stdlib already uses for a comparable
problem. It exists for exactly this one measured cost and does not reopen the case for
a general pipeline object: nothing else in this library gets a persistent handle.
The scope cuts below are decisions, not oversights:
- General regex.
find_patterns/replace_manyare leftmost-longest multi-pattern literal search, not a regex engine;chunk_hierarchical'sseparatorsare literal strings, not patterns. - Schema-aware JSON/YAML coercion. Out of scope for the same reason lemmatization is: it needs a schema or model, not an algorithm.
- A bundled lemma dictionary.
apply_pipeline/tf_idf/bm25_rankapply a caller-supplied lemma map;torsships no lemma data of its own, because full lemmatization needs a per-language dataset or a POS-tagging model, not an algorithm; that is outside a text-operations library's job. - A persistent search index.
bm25_rankrecomputes corpus statistics from scratch on every call: the right shape for reranking a small, already-retrieved candidate set, the wrong shape for querying a large corpus repeatedly. Reach for a real search engine (tantivy, in Rust) for that;torsdoes not build or expose index objects. - A bespoke coroutine API. Every function already releases the GIL for its native
pass, so the async surface is one thread dispatch per call (
tors.aio, below) rather than a purpose-built event-loop integration. - A general persistent pipeline object.
apply_pipelinere-describes and re-applies its steps on every call rather than compiling a reusable pipeline handle: seeCompiledLemmaDictabove for the one measured exception.
Beyond scope, a few limitations are worth stating plainly rather than glossing over:
- SimHash is not cryptographic. It's a fast, uniformly-spreading voting hash, not a security primitive: two unrelated documents can coincidentally land close together, especially on short text, and there's no universal "near-duplicate" distance threshold; calibrate per deployment.
soundex/metaphoneare English/Latin-script-oriented. Both pre-filter input to ASCII letters; accented and non-Latin characters are dropped, not encoded.- Chunking makes no retrieval-quality promise. Every chunker guarantees a mechanical contract (correct boundaries, genuine overlap when requested); none of them promises a particular chunk size or strategy helps any downstream model.
- Segmentation is rule-based UAX #29 only. No dictionary segmentation for spaceless scripts (Thai, Khmer, Burmese, Japanese word breaks are a different, dictionary-based problem).
tf_idf/bm25_rankmake no relevance claim. Both are correctly implemented, well-specified ranking formulas; neither promises retrieval quality for any particular corpus or query.
Async use
Releasing the GIL is not the same as not blocking: a native pass called directly from a
coroutine still occupies that coroutine's own turn on the event loop for the call's full
wall-clock duration. tors.aio is the pre-wired fix for the functions where that matters:
await tors.aio.tf_idf(corpus) runs the native pass in a worker thread via
asyncio.to_thread, and the event loop stays responsive for its whole duration.
It covers only the large-input-shaped functions (the chunking family, tf_idf,
bm25_rank, diff_opcodes, diff_opcodes_lines, apply_pipeline, the
normalize/finalize pipeline pair, the decode_utf8/finalize_utf8/
decode_utf16/b64_encode_bytes/b64_decode byte codecs, and
truncate_ellipsis/strip_controls), not all of
tors. Thread dispatch costs on the order of tens of microseconds: noise next to a
millisecond-or-slower native pass over a real corpus or document, real overhead next to a
microsecond-scale call over a short string. Wrapping every export would make the small,
common calls slower through this module than through the plain sync spelling, for no
benefit, so the rest of tors keeps exactly one spelling: call it directly from a
coroutine when the input is small enough that the whole thing finishes in microseconds.
There is no size-based branch inside any wrapper, and there never will be: a function
that sometimes runs inline and sometimes hops to a thread depending on its input is
unpredictable from the caller's side and can silently block the loop when the heuristic
misjudges. Every function in tors.aio always dispatches through asyncio.to_thread,
unconditionally; the choice between the sync spelling and tors.aio is the caller's,
made once at the call site, not a runtime guess. tests/test_aio.py pins this
structurally (no branch in the wrapper body) as well as behaviorally (a heartbeat
coroutine keeps ticking with worst gaps well under the call's own wall during a large
diff_opcodes await).
The streaming iterator constructors (word_bounds_iter and siblings, including the
chunking family's own chunk_text_iter/chunk_by_words_iter/chunk_by_sentences_iter)
have no async twin: an iterator is not an awaitable shape, and draining one to a list
inside a worker thread is exactly what the already-covered list-returning sibling does.
The eager construction pass is the GIL-released part anyway, so the manual
await asyncio.to_thread(lambda: list(tors.word_bounds_iter(text))) covers the
streaming shape when it is genuinely needed. Signatures are identical to the sync
spellings, pinned by tests/test_aio.py; the stub aio.pyi is generated by
tools/gen_aio_stub.py.
Performance
Full measured tables (GIL heartbeat gaps under asyncio, wall-time races against the
stdlib and difflib, and criterion throughput) live in the test suite itself
(tests/test_gil_release.py, tests/test_performance.py) so every number stays
reproducible and re-runnable: the summary here gives the shape, and the test files
are the ledger.
The GIL release is the headline. Running tors.finalize on 12 MiB of prose in a
background thread holds the asyncio event loop's worst heartbeat gap to 10–14 ms; the
equivalent pure-Python pipeline (unicodedata.normalize + str.replace + re.sub +
hashlib.sha256) holds it for 92–108 ms in the same thread placement: roughly 6–12×
worse, and it blows past this project's own CI budget in every sample. At 32 MiB the gap
widens further (17–21 ms vs 250–272 ms).
Already-normalized input is close to free. A quick-check fast path means
normalize/finalize/nfc/nfkd on already-clean 12 MiB text return the original
object with only a SIMD sentinel scan: 2.4–7.7 ms where a full pass costs 100+ ms
(28–45× faster).
Where no stdlib equivalent exists, the comparison is against the real alternative.
diff_opcodes diffs 256 KiB of mutated prose in ~4 ms against difflib's ~3 seconds
(~750×); get_close_matches against 13,900 candidates runs in ~65–72 ms against
difflib's ~3.4 s (~50×); find_patterns fills the gap left by pyahocorasick, which
holds the GIL for its entire scan by design (no ALLOW_THREADS anywhere in its scan
iterator); utf8_is_valid has no stdlib boolean primitive to race at all, so its record
is absolute throughput: up to ~95 GiB/s at 12 MiB, memory-bound above L3 cache.
List-returning functions have a real, disclosed cost at scale. word_bounds on
12 MiB of prose (3.67M segments) holds the GIL for 428–497 ms just marshalling the
returned list: a genuine cost of the list shape, not a bug. The _iter twins
(word_bounds_iter, chunk_text_iter, find_patterns_iter, and friends) exist for
exactly this: the same sequence, streamed, with each __next__ holding the GIL for one
tuple instead of the whole list at once, and 2.1× faster in wall time as well, in the
measured case.
Every number above traces to a specific measured cell; see the linked test files for methodology, corpus construction, and the full per-function tables.
Dependencies and licensing
The license gate is cargo deny check licenses advisories bans (make deny; the same
three checks run in CI's lint job). The allowlist in deny.toml is permissive-only:
MIT, Apache-2.0, BSD-2/3-Clause, ISC, Zlib, plus three documented additions, all
permissive grants inside the gate's intent:
Apache-2.0 WITH LLVM-exception (target-lexicon, a pyo3 build dependency: the
LLVM-exception removes attribution obligations from Apache-2.0), Unicode-3.0
(unicode-ident, the permissive license the Unicode Consortium publishes the UCD data
under), and 0BSD (enum-iterator/enum-iterator-derive, soundex/metaphone's
rphonetic dependency's own dependencies: the BSD Zero Clause License is
OSI-approved and public-domain-equivalent, strictly more permissive than plain MIT).
No GPL/LGPL/AGPL/MPL, no unlicensed. Dual/tri-licensed crates are consumed via
an allowed branch: notably r-efi (a getrandom dependency, dev tree only) offers
LGPL-2.1-or-later as one branch of MIT OR Apache-2.0 OR LGPL-2.1-or-later; tors
consumes it under MIT/Apache and the LGPL branch is never elected, which is exactly
what cargo-deny's SPDX expression evaluation verifies.
Direct dependencies (the full transitive closure is machine-checked by the gate; the dev tree, criterion and friends, is included in the check):
| crate | version | license | role |
|---|---|---|---|
| pyo3 | 0.29.2 | MIT OR Apache-2.0 | the CPython extension layer (abi3-py310) |
| unicode-normalization | 0.1.25 | MIT OR Apache-2.0 | NFC/NFD/NFKC/NFKD tables (Unicode 16.0.0) |
| unicode-segmentation | 1.13.3 | MIT OR Apache-2.0 | UAX #29 grapheme/word tables (Unicode 17.0.0) |
| sha2 | 0.10.9 | MIT OR Apache-2.0 | finalize's SHA-256 |
| const-hex | 1.19.1 | MIT OR Apache-2.0 | digest hex encoding |
| base64 | 0.23.1 | MIT OR Apache-2.0 | RFC 4648 encode/decode core, simd-unsafe feature enabled (the crate's own AVX2/NEON kernels, runtime-detected with a scalar fallback: already-shipped, widely-exercised unsafe code upstream, not written in tors) |
| memchr | 2.8.3 | Unlicense OR MIT | SIMD sentinel scans |
| simdutf8 | 0.1.5 | MIT OR Apache-2.0 | SIMD UTF-8 validity scan |
| similar | 3.2.0 | Apache-2.0 | Myers diff engine for diff_opcodes |
| aho-corasick | 1.1.5 | Unlicense OR MIT | leftmost-longest multi-pattern search engine |
| rs_merkle | 1.5.0 | Apache-2.0 OR MIT | merkle_root/merkle_diff's tree structure (domain-separated SHA-256 Hasher supplied by tors, see merkle_impl.rs) |
| fastcdc | 5.0.0 | MIT | chunk_cdc's FastCDC 2020 content-defined chunking |
| chardetng | 1.0.0 | Apache-2.0 OR MIT | detect_encoding's guesser (the engine Firefox ships) |
| encoding_rs | 0.8.35 | (Apache-2.0 OR MIT) AND BSD-3-Clause | the Encoding type chardetng's guess returns: already pulled in transitively by chardetng; named directly only to call .name() on it, no new package in the tree (the BSD-3-Clause conjunct is the WHATWG Encoding Standard data files' grant, inside the gate's BSD-3 allowance) |
| rust-stemmers | 1.2.0 | MIT OR BSD-3-Clause | tf_idf's/bm25_rank's opt-in Snowball stemmer= knob (18 languages): pulls in serde/serde_derive as a non-optional dependency (an Algorithm enum derive, unused by tors's own call sites); recorded here because it is the one real transitive-weight addition in this table |
| rphonetic | 4.0.0 | Apache-2.0 | soundex/metaphone's phonetic-code algorithms (an Apache Commons Codec port): pulls in enum-iterator/enum-iterator-derive (0BSD, the license-gate addition noted above), nom, and thiserror; tors pre-filters every input to ASCII letters before calling into it, working around a real, verified panic in the crate's own Soundex/DoubleMetaphone encoders on ordinary accented input (see the API docs) |
| criterion (dev) | 0.5.1 | Apache-2.0 OR MIT | the benchmark harness |
| strsim (dev) | 0.11.1 | MIT | differential oracle for levenshtein/jaro/jaro_winkler tests |
Maintenance note (the spec's module decision): simdutf8's
release line has been quiet since 2024-09-22 (0.1.5) while the repository
itself stays active (commits into 2026-06): a mature, zero-dependency
implementation of an algorithm that does not churn (UTF-8 validation), picked
with that fact known and disclosed.
Maintenance note on similar (the same decision): Apache-2.0 only, actively
maintained by mitsuhiko (the Flask author), and the diff engine behind insta,
a crate with a large existing consumer base; no Python binding for it exists, so
tors binds it directly for diff_opcodes.
Maintenance note on aho-corasick (the same decision): dual Unlicense OR MIT: the MIT branch is elected and recorded here (cargo-deny's SPDX
expression evaluation verifies exactly that election, the same mechanism that
handles memchr's Unlicense OR MIT, the same spelling). By BurntSushi, and
the Aho-Corasick engine inside Rust's own regex crate: the most
battle-tested implementation of this exact algorithm in the Rust ecosystem;
it was already in the lock as a transitive dependency (criterion's regex) before
find_patterns made it direct, so the dependency tree grew by zero packages.
Transitive closure at the last lock-state count (120 Cargo.lock entries
including tors itself, i.e. 119 dependency packages incl. dev, re-derived with
cargo metadata over the current lock): 74 MIT OR Apache-2.0, 12 MIT
(fastcdc and strsim among them), 6 Apache-2.0 OR MIT, 5 Apache-2.0
(rphonetic, soundex/metaphone's crate, among them), 3
MIT/Apache-2.0 (criterion-plot, itertools, version_check), 3
Unlicense OR MIT (aho-corasick, memchr, winapi-util), 2 0BSD
(enum-iterator/enum-iterator-derive, rphonetic's own dependencies: the
license-gate addition above), 2
Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT (wasip2, wit-bindgen),
2 BSD-2-Clause OR Apache-2.0 OR MIT (zerocopy), 2 Unlicense/MIT
(same-file, walkdir), 1 (Apache-2.0 OR MIT) AND BSD-3-Clause (encoding_rs,
a direct dependency), 1 Zlib OR Apache-2.0 OR MIT (tinyvec), 1
MIT OR Apache-2.0 OR Zlib (tinyvec_macros), 1
Apache-2.0 WITH LLVM-exception (target-lexicon), 1
(MIT OR Apache-2.0) AND Unicode-3.0 (unicode-ident), 1
MIT OR Apache-2.0 OR LGPL-2.1-or-later (r-efi, the tri-license noted above),
1 Apache-2.0/MIT (rs_merkle: the closure's third spelling of a dual
grant), and 1 MIT/BSD-3-Clause (rust-stemmers, a fourth spelling of the
same dual-grant idea). Every one satisfies the allowlist.
Development
The fuzz/ crate drives the *_impl.rs cores with raw adversarial bytes via
cargo-fuzz (libFuzzer): a bug class the hypothesis-based Python tests cannot
reach, since they shape input around documented contracts rather than raw
bytes. cargo fuzz run <target> -- -max_total_time=30 runs one target
briefly (nightly toolchain and cargo install cargo-fuzz --locked
required); targets assert the same invariants the Python gates pin, at
raw-byte depth; crashes are minimized with cargo fuzz tmin. make fuzz-quick runs every target for 30s each; the weekly fuzz workflow runs
the same set in CI. See
CONTRIBUTING.md for the full setup.
Contributing
See CONTRIBUTING.md for setup, the pre-PR checklist (tests, clippy,
fmt, ruff, the license gate), and commit-message conventions.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tors-0.3.1.tar.gz.
File metadata
- Download URL: tors-0.3.1.tar.gz
- Upload date:
- Size: 687.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c467b47a59e527a10c894632d18d1fea1527faadd23d16db15cf935a0393929
|
|
| MD5 |
1ed8872610c1a15c8b511e660270fcc4
|
|
| BLAKE2b-256 |
6bc36aa3dad6dc71b5a05cef0649ed59f24a7c5f7a873cdc692440b26c907783
|
File details
Details for the file tors-0.3.1-cp310-abi3-win_arm64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-win_arm64.whl
- Upload date:
- Size: 976.5 kB
- Tags: CPython 3.10+, Windows ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d1a85a13d6209712c4c09a2baf8174853fdf2d9b2d25d69beac48dd4057d1ed
|
|
| MD5 |
d3a1fbf5bd9c3e1e4245265b07243901
|
|
| BLAKE2b-256 |
9ea9b01938ba6b5b1100017c2a5627f8f5cd2d0d78e87c59e0816d4dcd6d341e
|
File details
Details for the file tors-0.3.1-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 1.0 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc3e50117dcf7b103f5ea33b74250702a51b64225cf121fc05f14af4378bbf18
|
|
| MD5 |
3b209dd8e0ab836223245a75fa7389ff
|
|
| BLAKE2b-256 |
5e3435b37ae11e52d9d31d233e1ffd1a469995374cf4caad00abb5a738931096
|
File details
Details for the file tors-0.3.1-cp310-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 1.5 MB
- Tags: CPython 3.10+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edc084c451571196761b66877287fb9cef2bd60fb89b77cc351802bdc64380c7
|
|
| MD5 |
d2cb195574e6ceb2d07799929e12b4cc
|
|
| BLAKE2b-256 |
636d1ecbf6283396db1ebb5489d163f0c38450885709a0844d76547c75e26411
|
File details
Details for the file tors-0.3.1-cp310-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 1.5 MB
- Tags: CPython 3.10+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1856b473c80bf73b19eadad20fd242ce8816bab2571c45b4ca354339a8e146e3
|
|
| MD5 |
2a55a3628c3a2b7653e0ed5838fb836a
|
|
| BLAKE2b-256 |
44666c210634dcc051cc511b7e1fcf424248bb6b95ce7ab2800d261d39002b5f
|
File details
Details for the file tors-0.3.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.3 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc5f8fe42ffc0a4097c494a3c9761f8fdaabec933dcb20f478ae24aa1799fe54
|
|
| MD5 |
4f93934feb47e48631dcd1c5e67c4e27
|
|
| BLAKE2b-256 |
896f4ae2a45ee9720312b4aa558bbd4227e1482496b9adeb39d122b4af4b4b04
|
File details
Details for the file tors-0.3.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 1.3 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc45eccb11f1907f1bafd8c36fda4ea4a7b853b90ec263e485737618654c8cb4
|
|
| MD5 |
178b8cdd20783638f19cd272641a1ac7
|
|
| BLAKE2b-256 |
551990722ab8383c528ab42f06333dbd2af14c687a40c6ee91c96e0f72d1a654
|
File details
Details for the file tors-0.3.1-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c64a14f46db8a299cd8ccb56b1d1cc22486ea6d562053cdb00d550f88b2f4d67
|
|
| MD5 |
d97d29757e9cb8984531ce2138d6ea33
|
|
| BLAKE2b-256 |
d03be396e19a5a8e8850c86b6df06d855372d406dfaf6e6c419bb6442a01ec5e
|
File details
Details for the file tors-0.3.1-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: tors-0.3.1-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 1.2 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5e9707b31d4a15bc505ce1013dbe5fab170149fde936d337705ea33cd39b8e6
|
|
| MD5 |
cb17b8c58510821294c7b73414bd327b
|
|
| BLAKE2b-256 |
3a9aaa4c0610914f79d53bec63b90429428216d5ea894193ef9fab930c38de07
|