Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions stdnum/eu/eori.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# eori.py - functions for handling Economic Operators Registration and Identification numbers
# coding: utf-8
#
# Copyright (C) 2026 Sergi Almacellas Abellana
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see <https://www.gnu.org/licenses/>.

"""EORI (Economic Operators Registration and Identification number).

An EORI number identifies economic operators in customs procedures. It starts
with a two-letter country code followed by up to 15 alphanumeric characters.
This module validates only the common EORI format; national EORI formats are
not checked.

More information:

* https://taxation-customs.ec.europa.eu/customs/customs-procedures-import-and-export/customs-operations/economic-operators-registration-and-identification-number-eori_en # noqa: E501

>>> validate('ES B64717838')
'ESB64717838'
>>> validate('ESB12345678!')
Traceback (most recent call last):
...
InvalidFormat: ...
"""

from __future__ import annotations

from string import ascii_uppercase, digits

from stdnum.exceptions import *
from stdnum.util import clean


_alphabet = ascii_uppercase + digits


def compact(number: str) -> str:
"""Convert the number to the minimal representation."""
return clean(number, ' -').strip().upper()


def validate(number: str) -> str:
"""Check if the number has a valid EORI format."""
number = compact(number)
if not 3 <= len(number) <= 17:
raise InvalidLength()
if (not all(x in ascii_uppercase for x in number[:2]) or
not all(x in _alphabet for x in number[2:])):
raise InvalidFormat()
return number


def is_valid(number: str) -> bool:
"""Check if the number has a valid EORI format."""
try:
return bool(validate(number))
except ValidationError:
return False
46 changes: 46 additions & 0 deletions tests/test_eu_eori.doctest
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
test_eu_eori.doctest - more detailed doctests for the stdnum.eu.eori module

Copyright (C) 2026 Sergi Almacellas Abellana

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see <https://www.gnu.org/licenses/>.

This file contains more detailed doctests for the stdnum.eu.eori module.

>>> from stdnum.eu import eori
>>> from stdnum.exceptions import *

EORI numbers are normalised and have a two-letter country code followed by at
most 15 alphanumeric characters.

>>> eori.compact('es- b64717838')
'ESB64717838'
>>> eori.validate('ESB64717838')
'ESB64717838'

Invalid lengths and characters are rejected.

>>> eori.validate('ES')
Traceback (most recent call last):
...
InvalidLength: ...
>>> eori.validate('DE1234567890123456')
Traceback (most recent call last):
...
InvalidLength: ...
>>> eori.validate('E112345678')
Traceback (most recent call last):
...
InvalidFormat: ...
>>> eori.is_valid('ES123/456')
False
Loading