isomorphism
Reversible anonymisation of Swiss personal data for Python. It replaces AHV numbers, IBANs, UID numbers, phone numbers, emails, names, addresses, companies, dates of birth, card and account numbers with placeholders before a text goes to a language model, and puts the real values back into the answer.
- Local only. No network calls, no telemetry, no dependencies, nothing downloaded at runtime.
- Reversible.
anonymize(text)returns the text with placeholders and a mapping,restore(text, mapping)undoes it, also for an answer that arrives in pieces. - Same value, same placeholder. The model can follow that
[PERSON_1]appears three times. - Checksums. AHV (EAN-13), IBAN (mod 97), UID (mod 11), cards (Luhn) and PostFinance accounts (mod 10) are verified. Numbers that look right but fail the check are reported anyway, with lower confidence.
- Audit-friendly. Every hit comes back as kind, position, confidence and recogniser, without the value.
- German, French, Italian and English in one pass, including Swiss spellings.
Python 3.12 or newer. MIT license.
pip install isomorphism
from isomorphism import anonymize, restore
text = "Hr. Keller, Musterstrasse 12, 9000 St. Gallen, AHV 756.1234.5678.97, Tel. 071 123 45 67"
safe, mapping = anonymize(text)
# '[PERSON_1], [ADDRESS_1], AHV [AHV_1], Tel. [PHONE_1]'
answer = call_your_model(safe) # the model only sees placeholders
print(restore(answer, mapping)) # real values are back
The mapping holds the real values. This package never logs or stores it; keeping it (in memory, per request) is up to you. repr(mapping) shows only the number of placeholders.
What it finds
| Kind | Placeholder | Formats | Check |
|---|---|---|---|
| AHV number | [AHV_n] |
756.1234.5678.97, with spaces or without separators |
prefix 756, EAN-13 |
| IBAN (CH, LI) | [IBAN_n] |
with or without spaces, upper or lower case | mod 97 |
| UID number | [UID_n] |
CHE-123.456.788, CHE123456788; MWST/TVA/IVA/HR stay in the text |
mod 11 |
| Phone (Switzerland) | [PHONE_n] |
+41 71 123 45 67, 0041 …, +41 (0)71 …, 071 123 45 67, 071/123 45 67, (071) …, 0711234567 |
format |
[EMAIL_n] |
format | ||
| Person | [PERSON_n] |
Hr. Keller, Frau Dr. Müller, M. Jean Dupont, signora Bianchi, Anna Muster, Muster, Anna; the same surname or a first name alone later in the text |
titles, first-name list |
| Company | [ORG_n] |
name before AG, GmbH, SA, Sàrl, Sagl, KlG, KG, Ltd, Inc, LLC … | legal form |
| Address | [ADDRESS_n] |
Musterstrasse 12, 9000 St. Gallen, Rue de Lausanne 12, 1003 Lausanne, Via Nassa 8, 6900 Lugano, 12 Baker Street, CH-9000 St. Gallen |
street words, postcode |
| Date of birth | [DOB_n] |
a date after geb., Geburtsdatum, né le, nata il, date of birth … |
context word |
| Card number | [CARD_n] |
16 digits (4111 1111 1111 1111), Amex |
Luhn |
| Account | [ACCOUNT_n] |
PostFinance 80-12345-6; digits after Konto, compte, conto, account |
mod 10 / context word |
detect(text) returns the hits without changing the text.
- Exceptions:
keep=["info@gemeinde.example", re.compile(r".*@gemeinde\.example")]leaves exact values (case-insensitive) or values matching a pattern untouched. - Authorities: names such as "Steueramt St. Gallen" or "Handelsregisteramt Zürich" are not replaced (
protect_authorities=True, the default). - Streaming:
restore_stream(chunks, mapping)orRestorer(mapping)restore an answer that arrives in pieces, also when a placeholder is split between two pieces. - Presidio (optional):
anonymize(text, detectors=[presidio_detector(analyzer, language="de")])adds names in running text from Microsoft Presidio. Presidio is not a dependency and runs locally.
Measured on the test cases
tests/cases.py holds invented Swiss texts (payslip extract, client email, receipt, tax query, French and Italian letters, English mail, written-out variants, numbers with wrong check digits, and a text full of look-alikes that must not match). The numbers below come from python -m tests.measure. A test fails if the table is out of date, if a listed value slips through, or if the false alarms exceed what is listed here.
| Kind | Expected | Found | Missed | False alarms |
|---|---|---|---|---|
| AHV | 10 | 10 | 0 | 0 |
| IBAN | 10 | 10 | 0 | 0 |
| UID | 9 | 9 | 0 | 0 |
| PHONE | 15 | 15 | 0 | 0 |
| 6 | 6 | 0 | 0 | |
| PERSON | 10 | 10 | 0 | 0 |
| ORG | 4 | 4 | 0 | 0 |
| ADDRESS | 9 | 9 | 0 | 2 |
| DOB | 6 | 6 | 0 | 0 |
| CARD | 6 | 6 | 0 | 0 |
| ACCOUNT | 3 | 3 | 0 | 0 |
Read it as follows:
- "Missed" is zero for every kind: no value in these texts gets through.
- The two address false alarms are
4500 Euroand1200 Meterafter a comma: a four-digit number followed by a capitalised word looks like a postcode and a place. They are reported with low confidence (0.55), so you can filter by confidence. - Names, companies and addresses are heuristics (titles, a list of about 440 first names, legal forms, street words). They find less than a language model would and can over-report. A person written two ways (
Anna Muster, thenMuster) gets two placeholders. - This is a small invented set, not a benchmark on real documents. Test with your own (invented) samples before you rely on it.
Limits
lang ("de", "fr", "it", "en") is accepted and checked but has no effect: all four languages are always active, because Swiss texts mix them. Foreign IBANs, the old 11-digit AHV format and service numbers such as 0800 … are not recognised. Names of other origins than the first-name list are found with a title or through Presidio.
No tool finds all personal data. Treat the result as one protective layer, not as a guarantee of anonymity.
Development
uv venv && uv pip install -e . pytest ruff mypy
ruff check . && ruff format --check . && mypy && pytest
Metadata
Release files for isomorphism 0.2.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 | Actions |
|---|---|---|---|---|
| isomorphism-0.2.0.tar.gz | 27.1 kB | With provenance | Details for isomorphism-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| isomorphism-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for isomorphism-0.2.0-py3-none-any.whl |
Total release size: 44.9 kB
Release files / isomorphism-0.2.0.tar.gz
| Download URL | isomorphism-0.2.0.tar.gz |
|---|---|
| Size | 27.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d5799b445ca4fcf63b9581f428e3d06e1564915738b41f3228bdc5ac63ee388
|
|
BLAKE2b-256 checksum How to use checksums |
15e446a886567691c6bc11bbb4bf3c8feeb1eb922a2ef5f2ebd561fc299e1e2e
|
| 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 Nov 2, 2026.
Transparency logRelease files / isomorphism-0.2.0-py3-none-any.whl
| Download URL | isomorphism-0.2.0-py3-none-any.whl |
|---|---|
| Size | 17.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f517c15595aeb9345695348ddde2eac4fd61c230248d611e5c1f5b1e4c49756f
|
|
BLAKE2b-256 checksum How to use checksums |
5c475613e166cf9440b60c65d1e03aae4f5f9afba8c1ea524e79e4219428b774
|
| 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 Nov 2, 2026.
Transparency logSecurity for isomorphism 0.2.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 2 files in this release have been uploaded with provenance metadata.
Attestation for isomorphism-0.2.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 Nov 2, 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.