Rishe (ریشه)
Fast Persian NLP and morphology, built in Rust.
Rishe (root in Persian) turns raw Persian text into clean, structured data in one call: normalized text, tokens, lemmas, sentences, ezafe markers, and a formal rewrite of colloquial writing. It also ships the morphology utilities that usually live in a separate library: stemming, verb conjugation, inflection, token splitting, spell correction, and stopword removal.
The core is Rust. You get it as a pip install with prebuilt wheels, no model
downloads, no runtime dependencies, and the same engine as a Rust crate.
from rishe import Pipeline
pipe = Pipeline()
doc = pipe("اون داره تو خونه کتاب میخونه، چیه؟")
doc.tokens # ['آن', 'دارد', 'تو', 'خانه', 'کتاب', 'میخواند', '،', 'چیست', '؟']
doc.lemmas # ['آن', 'داشت', 'تو', 'خانه', 'کتاب', 'خواند', '،', 'چیست', '؟']
doc.formal_text # 'آن دارد تو خانه کتاب میخواند، چیست؟'
Why Rishe
- Normalization about 80× faster than Hazm and Shekar: 1.2 µs per sentence against 96 µs and 104 µs.
- Best tokenization and lemmatization in this comparison. Token F1 of 1.0000 on UD-Seraji, 0.9989 on UD-PerDT and 0.9028 on PEYMA; lemma accuracy of 0.9209 and 0.9316, ahead of Hazm (0.9048 / 0.9009) and Shekar (0.81 / 0.82).
- Morphology in the same core. Stemming, conjugation, inflection, token splitting, spell correction and stopwords — no second library, no extra runtime. On the UD lemma proxy, stemming is 89.2% / 89.5% exact against Hazm (66.4% / 64.4%) and Shekar (76.1% / 72.0%).
- Spell correction that is fast enough to stay in the loop. 80.8% top-1 and 96.2% top-5 on synthetic 1-edit typos, at 0.16 ms per word; Shekar's statistical checker is 60.8% / 77.6% at 94 ms per word on the same set.
- Millions of tokens per second. 1.0M tokens/s on one thread;
pipe()runs batches in parallel with the GIL released and reaches 6.5M tokens/s on 12 threads. - Everything in one call. Normalization, informal → formal conversion,
tokenization, lemmatization, sentence splitting and ezafe detection from one
Pipelineobject, in about 16 µs per sentence. - Informal Persian handled. Colloquial forms (
اون,خونه,داره,میخوام,چیه) are rewritten to their formal spelling, and fused clitics such asبهشandازشare split intoبه+اوandاز+او. - Nothing to set up. Compiled-in lexicons (190k nouns, 1.2k verb stems, word frequencies, stopwords), no model downloads, no Python dependencies, no network access.
- Wheels for every major platform. Linux (glibc and musl, x86_64 and aarch64), macOS (Intel and Apple Silicon) and Windows, for Python 3.9+ (abi3). The same core is available as a Rust crate.
- Thread-safe. One
Pipelinecan be shared across threads.
Benchmarks
Measured against Hazm and Shekar on the full UD Persian-Seraji, UD Persian-PerDT and PEYMA test sets. Every library is built once and reused.
Core speed (per sentence, single thread)
| Rishe | Hazm | Shekar | |
|---|---|---|---|
| Normalize | 1.2 µs | 96 µs | 104 µs |
| Tokenize | 4.1 µs | 5.7 µs | 4.4 µs |
| Full pipeline | 16.3 µs | — | — |
Core accuracy
| Task | Dataset | Rishe | Compared with |
|---|---|---|---|
| Token F1 | UD-Seraji | 1.0000 | Shekar 0.9992, Hazm 0.9810 |
| Token F1 | UD-PerDT | 0.9989 | Shekar 0.9976, Hazm 0.9609 |
| Token F1 | PEYMA | 0.9028 | Shekar 0.9018, Hazm 0.8901 |
| Lemma accuracy | UD-Seraji | 0.9209 | Hazm 0.9048, Shekar 0.8058 |
| Lemma accuracy | UD-PerDT | 0.9316 | Hazm 0.9009, Shekar 0.8170 |
New feature highlights
| Feature | Metric | Rishe | Hazm | Shekar |
|---|---|---|---|---|
| Stemming | exact UD-lemma proxy (Seraji / PerDT) | 89.2% / 89.5% | 66.4% / 64.4% | 76.1% / 72.0% |
| Conjugation | hand-written spot-check (29 forms) | 29/29 | 28/29 | 29/29 |
| Conjugation | ms/verb | 0.010 | 0.152 | 0.080 |
| Inflection | comparative / superlative / plural | 100% | N/A | 100% |
| Token splitting | ms/token | 0.0006 | 12.05 | N/A |
| Spell correction | top-1 / top-5 on 1-edit typos | 80.8% / 96.2% | N/A | 60.8% / 77.6% |
| Stopwords | list size | 389 | 389 | 301 |
The normalizer operators are 1.5–6× faster than Shekar's per operator (the
exception is ya, 0.7×) with exact output agreement of 100% on 8 of 11
operators and 99.04% on verbal spacing. normalize_stages is opt-in; the default
normalize path is unchanged.
The full core tables, batch scaling and method are in docs/BENCHMARKS.md. The feature tables, proxy metrics and caveats are in docs/FEATURE_BENCHMARKS.md.
Reproduce:
python scripts/benchmark.py # core token/lemma/speed
python scripts/benchmark_features.py # stemming, conjugation, splitting, spelling
python scripts/benchmark_normalizer.py # 11 normalizer operators vs Shekar/Hazm
python scripts/benchmark_maskers.py # 12 maskers vs Shekar
Install
pip install rishe # Python >= 3.9
or, from Rust (Cargo.toml): rishe = "0.2".
No Python dependencies. No model downloads. No network access at runtime.
Usage
from rishe import Pipeline
pipe = Pipeline() # unify digits, strip diacritics, fix ZWNJ
doc = pipe("کتابهای خوب را به دوستانم میدهم.")
doc.normalized_text, doc.formal_text, doc.sentences
doc.tokens, doc.lemmas, doc.ezafe_markers
docs = pipe.pipe(texts) # parallel batch -> list[ProcessedDoc]
pipe.normalize("مي كنم 2026") # 'میکنم ۲۰۲۶'
pipe.tokenize("سلام، دنیا!") # raw tokenization, no normalization
pipe.word_tokenize("سلام، دنیا!") # normalize + tokenize
pipe.sent_tokenize(text) # sentence splitting only
pipe.lemmatize("میروم") # 'رفت' (past-stem lemmas, UD style)
pipe.lemma_full("میروم") # ('رفت', 'رو') (past + present stem)
pipe(text) also rewrites colloquial words to formal ones, so its tokens can
differ from the surface text; word_tokenize tokenizes without rewriting.
tokenize is the fast raw path used for the tokenization benchmarks.
Morphology and utilities
pipe.stem("کتابها") # 'کتاب'
pipe.conjugate("رفت")["present"][:3] # ['میروم', 'میروی', 'میرود']
pipe.comparative("بزرگ") # 'بزرگتر'
pipe.superlative("بزرگ") # 'بزرگترین'
pipe.plural("کتاب") # 'کتابها'
pipe.possessive("خانه", 0) # 'خانهام'
pipe.split_token("بهش") # ['به', 'او']
pipe.spell_suggest("سسلام") # ['سلام', 'اسلام', ...]
pipe.spell_correct("سسلام بر ششما") # 'سلام بر شما'
pipe.tokenize_spans("سلام دنیا") # [('سلام', 0, 4), ('دنیا', 5, 9)] (char offsets)
pipe.remove_stopwords(tokens) # drop Persian stopwords
pipe.process_stages(text, ["normalize", "sentences"]) # choose pipeline stages
Normalizer operators
normalize_stages applies the P1 normalizer operators in the order you list them:
alphabet, arabic_unicode, ya, ya_joda, digits, punctuation,
repeated_letters, orthographic_spacing, punctuation_spacing, word_spacing,
verbal_spacing, and spacing (all four spacing operators).
pipe.normalize_stages("﷽ 1𝟮3٤٥⓺", ["arabic_unicode", "digits"])
# 'بسم الله الرحمن الرحیم ۱۲۳۴۵۶'
pipe.normalize_stages("سلام(دنیا) ! می روم", ["punctuation_spacing", "verbal_spacing"])
# 'سلام (دنیا)! میروم'
word_spacing and verbal_spacing validate against the compiled lexicons, so they
only join forms the engine knows (کتاب ها → کتابها, رفته ام → رفتهام).
Unknown stage names raise ValueError.
Masking
mask_stages removes or masks URLs, emails, mentions, hashtags, emoji, HTML
tags, non-Persian letters, stopwords, offensive words, digits, diacritics and
punctuation. Stage names are url, email, mention, hashtag, emoji,
html, non_persian, stopwords, offensive, digits, diacritics and
punctuation.
pipe.mask_stages("سلام @user، به https://rishe.dev یا info@rishe.dev بنویس.",
["url", "email", "mention"])
# 'سلام به <URL> یا <EMAIL> بنویس.'
pipe.mask_stages("این کتاب است", ["stopwords"], mask_token="[MASK]")
# '[MASK] کتاب [MASK]'
Defaults are <URL> for URLs, <EMAIL> for emails, a space for mentions,
hashtags and HTML tags, and removal for everything else. Pass mask_token to
override all stages. Masking keeps the spaces left behind; run
normalize_stages(..., ["spacing"]) afterwards to collapse them. Unknown stage
names raise ValueError.
Spell correction uses a 190k-word frequency lexicon and edit-distance 1; typos with two or more edits are left unchanged.
Utilities
pipe.number_to_words(1404) # 'یک هزار و چهارصد و چهار'
pipe.number_to_words("3.14") # 'سه ممیز چهارده'
pipe.ngrams("این یک متن", 1, 2) # ['این', 'یک', 'متن', 'این یک', 'یک متن']
pipe.persianize("فایل را با کامپیوتر باز کن") # 'پرونده را با رایانه باز کن'
pipe.persianize_suggest("فایل") # [('فایل', ['پرونده'], 0, 4)]
flatten(["الف", ["ب", "پ"]]) # 'الف', 'ب', 'پ'
persianize uses a built-in curated loanword map; persianize_suggest returns
the alternatives and character spans.
Augmentation
keyboard_noise, ocr_noise and whitespace_noise simulate typing, OCR and
spacing errors. Pass seed= for reproducible output.
pipe.keyboard_noise(text, seed=42)
pipe.ocr_noise(text, seed=42)
pipe.whitespace_noise(text, seed=42)
Keywords and transliteration
pipe.rake(text, top_n=5)
pipe.textrank(text, top_n=5)
pipe.to_tajik("خانه") # 'хона'
pipe.to_persian("хона") # 'خانه'
Transliteration is a rule-based script mapping; it preserves the consonant skeleton and is not a phonetic translation.
Tokenizer options
pipe.word_tokenize_with("رفته است", join_verb_parts=True) # ['رفته_است']
pipe.word_tokenize_with("info@a.co", replace_emails=True) # ['EMAIL']
Options: join_verb_parts, join_abbreviations, separate_emoji,
replace_links, replace_ids, replace_emails, replace_numbers,
replace_hashtags. All are off by default, so plain word_tokenize is
unchanged.
Corpora
from rishe import ConlluReader, PeymaReader, PlainTextReader
for sentence in ConlluReader("data/corpora/fa_perdt-ud-test.conllu"):
print(sentence.text, sentence.tokens[0].lemma)
ConlluReader parses UD/CoNLL-U, PeymaReader parses the PEYMA test format,
and PlainTextReader reads one sentence per line. Other corpus-specific formats
can be added as needed.
CLI
rishe normalize "مي كنم"
rishe tokenize "سلام دنیا" --json
rishe mask "info@a.co" --stages email
printf '%s\n' '{"command":"tokenize","text":"سلام دنیا"}' | rishe serve
Migrating from Hazm
from rishe import Lemmatizer, Normalizer, WordTokenizer, Stemmer
normalizer = Normalizer()
normalizer.normalize("مي كنم") # 'میکنم'
lemmatizer = Lemmatizer()
lemmatizer.lemmatize("میروم") # 'رفت#رو'
WordTokenizer().tokenize("رفته است") # ['رفته_است']
The wrappers cover Normalizer, WordTokenizer, SentenceTokenizer,
Lemmatizer, Stemmer and TokenSplitter, plus module-level word_tokenize
and sent_tokenize. Hazm constructor arguments that point at custom lexicon
files are rejected because Rishe compiles its lexicons in.
use rishe::Pipeline;
let pipe = Pipeline::create(true, true, true);
let doc = pipe.process("کتابهای خوب را به دوستانم میدهم.");
println!("{:?}", doc.tokens);
println!("{}", pipe.stem("کتابها"));
Scope
Rishe is a text-preprocessing and morphology engine: normalization, tokenization, sentence splitting, lemmatization, stemming, verb conjugation, inflection, token splitting, spell correction, stopword removal, ezafe detection and informal → formal conversion.
It does not yet do POS tagging, chunking, NER, dependency parsing, embeddings or text classification. Those are the next optional model packs; the base stays zero-dependency and fast. Today, use Rishe as the preprocessing step in front of a tagger or parser.
Building from source
cargo test
maturin develop --release # Python extension into the current venv
python scripts/benchmark.py # needs: hazm shekar conllu
python scripts/benchmark_features.py
scripts/extract_lexicons.py regenerates data/*.tsv and
data/word_freq.tsv from Hazm's data files; build.rs compiles them into FSTs.
License
Dual-licensed under Apache-2.0 or MIT, at your option. The bundled lexicons, stopwords and word frequencies are derived from Hazm data (MIT); see NOTICE.md.
Metadata
Release files for rishe 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| rishe-0.3.0.tar.gz | 1.8 MB | With provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| rishe-0.3.0-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| rishe-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ x86-64 | Details |
| rishe-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ ARM64 | Details |
| rishe-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| rishe-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| rishe-0.3.0-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| rishe-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 17.0 MB
Release files / rishe-0.3.0.tar.gz
| Download URL | rishe-0.3.0.tar.gz |
|---|---|
| Size | 1.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e81ab5c38d85d68a4b497209998259dc2df882f2038e0261d33eec251d6c2b02
|
|
BLAKE2b-256 checksum How to use checksums |
a7ecf910715cc46967e39af46d56a47c077e4ac0a41ef4ba5a79d4ab10b75939
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / rishe-0.3.0-cp39-abi3-win_amd64.whl
| Download URL | rishe-0.3.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
259b3c3d6526faf0b1e7bd8e8570ce7c9ce76e7eb890095b377af17661c924e7
|
|
BLAKE2b-256 checksum How to use checksums |
2a0b3d288a892302b711d27fb5047d8faa52de96ac221578d576ede771fdc70c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / rishe-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl
| Download URL | rishe-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 2.4 MB |
| Tags | CPython 3.9 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
7c383fe36d414a019b904baf8cde5d6799ba6fc374c3cbe775ac3233d4c5508f
|
|
BLAKE2b-256 checksum How to use checksums |
8243787d4f5fea5130b72e684c123a23f7004820ddae36780ea91f47c0a6c68a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / rishe-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl
| Download URL | rishe-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | CPython 3.9 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
c47ac40865dbc5c7e1cea73db3957f27ffe26017399abb18ea1485b67dc69374
|
|
BLAKE2b-256 checksum How to use checksums |
5091bcaaae3fe9c5680bdbb68f08c6de5e4347a72c1adc8b03bea2740b9f9136
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / rishe-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | rishe-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 2.2 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
7a255b6a8ebd85f9c6a55c73f31c681eb71f0e631132b9ce8a6660571e9870f3
|
|
BLAKE2b-256 checksum How to use checksums |
ab3c8266aeb02e476559837a156617a140c1c6dae89fa7b648f0cfd928e2037a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / rishe-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | rishe-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
5d69e1498e4ba0eaf80ae44ed3042d444ea5a3e0f4c2df6c4e423fe7c0af4c24
|
|
BLAKE2b-256 checksum How to use checksums |
3f4bad98f52a56f3e3cccdb945efd8cb80ef9ee6af78337b270e77d04b64fc4b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / rishe-0.3.0-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | rishe-0.3.0-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
42b4c3fb26ff72d015d04d7a668f0c61cd604144586ea45f94230a5f47741189
|
|
BLAKE2b-256 checksum How to use checksums |
d0a3c1729e8aa76acd96443b114aeeeae0e4981f6e96f88241bb73eb018d306a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / rishe-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | rishe-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
2caa61256f9c3dc6966d4fd931d58c52425552de839cd00c50ff47e96d59ea48
|
|
BLAKE2b-256 checksum How to use checksums |
6284d34c5ce4956273fbdd36790108cffa1af959cd5badf69fa63a7966b15776
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for rishe 0.3.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.
Full provenance
All 8 files in this release have been uploaded with provenance metadata.
Attestation for rishe-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA 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.