farsflow
farsflow is a lightweight Persian text preprocessing library focused on deterministic, semantic-safe normalization for modern AI and search pipelines.
❓ WHY farsflow??
Persian text often contains inconsistent spacing, Arabic/Persian character variants, invisible Unicode formatting characters, Arabic diacritics, and broken ZWNJ usage that negatively affect search, embeddings, NLP pipelines, and LLM applications.
farsflow provides a minimal and deterministic preprocessing layer designed to clean text without aggressive or semantic-destructive transformations.
🚀 Features
- Deterministic and semantic-safe normalization
- Safe ZWNJ (Joiner) correction
- Whitespace and punctuation cleanup
- Line and paragraph structure preserved by default
- Unicode cleanup (Arabic/Persian variants, Bidi controls, invisible formatting characters)
- Configurable normalization options (digits, diacritics, hamza folding, line structure)
- Modular processors
- Zero dependencies
📦 Installation
pip install farsflow
✨ Quick Start
import farsflow as ff
text = "سلام دنیا! این یك تست است که می نویسم ۴۵۶"
cleaned = ff.clean(text)
print(cleaned)
Expected output:
سلام دنیا! این یک تست است که مینویسم 456
🧩 Pipeline Components
farsflow ships with a set of modular, composable components:
- Normalizer — character normalization, Unicode cleanup, and optional diacritics removal
- JoinerFixer — fixes ZWNJ usage without over-correction
- SpaceCleaner — trims redundant whitespace and punctuation spacing
- Pipeline — orchestrates components in a deterministic order
You can customize the pipeline:
from farsflow import (
Pipeline,
Normalizer,
SpaceCleaner,
)
pipeline = Pipeline([
Normalizer(),
SpaceCleaner(),
# JoinerFixer skipped to demonstrate modular behavior
])
text = "می نويسم که این يك متن تستي است"
cleaned = pipeline(text)
print(cleaned)
Expected output:
می نویسم که این یک متن تستی است
📄 Line and paragraph structure
By default farsflow preserves line breaks. Within each line, runs of spaces and tabs collapse and the edges are trimmed; a run of blank lines becomes a single blank line. This is what you want for RAG chunking, sentence splitting, and anything downstream that relies on paragraph boundaries.
import farsflow as ff
ff.clean("تيتر خبر\n\n\nبراساس گزارش ها، نرخ تورم كاهش يافته است.")
# 'تیتر خبر\n\nبراساس گزارشها، نرخ تورم کاهش یافته است.'
To fold everything onto one line instead — the behavior of 0.1.1 and earlier —
set the policy on the Normalizer:
from farsflow import Pipeline, Normalizer, JoinerFixer, SpaceCleaner
pipeline = Pipeline([
Normalizer(newline_policy="collapse"),
JoinerFixer(),
SpaceCleaner(),
])
pipeline("تيتر خبر\n\nمتن خبر") # 'تیتر خبر متن خبر'
The policy lives on Normalizer — the first stage — because line structure has
to be settled before any later rule runs. JoinerFixer deliberately never
matches across a line break, so if a later stage turned newlines into spaces, a
half-space rule would miss on the first pass and fire on the second, and the
output would not be stable.
⚠️ Lossy operations are opt-in
Two Normalizer options delete or merge information instead of just
reshaping it. Both default to False — ff.clean() never does this unless
you ask for it:
remove_diacritics(defaultFalse) — strips Arabic diacritics (harakat, tanvin, shadda, sukun):"کاملاً"→"کاملا". Diacritics are rare in everyday Persian text, but appear in poetry, Quranic quotations, and some literary or TTS text, where deleting them is a real loss. Turn this on for search/embedding pipelines that want maximum recall regardless.fold_hamza_carriers(defaultFalse) — foldsأ/إtoاandؤtoو:"مؤلف"→"مولف". These letters carry a real distinction in some Arabic loanwords, so folding them is a lossy, fuzzy-matching choice, not a safe default.
from farsflow import Pipeline, Normalizer, JoinerFixer, SpaceCleaner
pipeline = Pipeline([
Normalizer(remove_diacritics=True, fold_hamza_carriers=True),
JoinerFixer(),
SpaceCleaner(),
])
The ي/ى→ی, ك→ک, and ة/ۀ→ه substitutions stay on unconditionally
— those forms are not used distinctly in Persian orthography, so folding them
loses nothing.
Design Principles
farsflow follows a few core principles:
- deterministic output
- semantic-safe transformations
- opt-in for potentially lossy operations
- zero dependencies
- modular architecture
🧪 Testing
pytest
# or:
pytest path/to/test_file.py
🗺 Scope
farsflow intentionally stays small: three processors, a pipeline, and a handful of documented options. (v0.1.2)
📄 License
MIT License — see LICENSE.
🤝 Contributing
Contributions are welcome.
Please open an issue or submit a pull request on GitHub.
📝 Changelog
See CHANGELOG.md for version history.
Metadata
Release files for farsflow 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| farsflow-0.1.2.tar.gz | 15.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| farsflow-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.1 kB
Release files / farsflow-0.1.2.tar.gz
| Download URL | farsflow-0.1.2.tar.gz |
|---|---|
| Size | 15.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
28b09597e3fad8a62cc3e1808ef9f926cce929cf1613790acf0e1464da13efca
|
|
BLAKE2b-256 checksum How to use checksums |
e9931b7b7cee2091700b5d0b4bc5efdc3db0593078a81a020defc60dfc956bd3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / farsflow-0.1.2-py3-none-any.whl
| Download URL | farsflow-0.1.2-py3-none-any.whl |
|---|---|
| Size | 15.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a022d61afd0e46b23b0f974f7b6d33b49199a0bf1131321c54a38ec322f6553f
|
|
BLAKE2b-256 checksum How to use checksums |
05cc7a29994068efd5169a6c8967034fa22186e08669e76b6cd52de206eec4df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|