Skip to main content

idnumbers

Welcome to the idnumbers project! Our goal is to provide a python3 library for verifying and parsing national ID numbers. This library can be used to quickly and easily validate and extract information from ID numbers issued by various countries. It is an open source project, so feel free to use and contribute to it.

  • PyPI version
  • PyPI Stats

Features

The idnumbers library offers the following features:

  • Verification of national ID numbers: This feature allows you to check whether a given ID number is consistent with the respective country's format and checksum (not proof of issuance).
  • Parsing of national ID numbers: This feature allows you to extract useful information from an ID number such as the date of birth, gender, and more.
  • Support for multiple countries: The library currently supports several countries, with more being added in the future.
  • Easy to use and well-documented API: The library has a simple and intuitive API that makes it easy to use and well-documented for developers to understand.
  • Lightweight and efficient: The library is lightweight, meaning it does not have many dependencies, and it is efficient, meaning it does not consume much memory or processing power.

Installation

Installing idnumbers is easy! You can use pip, the package installer for Python, to install the latest version of the library. Simply open a terminal and run the following command:

It is highly recommended to install the library in a virtual environment, this will prevent conflicts with other python packages in your system. You can create a virtual environment using virtualenv or conda.

For virtualenv:

virtualenv <envname>
source <envname>/bin/activate

For Anaconda:

conda create --name <envname>
conda activate <envname>

Once you have activated your virtual environment, you can install idnumbers by running the following command:

pip install idnumbers

This installs the latest version of idnumbers; the library has no runtime dependencies.

You can also install a specific version of idnumbers by specifying the version number in the command, like this:

pip install idnumbers==<version>

See the changelog for the changes in each release.

Alternatively, you can install from source by cloning the git repository and installing it via

git clone https://github.com/Identique/idnumbers.git
cd idnumbers
pip install .

Please make sure you have the latest version of pip and setuptools installed before proceeding with the installation.

Once you have finished using the library, you can deactivate the virtual environment by running:

deactivate

or

conda deactivate

Using idnumbers with uv

Install uv first, then choose the workflow that fits your use case.

Project dependency

From an existing uv project directory:

uv add idnumbers

For a new project, first run:

uv init idnumbers-example
cd idnumbers-example

Then run uv add idnumbers to record the dependency and update the project's environment and lockfile.

Existing virtual environment

In an activated virtual environment, or a directory containing .venv:

uv pip install idnumbers

If you need a new environment, run uv venv first. To install a specific released version instead:

uv pip install idnumbers==1.13.0

One-off validation

uvx --with idnumbers python -c "from idnumbers.nationalid import TWN; print(TWN.NationalID.validate('A123456789'))"

This prints True. uv installs the dependency in a temporary uv-managed isolated environment, without adding it to an existing project's dependencies or requiring a manually activated environment. Validation checks format and checksum, not whether an ID was issued.

Standalone script

Save this as uv_example.py. The PEP 723 metadata declares its Python requirement and dependency:

# /// script
# requires-python = ">=3.9"
# dependencies = ["idnumbers"]
# ///

from idnumbers.nationalid import TWN

id_number = "A123456789"
assert TWN.NationalID.validate(id_number)
assert TWN.NationalID.parse(id_number) is not None
print(TWN.NationalID.validate(id_number))
print(TWN.NationalID.parse(id_number))

Run it with:

uv run uv_example.py

uv reads the metadata and manages an isolated environment for the script. See the uv scripts guide for details. The runnable companion example validates and parses three countries and fails if any result is unexpected. From this checkout, run uv run docs/examples/uv_script.py.

Usage

Verify National IDs

The idnumbers library makes it easy to verify national ID numbers. Here are some examples of how to use the library for verifying ID numbers.

from idnumbers.nationalid import AUS, NGA, ZAF

# Verify AUS tax file number (with checksum code)
taxfile_number = '32547689'
is_valid = AUS.TaxFileNumber.validate(taxfile_number)
if is_valid:
    print(f'{taxfile_number} is a valid AUS Tax File Number')
else:
    print(f'{taxfile_number} is an invalid AUS Tax File Number')

# Verify AUS driver license number
driver_license = '12 345 678'
is_valid = AUS.DriverLicenseNumber.validate(driver_license)
if is_valid:
    print(f'{driver_license} is a valid AUS Driver License Number')
else:
    print(f'{driver_license} is an invalid AUS Driver License Number')

# Verify AUS Medicare number (with checksum code)
medicare_number = '2123 45670 1'
is_valid = AUS.MedicareNumber.validate(medicare_number)
if is_valid:
    print(f'{medicare_number} is a valid AUS Medicare Number')
else:
    print(f'{medicare_number} is an invalid AUS Medicare Number')

