Contracts¶
slugany guarantees several invariant contracts, verified by the test suite.
Idempotency¶
Applying slugify() to a slug produces the same slug:
This holds for all style presets:
for style in ("kebab", "snake", "camel", "pascal", "train", "dot", "filename", "url"):
result = slugify("hello world", style=style)
assert slugify(result, style=style) == result
Determinism¶
The same input always produces the same output:
No random behavior, no time-dependent logic, no global mutable state.
No Double Separators¶
Output never contains consecutive separators, nor leading/trailing separators:
assert "--" not in slugify("Hello -- World")
assert not slugify(" Hello ").startswith("-")
assert not slugify(" Hello ").endswith("-")
This also holds for multi-character separators:
assert slugify("hello world", separator="--") == "hello--world"
assert slugify("--hello--", separator="--") == "hello"
ASCII Output¶
With allow_unicode=False (default), output is always pure ASCII:
All non-ASCII characters are transliterated or stripped. Confusable homoglyphs (Cyrillic, Greek) are converted to their Latin equivalents.
Fallback¶
Empty strings, whitespace, punctuation-only, and emoji-only inputs produce empty string (or fallback):
assert slugify("") == ""
assert slugify(" ") == ""
assert slugify("!!!") == ""
assert slugify("!!!", fallback="untitled") == "untitled"
The fallback is returned as-is — it should be a valid slug to maintain idempotency:
Case-Insensitive Stopwords¶
Stopwords are matched case-insensitively regardless of the lowercase setting:
assert slugify("The Hello World", stopwords=["the"]) == "hello-world"
assert slugify("THE HELLO WORLD", stopwords=["the"]) == "hello-world"
Stopwords containing non-alphanumeric characters are normalized before matching:
assert slugify("hello-world foo", stopwords=["hello-world"]) == "foo"
assert slugify("hello_world foo", stopwords=["hello_world"]) == "foo"
Non-ASCII stopwords are transliterated to match the deconfused text:
assert slugify("café hello", stopwords=["café"]) == "hello"
assert slugify("ä hello", stopwords=["ä"], lang="de") == "hello"
CSS-Safe Output¶
When css_safe=True, slugs starting with a digit are prefixed to produce valid CSS identifiers:
assert slugify("2024 recap", css_safe=True) == "s-2024-recap"
assert slugify("hello world", css_safe=True) == "hello-world"
The prefix adapts to the active style preset: