kalja
kalja is a small, dependency-free Python library for text mutation and human-input fuzzing.
It generates deliberately imperfect text for testing systems such as search, fuzzy matching, forms, parsers, typo correction, normalization, and NLP pipelines.
kalja provides Finnish-focused defaults, including a Finnish desktop keyboard layout, while also supporting US QWERTY and custom keyboard layouts.
Installation
pip install kalja
kalja requires Python 3.10 or later.
Quick start
import kalja
result = kalja.mutate(
"Missä te olette?",
intensity=0.5,
seed=42,
)
print(result)
mutate() applies a combination of supported mutation operations to the input text.
Providing a seed makes the result reproducible:
first = kalja.mutate(
"Missä te olette?",
intensity=0.7,
seed=42,
)
second = kalja.mutate(
"Missä te olette?",
intensity=0.7,
seed=42,
)
assert first == second
Generating variants
Use variants() to generate multiple mutated versions of the same input:
import kalja
results = kalja.variants(
"Voinko varata ajan huomiselle?",
count=10,
intensity=0.7,
seed=42,
)
for result in results:
print(result)
count controls the number of generated results.
Variants are not guaranteed to be unique. At low intensities or with short inputs, multiple mutation passes may produce the same output.
Mutation intensity
The high-level mutate() and variants() APIs use an intensity value between 0.0 and 1.0.
kalja.mutate(text, intensity=0.0)
kalja.mutate(text, intensity=0.5)
kalja.mutate(text, intensity=1.0)
Intensity is a relative fuzzing level, not a direct probability.
Internally, kalja scales the configured mutation rates:
effective rate = maximum rate × intensity
This means intensity=1.0 enables kalja's full configured mutation rates. It does not mean that every character will be mutated.
At intensity=0.0, all mutation rates are zero and the original text is returned unchanged.
Keyboard layouts
kalja includes Finnish and US desktop keyboard layouts.
Finnish desktop is the default:
import kalja
result = kalja.mutate(
"Missä te olette?",
layout=kalja.FI_DESKTOP,
seed=42,
)
US QWERTY can be selected explicitly:
result = kalja.mutate(
"Where are you?",
layout=kalja.US_DESKTOP,
seed=42,
)
Keyboard-error mutations use physical key positions to select nearby keys.
The included layouts are:
kalja.FI_DESKTOP— Finnish desktop QWERTYkalja.US_DESKTOP— US desktop QWERTY
Keyboard coordinates are approximate and are intended for neighboring-key mutation rather than exact physical keyboard simulation.
Low-level mutations
kalja also exposes individual mutation operations when precise control is needed.
Keyboard errors
Replace characters with neighboring keys:
result = kalja.keyboard_error(
"Oulu 90100",
rate=0.1,
seed=42,
)
A different layout can be supplied:
result = kalja.keyboard_error(
"hello world",
rate=0.1,
layout=kalja.US_DESKTOP,
seed=42,
)
Character transposition
Randomly swap adjacent characters:
result = kalja.transpose_chars(
"kalja",
rate=0.05,
seed=42,
)
Character omission
Randomly remove characters:
result = kalja.drop_chars(
"kalja",
rate=0.02,
seed=42,
)
Character repetition
Randomly duplicate characters:
result = kalja.repeat_chars(
"kalja",
rate=0.02,
seed=42,
)
Spacing
Randomly remove existing spaces or insert new spaces:
result = kalja.mutate_spacing(
"Missä te olette?",
rate=0.02,
seed=42,
)
Casing
Randomly flip the case of characters with distinct upper- and lowercase forms:
result = kalja.mutate_casing(
"Olen Oulussa",
rate=0.02,
seed=42,
)
Punctuation
Randomly omit or duplicate existing punctuation:
result = kalja.mutate_punctuation(
"Missä olet?",
rate=0.02,
seed=42,
)
Rates
Low-level mutation functions use rate rather than intensity.
A rate is the probability associated with that specific mutation operation:
kalja.drop_chars(
"abcdef",
rate=0.1,
seed=42,
)
Here, each character has a 0.1 probability of being dropped.
This differs from the high-level intensity parameter, which scales several mutation rates at once.
Configurable mutator
For more control, use Mutator directly:
import kalja
mutator = kalja.Mutator(
keyboard_error_rate=0.08,
transposition_rate=0.04,
omission_rate=0.02,
repetition_rate=0.03,
spacing_rate=0.02,
casing_rate=0.01,
punctuation_rate=0.02,
layout=kalja.FI_DESKTOP,
seed=42,
)
result = mutator.mutate("Olen täysin kunnossa.")
A Mutator owns its own pseudorandom number generator.
Repeated calls advance that generator:
first = mutator.mutate("Missä te olette?")
second = mutator.mutate("Missä te olette?")
The two results may differ.
Recreating a Mutator with the same configuration and seed reproduces the same sequence of results.
kalja does not modify Python's process-global random state.
Command-line interface
kalja includes a command-line interface.
Mutate text directly:
kalja "Missä te olette?"
Control mutation intensity:
kalja --intensity 0.7 "Missä te olette?"
Use a seed for reproducible output:
kalja --intensity 0.7 --seed 42 "Missä te olette?"
Generate multiple variants:
kalja --count 5 --intensity 0.7 --seed 42 "Missä te olette?"
Select the US keyboard layout:
kalja --layout us "Where are you?"
The Finnish layout is the default:
kalja --layout fi "Missä te olette?"
kalja can also read text from standard input:
echo "Missä te olette?" | kalja --intensity 0.7 --seed 42
Run the built-in help for all options:
kalja --help
Use cases
kalja can be used to generate imperfect inputs for testing:
- search and autocomplete
- fuzzy matching
- typo-tolerant systems
- form handling
- text normalization
- parsers
- chat applications
- NLP pipelines
- validation logic
- test fixtures and fuzz tests
For example, a search system can be tested against several mutated versions of a query:
import kalja
queries = kalja.variants(
"ravintola oulu",
count=20,
intensity=0.6,
seed=42,
)
for query in queries:
test_search(query)
Unicode and Finnish text
kalja works with Python Unicode strings and supports Finnish characters such as:
ä ö å Ä Ö Å
The Finnish desktop layout includes these characters when generating neighboring-key substitutions.
kalja currently operates on Python string characters rather than Unicode grapheme clusters. Combining character sequences may therefore be treated as multiple mutation units.
Limitations
kalja is a mechanical text mutation and fuzzing library. It is not a linguistic model of how people make mistakes.
In particular:
- keyboard geometry is approximate
- Finnish and US desktop layouts are currently included
- AltGr combinations are not modeled
- dead-key composition is not modeled
- mobile keyboard geometry is not modeled
- Unicode grapheme clusters are not treated as atomic units
- generated mutations are not guaranteed to represent realistic human errors
- generated variants are not guaranteed to be unique
These constraints are intentional: kalja aims to provide small, deterministic, understandable mutation primitives suitable for testing.
Development
Install the development environment with uv:
uv sync
Run the test suite:
uv run pytest
Run linting:
uv run ruff check .
Check formatting:
uv run ruff format --check .
Run type checking:
uv run mypy src
Run coverage:
uv run pytest --cov=kalja --cov-report=term-missing
License
kalja is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later). See the LICENSE file for details.
Release files for kalja 0.1.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 | |
|---|---|---|---|
| kalja-0.1.0.tar.gz | 26.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kalja-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.1 kB
Release files / kalja-0.1.0.tar.gz
| Download URL | kalja-0.1.0.tar.gz |
|---|---|
| Size | 26.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f1036c420c7d5f4b3ff56b36d7b30d191711e67e6dac9c447f603a3cc1481e1
|
|
BLAKE2b-256 checksum How to use checksums |
80cee9b53120d81eeafa29a6e466bdf4bf728fca9900cba240dca811f0192a42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 25, 2026.
Transparency logRelease files / kalja-0.1.0-py3-none-any.whl
| Download URL | kalja-0.1.0-py3-none-any.whl |
|---|---|
| Size | 22.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d2abe423b809776a89f5e74e686d31056371338e71c1bc0f912014839163ab70
|
|
BLAKE2b-256 checksum How to use checksums |
bc8a802abbb292f73645c768d3f0b13c5af1b472028dcc33f93ad52f2f7aef91
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 25, 2026.
Transparency log