# Verify NGA national id number
nga_nationalid = '12345678901'
is_valid = NGA.NationalID.validate(nga_nationalid)
if is_valid:
    print(f'{nga_nationalid} is a valid Nigerian National ID Number')
else:
    print(f'{nga_nationalid} is an invalid Nigerian National ID Number')

# Verify ZAF national ID number
zaf_nationalid = '7605300675088'
is_valid = ZAF.NationalID.validate(zaf_nationalid)
if is_valid:
    print(f'{zaf_nationalid} is a valid South African ID Number')
else:
    print(f'{zaf_nationalid} is an invalid South African ID Number')

assert AUS.TaxFileNumber.validate(taxfile_number)
assert AUS.DriverLicenseNumber.validate(driver_license)
assert AUS.MedicareNumber.validate(medicare_number)
assert NGA.NationalID.validate(nga_nationalid)
assert ZAF.NationalID.validate(zaf_nationalid)

These examples show how to use the idnumbers library to verify different types of national ID numbers for different countries. The validate method returns a boolean value indicating if the ID number is valid or not. All modules under nationalid package support the validate function.

You can also use the library to validate different types of ID numbers for different countries.

It is important to keep in mind that the library is only able to validate the format and the checksum of the ID number, not if it is an actual issued ID number.

Input handling

  • validate() returns a bool for any input and is not meant to raise. Anything that is not a str (None, numbers, bytes, lists, ...) gives False, an integer is never converted to a string, so validate(37605030299) is False.
  • The whole string must match the ID format. Nothing is stripped, so surrounding whitespace or a trailing newline gives False (the Irish PPS number is the one exception: its own format allows one trailing space).
  • Only ASCII digits 0-9 are accepted, other Unicode digits such as ١٢٣ or 123 give False.
  • Separators and letter case are type specific. Several ID types accept their usual written form (for example spaces, dashes or dots inside the number, as in 8924 7352 8038), and a few accept lower case letters. Most types are case sensitive. The METADATA.regexp of each type shows the accepted written form. A few types (for example the Czech TaxNumber, which drops every / anywhere in the input before matching) also normalize the input before checking it, so for those the regexp alone does not show everything they accept.
  • parse() returns None for invalid input.

Parse a primary ID by country

parse_id_info(country, id_number) uses the same country lookup and validation as idnumbers.validate(), selecting only the country's primary NationalID. It returns a frozen ParseSuccess or ParseFailure; use result.ok or isinstance(result, ParseSuccess) to narrow the result in typed code.

from idnumbers import FailureReason, ParseSuccess, parse_id_info

result = parse_id_info('tw', 'A123456789')
if result.ok:
    print(result.country_code, result.info['gender'])
else:
    print(result.reason, result.error_message)
assert isinstance(result, ParseSuccess)

# A valid primary ID need not contain extractable information.
result = parse_id_info('us', '012-12-0928')
assert not result.ok and result.reason == FailureReason.NOT_PARSABLE
print(result.reason.value)

On success, country_code is the resolved alpha-3 code and info is a fresh shallow dictionary matching the country class's parse() output, including its date and enum values. Result fields are frozen, but the dictionary itself is not immutable. The supplied id_number is preserved without normalization.

An unknown or non-string country gives UNSUPPORTED_COUNTRY with country_code=None. Invalid IDs have the same reason as validate() (best-effort granular reasons). A valid ID with no parser, or a parser returning None, gives NOT_PARSABLE. Validator/parser exceptions give VALIDATION_FAILED with error_message formatted as ExceptionType: message; this entry point does not raise. Failure results have reason and error_message, not info. The existing country-specific Cls.parse() methods and their return types are unchanged; use those classes when you need a secondary ID type.

Parse National IDs

The idnumbers library supports the parse function for certain national ID numbers, which allows you to easily extract detailed information from the ID number. The parse function is only available for national IDs for which the METADATA.parsable field is set to True.

For example, the South African ID Number and Taiwanese National ID Number support the parse function. Depending on the ID type, parsing can extract information such as date of birth, gender and citizenship.

Here is an example of how to use the parse function for a South African ID number:

from idnumbers.nationalid import ZAF

# Parse the national ID number
id_number = '7605300675088'
id_data = ZAF.NationalID.parse(id_number)
assert id_data is not None
assert id_data['yyyymmdd'].isoformat() == '1976-05-30'

# Access the date of birth
print(f'Date of birth: {id_data["yyyymmdd"]}')

# Access the gender
print(f'Gender: {id_data["gender"]}')

# Access the citizenship
print(f'Citizenship: {id_data["citizenship"]}')

This example shows how to use the parse method of the ZAF.NationalID class to extract the date of birth, gender and citizenship from a South African ID number. The parse method returns a dictionary with various fields, including yyyymmdd for date of birth, gender for gender and citizenship for citizenship.

