Library / SDK
arthurdejong/python-stdnum avatar
arthurdejong/python-stdnum

python-stdnum: validating tax IDs, IBANs and national numbers in Python

A Python library to provide functions to handle, parse and validate standard numbers.

605 stars239 forksPythonLGPL-2.1

At a glance

What is it?
python-stdnum is a Python library of per-country modules that parse, validate and reformat standardized numbers, from Argentine CUIT to Spanish NIF and Austrian VAT numbers. It covers structure and check digits, not identity or registry lookup.
Who is it for?
Adopt python-stdnum when a schema check or a form field needs the correct check-digit and formatting rules for a specific country's number, and skip it when you need to know whether a number was ever issued or belongs to a real person, since it does no registry lookup. Before depending on it, check that the module for your country exists in the stdnum/ tree, then read its validation rules in the docs directory, and pin a release rather than tracking master.
Can I use it commercially?
Yes, with conditions. LGPL-2.1 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 69 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One module per country, one function per format

The problem this library solves is narrow and real: identifiers that look arbitrary to a string library but carry structure. A Spanish NIF, an Argentine CUIT, a Belgian enterprise number and a Chilean RUT each have a fixed length, an alphabet, a check digit computed by a national formula, and a conventional formatting style. If you write that logic yourself, you get it wrong somewhere, and the wrong answer is a rejected payment or a corrupted customer record.

python-stdnum stores each country's rules in its own module inside the stdnum/ package. The supported list is long and specific rather than generic: NRT for Andorra, NIPT and NUIS for Albania, CBU and CUIT and DNI for Argentina, ABN, ACN and TFN for Australia, BIC for ISO 9362 codes, Bitcoin addresses, CNPJ and CPF for Brazil, ESR and Swiss VAT numbers, CUPS and Referencia Catastral for Spain, NACE and OSS and the European VAT number, HETU and Y-tunnus for Finland, SIREN and SIRET and NIR for France, and a great deal more. The README states the inclusion rule plainly: basically any number or code that has some validation mechanism available or some common formatting is eligible for inclusion.

That eligibility rule is also the honest boundary. This is a format library, not a verification service.

Parsing, validation and reformatting are separate operations

The library's own framing gives three verbs: parse, validate and reformat. Each country module implements them for the formats it covers, and the naming conventions across countries are consistent enough that code written for one jurisdiction transfers to another. A module exposes its formats as attributes, for example a country module for Belgium carries the Belgian IBAN and the Belgian social security number, and the Belgian national number under its usual short names.

The design consequence matters for how you write validation. The check digit is the mechanical part and it is where most of the value sits, because it catches transcription errors and typos in forms and file imports without a single network call. Formatting is the part that makes stored data comparable across systems, since the same number can be entered with spaces, dashes or none at all. Parsing is what normalizes an input string into the components a format is built from, such as separating a check character from the body before the algorithm runs.

Because each format is its own module rather than a row in a table, a country can carry quirks that a generic design would flatten. Spanish NIE numbers for foreign residents, for instance, exist alongside DNI and NIF in the same national system, and they are listed separately in the supported formats.

Installing from the packaging metadata rather than a documented pip line

The project ships a classic setuptools package. setup.py reads the README as the long description, pulls the version from the package itself so there is a single source of truth for it, and declares the distribution under a plain name:

