polytypo
Locale-correct quotes, dashes, ellipses, apostrophes, symbols and no-break spaces —
one portable spec, designed for byte-identical output across runtimes.
Try it live, no install: polytypo.dev
This is the Python implementation. The full spec — all locales, all rules, worked examples in
each — lives in polytypo/polytypo. This runtime supports
the text, html and yaml modes fully, and markdown for the commonmark dialect only —
mdx raises POLYTYPO_INVALID_DIALECT (no MDX/JSX parser is available for Python; see
Supported dialects).
Install
pip install polytypo
Usage
from polytypo import transform
transform("She said, \"it's fine\" -- but I wasn't sure...", locale="en-US")
# She said, “it’s fine”—but I wasn’t sure…
Same input, one locale changed — quotes, dash spacing and all follow the target locale, not a single hardcoded style:
transform('Sie sagte: "Alles gut" -- aber ich war mir nicht sicher...', locale="de-DE")
# Sie sagte: „Alles gut“ – aber ich war mir nicht sicher…
HTML and Markdown are first-class modes, not an afterthought — tags, attributes and fenced code are left alone; only text content is touched:
from polytypo.html import transform
transform('<a title="test... wait">Wait... she said "go on."</a>', locale="en-US")
# <a title="test... wait">Wait… she said “go on.”</a>
polytypo.text, polytypo.html, polytypo.markdown and polytypo.yaml each exclude the
parser dependency the other modes don't need (importing polytypo.text never imports
html.parser-based span extraction or tree-sitter; polytypo.yaml needs no parser at all). The
aggregate polytypo module supports every mode via a mode keyword, defaulting to "text".
locale has no default anywhere and must always be passed explicitly — there is no silent
fallback to English.
import polytypo
polytypo.transform("...", locale="fr", mode="markdown", dialect="commonmark")
yaml mode is the one that asks something of you, and it asks for a reason. YAML is a data
format with prose in some of it, so you name the keys whose values are prose; there is no default
and no guess:
from polytypo.yaml import transform
transform(
"summary: Rates -- all of them...\nrun: git diff -- a--b\n", locale="en-US", keys=["summary"]
)
# summary: Rates—all of them…
# run: git diff -- a--b
Nothing in YAML's syntax separates a sentence from a shell script: description holds one and
run holds the other, spelled identically. Quoting, indentation, anchors and a block scalar's
chomping indicator are never decoded and rewritten — the file is located, not re-emitted — so the
trailing newlines of a |+ block come back exactly as you wrote them. An empty keys list is
legal and processes nothing.
analyze() runs the same pipeline and reports what it would do instead of doing it — one record
per edit, each with the rule that made it and code-point offsets into the input you passed (into
the document, in html, markdown and yaml mode, not into a span):
import polytypo
polytypo.analyze('Wait... "really"?', locale="en-US")
# [Change(rule_id='ellipsis', start=4, end=7, before='...', after='…'),
# Change(rule_id='quotes', start=8, end=9, before='"', after='“'), …]
It is a report, not a patch. The list is empty exactly when transform would return the input
unchanged, and every rule_id is a rule that was enabled for that call — but two rules may touch
the same original range (French spaces deletes the space before : and nbsp puts a no-break
one back), so replaying the list is not guaranteed to reproduce the output. Call transform for
the text. Full contract: spec/rules/analyze.md.
Supported dialects
markdown mode requires a dialect keyword, exactly as the spec requires (no default,
detection is forbidden). This runtime supports dialect="commonmark" (CommonMark plus GFM —
tables, strikethrough, task lists, autolink literals). dialect="mdx" is a real dialect the spec
names, but this runtime has no MDX/JSX parser for it and raises POLYTYPO_INVALID_DIALECT
immediately rather than silently mishandling it — a narrower, honest conformance claim, not a
port defect.
Licence
MIT. See LICENSE.
Release files for polytypo 1.6.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| polytypo-1.6.3.tar.gz | 341.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| polytypo-1.6.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 421.9 kB
Release files / polytypo-1.6.3.tar.gz
| Download URL | polytypo-1.6.3.tar.gz |
|---|---|
| Size | 341.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
732ed6ed562d288d182a3b1a6c4244951bd0b0347cf69ef1d8309c0df2d965c3
|
|
BLAKE2b-256 checksum How to use checksums |
267516c86e92c946cd226936d32f915676ac87daa2d969c18e3ff512ed568797
|
| 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 24, 2026.
Transparency logRelease files / polytypo-1.6.3-py3-none-any.whl
| Download URL | polytypo-1.6.3-py3-none-any.whl |
|---|---|
| Size | 80.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c1a5a217067cb0e0becea55ea609e876903cd61eaaa3a27dc7e8d5926aadc35b
|
|
BLAKE2b-256 checksum How to use checksums |
7ed5bb120e141ff10cf0bef333141fdbd68832c66813757e5a3359cb6e2d7479
|
| 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 24, 2026.
Transparency log