Similarly, TWN.NationalID.parse() extracts information from a Taiwanese National ID. NGA.NationalID and AUS.MedicareNumber do not provide parse(); their metadata marks them non-parsable.

Please note that the returned parsed data may vary depending on the country and id type you are using. Also, it is important to keep in mind that the library is only able to validate the format and the checksum of the ID number, not if it is an actual issued ID number.

Look up a country

When the country is only known at run time, for example from a form field, the country registry finds the validator for you. The lookup functions accept an ISO 3166-1 alpha-2 or alpha-3 code in any letter case, and return None for anything they don't know instead of raising an error. The registry imports a country module the first time it is needed, so import idnumbers stays fast. register() adds a custom country to the registry.

import idnumbers

# Find the validator of a country by its alpha-2 or alpha-3 code, in any letter case
validator = idnumbers.get_validator('tw')
assert validator is not None
print(validator.validate('A123456789'))

# Describe a country
australia = idnumbers.get_country('AUS')
assert australia is not None
assert australia.alpha2 == 'AU'
print(australia.name, australia.alpha2)
print([id_type.__name__ for id_type in australia.id_types])

# An unknown country gives None
print(idnumbers.get_validator('XX') is None)

# List the supported countries
print(len(idnumbers.list_supported_countries()))

get_country() returns a CountryEntry with the alpha3 and alpha2 codes, the English name, the national_id class of the country and all its id_types. resolve_country() only turns a code into the alpha-3 code and imports nothing.

Validate by country

validate() checks an ID number of any supported country in one call. It takes the code of the country and the ID number, and returns a ValidationResult with is_valid, the alpha-3 country_code, the id_number as you passed it, the extracted_info of a valid ID number whose type can be parsed, and a reason for a failure. A result is truthy when it is valid, and validate() never raises: an unknown country or a malformed ID number gives an invalid result. Reasons are best effort: invalid_length, invalid_format, checksum_mismatch and invalid_birthdate are derived from metadata, definite checksum mismatches and traced calendar checks, in that order. Inconclusive checks and validator/parser exceptions retain validation_failed. The country classes and their validity rules are unchanged. The enum is non-exhaustive: callers should handle unknown future reasons with a generic fallback.

from idnumbers import validate, validate_many

# A valid ID number, with the data parsed from it
result = validate('tw', 'A123456789')
assert result.is_valid and result.extracted_info is not None
print(result.is_valid, result.country_code, result.extracted_info['gender'])

# An invalid result is falsy and says why
assert not validate('tw', 'A123456780')
print(bool(validate('tw', 'A123456780')))

# An unsupported country is reported, not raised
unsupported = validate('XX', 'A123456789')
assert unsupported.reason is not None
print(unsupported.reason.value)

# Validate several ID numbers, each with its own country
print([result.is_valid for result in validate_many([('tw', 'A123456789'), ('XX', '1')])])

Explain a failure of any ID type

failure_reason(id_class, id_number) also supports secondary types. It returns None for a valid ID and never raises. For diagnosis only, it considers both the original input and a candidate with whitespace and . - / ( ) removed. This does not normalize validation input or make a rejected ID valid. A computed checksum without a named checksum regexp group is inconclusive, so it retains the generic reason rather than guessing.

from idnumbers import FailureReason, failure_reason, validate
from idnumbers.nationalid import AUS

assert validate('TW', 'A12345').reason == FailureReason.INVALID_LENGTH
assert validate('TW', 'A123456788').reason == FailureReason.CHECKSUM_MISMATCH
assert validate('ZA', '7602300675085').reason == FailureReason.INVALID_BIRTHDATE
assert failure_reason(AUS.MedicareNumber, '2123 45670 1') is None
short = validate('TW', 'A12345')
assert short.reason is not None
print(short.reason.value)

Guides

Supported Countries

Here's the list of the countries we have implemented Country List. The list_supported_countries() function returns them at run time.

Metadata Structure of each ID

Here's the metadata definitions and how to use it in idnumbers: METADATA

Contribution

The idnumbers project is an open-source project and contributions from the community are always welcome. There are several ways you can contribute to the project:

  1. Use the library: The best way to contribute to the project is by using the library and providing feedback. This will help us understand how the library is being used and identify areas for improvement.
  2. Raise feature requests: If you have an idea for a new feature or an improvement, please raise an issue on GitHub. This will allow us to discuss the feature and plan its implementation.
  3. Implement new ID number parsers or validators: The library currently supports several countries, but there is always room for more. If you want to add support for a new country, you can submit a pull request with the implementation. Before that, please raise a new ID number reqeust to us.
  4. Report bugs: If you find a bug in the library, please raise an issue on GitHub with a detailed description of the problem.
  5. Improve documentation: The library has a well-documented API, but there is always room for improvement. If you find any errors or inconsistencies in the documentation, you can submit a pull request with the changes.