python
setup(
    name='python-stdnum',
    version=stdnum.__version__,

The packaging file also declares the author as Arthur de Jong, the project URL as https://arthurdejong.org/python-stdnum/, and the licence as LGPL. The long description is read from README.md with markdown content type set, which tells you the README is the front page of the distribution and not an afterthought. The distribution name matches the import name, so the package you install and the module you import are both stdnum.

The repository does not spell out a shell command for installation in what is documented here, so rather than invent one, the practical route is the project's own homepage at https://arthurdejong.org/python-stdnum/ and the documentation link listed in the packaging metadata, https://arthurdejong.org/python-stdnum/doc/. The source tree supports the usual workflow as well: setup.py, setup.cfg, tox.ini, a tests/ directory and a ChangeLog are all present, so a checkout can be run and tested directly with the tooling the project already configures.

What the source tree reveals about maintenance work

The top-level layout says more about how this project is worked on than the feature list does. There is an update/ directory, which is where the reference data behind these formats is refreshed when a country changes its rules. There is a scripts/ directory for helper tooling and an online_check/ directory that suggests a way of checking formats against live services. docs/ holds the documentation tree, tests/ the test suite, and ChangeLog and NEWS record what changed between versions.

That structure implies a recurring kind of cost. Format rules are not static. Tax authorities revise digits, lengths and alphabets, and each revision has to be picked up in the relevant country module, reflected in the tests and noted in the ChangeLog. So the dependency you take on is not only the code but the maintenance rhythm behind it.

The repository is not archived. The last push was on 2026-08-15, and the recent releases were 2.2 on 2026-01-04, 2.1 on 2025-05-17 and 2.0 on 2025-05-05. The gap between the release cadence and the push date is worth noticing: the code keeps moving between tagged versions, so pinning to a release and pinning to master are different operational choices with different upgrade paths.

Where a check-digit library stops being enough

The most important limitation is the one the library does not try to solve. Validation here means the number is structurally plausible: right length, permitted characters, and a check digit that matches the national formula. A number can pass every check in this library and still never have been issued, or belong to a person who died years ago, or have been copied from someone else's document. Anyone using it for identity verification needs a different tool entirely, and the project does not claim to provide one.

Two more constraints are worth naming. First, country coverage is uneven in practice, even though the supported list is long: a format nobody has contributed a module for simply is not there, and the README's list of formats is the authoritative check. Second, tax and identity numbers are personal data, so a library that encourages collecting, storing and normalizing them pulls the usual handling obligations along with it, whatever the licence says.

The licence is LGPL-2.1, which is a real consideration for embedding. The header in setup.py states the library is free software, that you can redistribute and modify it under the terms of the GNU Lesser General Public License version 2.1 or later, and that it is distributed in the hope that it will be useful but without any warranty.

Comparing it with a regex or a one-off checksum

The obvious alternative is to keep doing what many projects do: a regular expression per country, or a small hand-written checksum function for the two or three formats you actually care about. For a single national tax number, that is genuinely competitive. A regex needs no dependency, it is instant, and its failure mode is obvious.

The difference shows up at the second format and beyond. A regex says nothing about whether the check digit is right, so it accepts transposed digits and wrong lengths that look plausible. A hand-written checksum encodes the algorithm, but then each additional country means another hand-written algorithm, another test, and another place to get an off-by-one wrong. python-stdnum moves that work into per-country modules that are already written, already tested and already named the way the issuing authority names them.

The other approach, calling the authority's own validation service, is a genuinely different trade-off rather than a better version of the same thing. It answers a stronger question, since a registry can tell you whether a number was issued, but it needs network access, credentials, and a response time you cannot control in a form validation path. A format library is fast, offline and predictable, which is exactly why it is useful as the first filter before anything heavier is considered.

Editorial conclusion

Adopt python-stdnum when a schema check or a form field needs the correct check-digit and formatting rules for a specific country's number, and skip it when you need to know whether a number was ever issued or belongs to a real person, since it does no registry lookup. Before depending on it, check that the module for your country exists in the stdnum/ tree, then read its validation rules in the docs directory, and pin a release rather than tracking master.

Frequently asked questions

What does python-stdnum do and what does it not do?

It parses, validates and reformats standardized numbers and codes, with a module per country format, for example CUIT for Argentina or SIRET for France. It checks structure and check digits only and does not look up whether a number was actually issued.

Which countries does python-stdnum support?

The README lists a large set of formats, including Argentine CBU and CUIT, Belgian enterprise numbers, Brazilian CPF and CNPJ, Spanish DNI and NIE and CUPS, Finnish HETU and Y-tunnus, French SIREN and SIRET, and the European VAT number. The list in the README is the authoritative check for a specific format.

How is python-stdnum licensed for use in a commercial product?

The repository declares the LGPL licence, and setup.py states you can redistribute and modify the library under the GNU Lesser General Public License version 2.1 or later. It is distributed without any warranty, so you need to read the licence terms for your own linking model rather than take that summary as advice.

How often is python-stdnum updated?

The last push was on 2026-08-15, and the recent releases were 2.2 on 2026-01-04, 2.1 on 2025-05-17 and 2.0 on 2025-05-05. Country formats change over time, so the update/ and scripts/ directories are where that refresh work lives.

Official sources

  1. arthurdejong/python-stdnum on GitHub
  2. License: LGPL-2.1
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/arthurdejong-python-stdnum.svg)](https://hysenlabs.com/projects/arthurdejong-python-stdnum)

Was this useful?