We appreciate any contributions, big or small, and we are always looking for ways to improve the library. If you have any questions or need help getting started, please feel free to reach out to us.

License

The idnumbers project is released in MIT license.

Thank You!

The idnumbers project provides a python3 library for verifying and parsing national ID numbers. It supports multiple countries and provides a simple and well-documented API. The library is open-source, and contributions from the community are always welcome. Whether you're using the library and providing feedback, raising feature requests, implementing new ID number parsers or validators or reporting bugs, you're helping the project to be better.

Thank you for considering using idnumbers in your project. We hope it will be useful for you and we are looking forward to your feedback and contributions

Primary-ID formatting and input masks

normalize_id(), format_id() and get_input_mask() use the registry's primary NationalID only (Australia: driver licence; Greece: identity card). Alpha-2/alpha-3 codes and registered aliases work.

from idnumbers import format_id, get_input_mask, normalize_id, validate

compact = normalize_id('BR', '111.444.777-35')
assert compact == '11144477735' and compact is not None
assert format_id('BRA', compact) == '111.444.777-35'
assert format_id('BRA', '123') is None
mask = get_input_mask('TW')
assert mask is not None
assert mask.country_code == 'TWN' and mask.masks == ('L#########',)
assert mask.pattern.fullmatch('A123456789') is not None
formatted = format_id('BRA', compact)
assert formatted is not None
assert validate('BR', formatted).is_valid
assert normalize_id('se', '811218+9876') == '811218+9876'
assert format_id('SWE', '811218+9876') == '811218+9876'
assert format_id('se', '19811218+9876') == '19811218+9876'
print(compact, mask.masks)

Normalization uppercases and removes whitespace (including U+200B–U+200D, U+2060 and U+FEFF) and . - / ( ). If no mask contains literal separators and the whitespace-free text already has an allowed slot count, those separators stay: Finland's century sign is significant. + is never removed. Sweden's minus is removed and reinserted by formatting. A single Swedish plus before the last four digits remains normalized and replaces the preferred minus literal when formatting either the 10-digit or 12-digit form. Normalization does not validate or reject unknown lengths. Formatting chooses the first mask with the right slot count, without checking characters or validity. Unsupported countries and non-string IDs return None; formatting also returns None if no layout fits. Custom countries without masks use metadata length bounds for formatting, and get_input_mask() returns None.

InputMask is frozen, with country_code, tuple masks, and compiled pattern. Patterns match the whole input, case-sensitively: # means [0-9], L means [A-Z], X means [A-Z0-9], and * means Unicode non-whitespace. Other mask characters are escaped literals. This is a UI layout pattern, not the validator's regexp. It describes literal preferred layouts, not every accepted input: Sweden's masks and pattern retain -, even though formatting preserves the significant + in plus forms. Its intentional ASCII letter restriction excludes Greek identity-card letters even though the country validator accepts them and formatting preserves them unchanged. The metadata example uses accepted Latin AB-123456. Length-only formatting can produce characters that do not match the pattern; call validate() separately.

Measured over all 78 built-in primary metadata examples, normalized input validates in 74 countries; CHE, CHL, KOR and USA reject their normalized example. All 78 formatted examples validate. These are example measurements, not a promise for arbitrary inputs. Country validators, parsers and checksum methods are unchanged. See metadata masks and the formatting API documentation.

Metadata

Release files for idnumbers 1.15.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for idnumbers 1.15.0
File Size Uploaded
idnumbers-1.15.0.tar.gz 191.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for idnumbers 1.15.0
File Interpreter ABI Platform
idnumbers-1.15.0-py3-none-any.whl Python 3 none any Details

Total release size: 432.6 kB

Release files / idnumbers-1.15.0.tar.gz

Download URL idnumbers-1.15.0.tar.gz
Size 191.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0164cee0702ade8221e2043cca54831d422e33c76c388ac3c2bd45f31e21190d
BLAKE2b-256 checksum
How to use checksums
ce6b3c65583da65056695f2dbcd050154b3cb542670fecaca1f5a0ac2c1bf864
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / idnumbers-1.15.0-py3-none-any.whl

Download URL idnumbers-1.15.0-py3-none-any.whl
Size 241.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8298e33856cadb44a546ba962bcc2865efd1d39a752a2d96837c569cff94bdf7
BLAKE2b-256 checksum
How to use checksums
8a1fc74cd94e1644eb87e74da392d90841035ac77d39d01c7b25d9839186ee20
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

1.20.0

2 release files

1.19.0

2 release files

1.18.0

2 release files

1.17.0

2 release files

1.16.0

2 release files

This release

1.15.0 This release

2 release files

1.11.0

2 release files

1.10.0

2 release files

1.9.1

2 release files

1.9.0

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page