diff --git a/pyproject.toml b/pyproject.toml index 20dca945..b7b6261c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ bandit = "^1.7.5" [tool.poetry.group.tests.dependencies] pytest = "^7.2.2" -[tool.poetry.group.type-lint-fmt.dependencies] +[tool.poetry.group.type-lint-format.dependencies] black = "^23.1.0" flake8 = "^5.0.4" flake8-docstrings = "^1.7.0" @@ -56,26 +56,24 @@ pyright = "^1.1.299" requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" -################################ -# formatters, linters, testers # -################################ +####################### +# misc configurations # +####################### [tool.black] line-length = 100 target-version = ["py38", "py39", "py310", "py311"] -extend-exclude = "i18n" [tool.bandit] exclude_dirs = [".github", ".pytest_cache", ".tox", ".vscode", "site", "tests"] [tool.isort] ensure_newline_before_comments = true -extend_skip_glob = ["**/i18n/*"] force_grid_wrap = 0 force_sort_within_sections = true +import_heading_localfolder = "local" import_heading_stdlib = "standard" import_heading_thirdparty = "external" -import_heading_localfolder = "local" include_trailing_comma = true known_local_folder = ["validators"] length_sort = true @@ -89,7 +87,7 @@ use_parentheses = true [tool.pyright] include = ["validators", "tests"] -exclude = ["**/__pycache__", ".pytest_cache/", ".tox/", "site/", "**/i18n/*"] +exclude = ["**/__pycache__", ".pytest_cache/", ".tox/", "site/"] pythonVersion = "3.8" pythonPlatform = "All" typeCheckingMode = "strict" @@ -100,22 +98,22 @@ legacy_tox_ini = """ min_version = 4.0 env_list = py{38,39,310,311} - fmt_black - fmt_isort + format_black + format_isort lint - type + type_check [testenv] description = run unit tests deps = pytest commands = pytest tests/ -[testenv:fmt_black] +[testenv:format_black] description = run formatter deps = black commands = black . -[testenv:fmt_isort] +[testenv:format_isort] description = run formatter deps = isort commands = isort . @@ -125,7 +123,7 @@ description = run linters deps = flake8 commands = flake8 -[testenv:type] +[testenv:type_check] description = run type checker deps = pyright diff --git a/setup.cfg b/setup.cfg index e11e38ee..78ad897d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,5 +2,5 @@ [flake8] docstring-convention = google -exclude = __pycache__,.github,.pytest_cache,.tox,.venv,.vscode,site,i18n +exclude = __pycache__,.github,.pytest_cache,.tox,.venv,.vscode,site max-line-length = 100 diff --git a/tests/i18n/__init__.py b/tests/i18n/__init__.py index e69de29b..d0d2ab48 100644 --- a/tests/i18n/__init__.py +++ b/tests/i18n/__init__.py @@ -0,0 +1,4 @@ +"""Test i18n.""" +# -*- coding: utf-8 -*- + +# isort: skip_file diff --git a/tests/i18n/test_es.py b/tests/i18n/test_es.py index 3d955c7b..a555ce9c 100644 --- a/tests/i18n/test_es.py +++ b/tests/i18n/test_es.py @@ -1,105 +1,132 @@ +"""Test i18n/es.""" # -*- coding: utf-8 -*- + +# external import pytest -from validators import ValidationFailure -from validators.i18n.es import es_cif, es_doi, es_nie, es_nif +# local +from validators import es_nif, es_nie, es_doi, es_cif, ValidationFailure -@pytest.mark.parametrize(('value',), [ - ('B25162520',), - ('U4839822F',), - ('B96817697',), - ('P7067074J',), - ('Q7899705C',), - ('C75098681',), - ('G76061860',), - ('C71345375',), - ('G20558169',), - ('U5021960I',), -]) -def test_returns_true_on_valid_cif(value): +@pytest.mark.parametrize( + ("value",), + [ + ("B25162520",), + ("U4839822F",), + ("B96817697",), + ("P7067074J",), + ("Q7899705C",), + ("C75098681",), + ("G76061860",), + ("C71345375",), + ("G20558169",), + ("U5021960I",), + ], +) +def test_returns_true_on_valid_cif(value: str): + """Test returns true on valid cif.""" assert es_cif(value) -@pytest.mark.parametrize(('value',), [ - ('12345',), - ('ABCDEFGHI',), - ('Z5021960I',), -]) -def test_returns_false_on_invalid_cif(value): +@pytest.mark.parametrize( + ("value",), + [ + ("12345",), + ("ABCDEFGHI",), + ("Z5021960I",), + ], +) +def test_returns_false_on_invalid_cif(value: str): + """Test returns false on invalid cif.""" result = es_cif(value) assert isinstance(result, ValidationFailure) -@pytest.mark.parametrize(('value',), [ - ('X0095892M',), - ('X8868108K',), - ('X2911154K',), - ('Y2584969J',), - ('X7536157T',), - ('Y5840388N',), - ('Z2915723H',), - ('Y4002236C',), - ('X7750702R',), - ('Y0408759V',), -]) -def test_returns_true_on_valid_nie(value): +@pytest.mark.parametrize( + ("value",), + [ + ("X0095892M",), + ("X8868108K",), + ("X2911154K",), + ("Y2584969J",), + ("X7536157T",), + ("Y5840388N",), + ("Z2915723H",), + ("Y4002236C",), + ("X7750702R",), + ("Y0408759V",), + ], +) +def test_returns_true_on_valid_nie(value: str): + """Test returns true on valid nie.""" assert es_nie(value) -@pytest.mark.parametrize(('value',), [ - ('K0000023T',), - ('L0000024R',), - ('M0000025W',), - ('00000026A',), - ('00000027G',), - ('00000028M',), - ('00000029Y',), - ('00000030F',), - ('00000031P',), - ('00000032D',), - ('00000033X',), - ('00000034B',), - ('00000035N',), - ('00000036J',), - ('00000037Z',), - ('00000038S',), - ('00000039Q',), - ('00000040V',), - ('00000041H',), - ('00000042L',), - ('00000043C',), - ('00000044K',), - ('00000045E',), -]) -def test_returns_true_on_valid_nif(value): +@pytest.mark.parametrize( + ("value",), + [ + ("K0000023T",), + ("L0000024R",), + ("M0000025W",), + ("00000026A",), + ("00000027G",), + ("00000028M",), + ("00000029Y",), + ("00000030F",), + ("00000031P",), + ("00000032D",), + ("00000033X",), + ("00000034B",), + ("00000035N",), + ("00000036J",), + ("00000037Z",), + ("00000038S",), + ("00000039Q",), + ("00000040V",), + ("00000041H",), + ("00000042L",), + ("00000043C",), + ("00000044K",), + ("00000045E",), + ], +) +def test_returns_true_on_valid_nif(value: str): + """Test returns true on valid nif.""" assert es_nif(value) -@pytest.mark.parametrize(('value',), [ - ('12345',), - ('X0000000T',), - ('00000000T',), - ('00000001R',), -]) -def test_returns_false_on_invalid_nif(value): +@pytest.mark.parametrize( + ("value",), + [ + ("12345",), + ("X0000000T",), + ("00000000T",), + ("00000001R",), + ], +) +def test_returns_false_on_invalid_nif(value: str): + """Test returns false on invalid nif.""" result = es_nif(value) assert isinstance(result, ValidationFailure) -@pytest.mark.parametrize(('value',), [ - # CIFs - ('B25162520',), - ('U4839822F',), - ('B96817697',), - # NIEs - ('X0095892M',), - ('X8868108K',), - ('X2911154K',), - # NIFs - ('26643189N',), - ('07060225F',), - ('49166693F',), -]) -def test_returns_true_on_valid_doi(value): +@pytest.mark.parametrize( + ("value",), + [ + # CIFs + ("B25162520",), + ("U4839822F",), + ("B96817697",), + # NIEs + ("X0095892M",), + ("X8868108K",), + ("X2911154K",), + # NIFs + ("26643189N",), + ("07060225F",), + ("49166693F",), + ], +) +def test_returns_true_on_valid_doi(value: str): + """Test returns true on valid doi.""" assert es_doi(value) diff --git a/tests/i18n/test_fi.py b/tests/i18n/test_fi.py index b900bc4e..a8e53f9e 100644 --- a/tests/i18n/test_fi.py +++ b/tests/i18n/test_fi.py @@ -1,60 +1,82 @@ +"""Test i18n/es.""" # -*- coding: utf-8 -*- + +# external import pytest -from validators import ValidationFailure +# local from validators.i18n.fi import fi_business_id, fi_ssn +from validators import ValidationFailure -@pytest.mark.parametrize(('value',), [ - ('2336509-6',), # Supercell - ('0112038-9',), # Fast Monkeys - ('2417581-7',), # Nokia -]) -def test_returns_true_on_valid_business_id(value): +@pytest.mark.parametrize( + ("value",), + [ + ("2336509-6",), # Supercell + ("0112038-9",), # Fast Monkeys + ("2417581-7",), # Nokia + ], +) +def test_returns_true_on_valid_business_id(value: str): + """Test returns true on valid business id.""" assert fi_business_id(value) -@pytest.mark.parametrize(('value',), [ - (None,), - ('',), - ('1233312312',), - ('1333333-8',), - ('1231233-9',), -]) -def test_returns_failed_validation_on_invalid_business_id(value): +@pytest.mark.parametrize( + ("value",), + [ + (None,), + ("",), + ("1233312312",), + ("1333333-8",), + ("1231233-9",), + ], +) +def test_returns_failed_validation_on_invalid_business_id(value: str): + """Test returns failed validation on invalid business id.""" assert isinstance(fi_business_id(value), ValidationFailure) -@pytest.mark.parametrize(('value',), [ - ('010190-002R',), - ('010101-0101',), - ('010101+0101',), - ('010101A0101',), - ('010190-900P',), -]) -def test_returns_true_on_valid_ssn(value): +@pytest.mark.parametrize( + ("value",), + [ + ("010190-002R",), + ("010101-0101",), + ("010101+0101",), + ("010101A0101",), + ("010190-900P",), + ("020516C903K",), + ("010594Y9032",), + ], +) +def test_returns_true_on_valid_ssn(value: str): + """Test returns true on valid ssn.""" assert fi_ssn(value) -@pytest.mark.parametrize(('value',), [ - (None,), - ('',), - ('010190-001P',), # Too low serial - ('010190-000N',), # Too low serial - ('000190-0023',), # Invalid day - ('010090-002X',), # Invalid month - ('010190-002r',), # Invalid checksum - ('101010-0102',), - ('10a010-0101',), - ('101010-0\xe401',), - ('101010b0101',) -]) -def test_returns_failed_validation_on_invalid_ssn(value): +@pytest.mark.parametrize( + ("value",), + [ + (None,), + ("",), + ("010190-001P",), # Too low serial + ("010190-000N",), # Too low serial + ("000190-0023",), # Invalid day + ("010090-002X",), # Invalid month + ("010190-002r",), # Invalid checksum + ("101010-0102",), + ("10a010-0101",), + ("101010-0\xe401",), + ("101010b0101",), + ("0205169C03K",), + ("0105949Y032",), + ], +) +def test_returns_failed_validation_on_invalid_ssn(value: str): + """Test returns failed validation on invalid_ssn.""" assert isinstance(fi_ssn(value), ValidationFailure) def test_returns_failed_validation_on_temporal_ssn_when_not_allowed(): - assert isinstance( - fi_ssn('010190-900P', allow_temporal_ssn=False), - ValidationFailure - ) + """Test returns failed validation on temporal-ssn when not allowed.""" + assert isinstance(fi_ssn("010190-900P", allow_temporal_ssn=False), ValidationFailure) diff --git a/validators/__init__.py b/validators/__init__.py index 27cab535..c4ccee2e 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -15,7 +15,6 @@ from .email import email from .hashes import md5, sha1, sha224, sha256, sha512 from .hostname import hostname -from .i18n import fi_business_id, fi_ssn from .iban import iban from .ip_address import ipv4, ipv6 from .length import length @@ -25,6 +24,8 @@ from .utils import validator, ValidationFailure from .uuid import uuid +from .i18n import es_cif, es_doi, es_nie, es_nif, fi_business_id, fi_ssn + __all__ = ( "amex", "between", @@ -34,8 +35,6 @@ "discover", "domain", "email", - "fi_business_id", - "fi_ssn", "hostname", "iban", "ipv4", @@ -56,6 +55,13 @@ "ValidationFailure", "validator", "visa", + # i18n + "es_cif", + "es_doi", + "es_nie", + "es_nif", + "fi_business_id", + "fi_ssn", ) __version__ = "0.20.0" diff --git a/validators/i18n/__init__.py b/validators/i18n/__init__.py index 12775c6c..39b25382 100644 --- a/validators/i18n/__init__.py +++ b/validators/i18n/__init__.py @@ -1,4 +1,10 @@ -# TODO: remove, let the user import it if they really want it -from .fi import fi_business_id, fi_ssn # noqa +"""Country.""" +# -*- coding: utf-8 -*- -__all__ = ('fi_business_id', 'fi_ssn') +# isort: skip_file + +# local +from .es import es_cif, es_doi, es_nie, es_nif +from .fi import fi_business_id, fi_ssn + +__all__ = ("fi_business_id", "fi_ssn", "es_cif", "es_doi", "es_nie", "es_nif") diff --git a/validators/i18n/es.py b/validators/i18n/es.py index ed2e2a63..eb5a8427 100644 --- a/validators/i18n/es.py +++ b/validators/i18n/es.py @@ -1,200 +1,186 @@ +"""Spain.""" # -*- coding: utf-8 -*- -from validators.utils import validator - -__all__ = ('es_cif', 'es_nif', 'es_nie', 'es_doi',) +# standard +from typing import Dict, Set -def nif_nie_validation(doi, number_by_letter, special_cases): - """ - Validate if the doi is a NIF or a NIE. - :param doi: DOI to validate. - :return: boolean if it's valid. - """ - doi = doi.upper() - if doi in special_cases: - return False +# local +from validators.utils import validator - table = 'TRWAGMYFPDXBNJZSQVHLCKE' - if len(doi) != 9: +def _nif_nie_validation(value: str, number_by_letter: Dict[str, str], special_cases: Set[str]): + """Validate if the doi is a NIF or a NIE.""" + if value in special_cases or len(value) != 9: return False - - control = doi[8] - - # If it is not a DNI, convert the first letter to the corresponding - # digit - numbers = number_by_letter.get(doi[0], doi[0]) + doi[1:8] - - return numbers.isdigit() and control == table[int(numbers) % 23] + value = value.upper() + table = "TRWAGMYFPDXBNJZSQVHLCKE" + # If it is not a DNI, convert the first + # letter to the corresponding digit + numbers = number_by_letter.get(value[0], value[0]) + value[1:8] + # doi[8] is control + return numbers.isdigit() and value[8] == table[int(numbers) % 23] @validator -def es_cif(doi): - """ - Validate a Spanish CIF. +def es_cif(value: str, /): + """Validate a Spanish CIF. Each company in Spain prior to 2008 had a distinct CIF and has been - discontinued. For more information see `wikipedia.org/cif`_. + discontinued. For more information see [wikipedia.org/cif][1]. The new replacement is to use NIF for absolutely everything. The issue is - that there are "types" of NIFs now: company, person[citizen vs recident] + that there are "types" of NIFs now: company, person [citizen or resident] all distinguished by the first character of the DOI. For this reason we - will continue to call CIF NIFs that are used for companies. - - This validator is based on `generadordni.es`_. + will continue to call CIFs NIFs, that are used for companies. - .. _generadordni.es: - https://generadordni.es/ + This validator is based on [generadordni.es][2]. - .. _wikipedia.org/cif: - https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal - - Examples:: + [1]: https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal + [2]: https://generadordni.es/ + Examples: >>> es_cif('B25162520') - True - + # Output: True >>> es_cif('B25162529') - ValidationFailure(func=es_cif, args=...) + # Output: ValidationFailure(func=es_cif, args=...) - .. versionadded:: 0.13.0 + Args: + value: + DOI string which is to be validated. - :param doi: DOI to validate - """ - doi = doi.upper() + Returns: + (Literal[True]): + If `value` is a valid DOI string. + (ValidationFailure): + If `value` is an invalid DOI string. - if len(doi) != 9: + > *New in version 0.13.0*. + """ + if not value or len(value) != 9: return False - - table = 'JABCDEFGHI' - first_chr = doi[0] - doi_body = doi[1:8] - control = doi[8] - + value = value.upper() + table = "JABCDEFGHI" + first_chr = value[0] + doi_body = value[1:8] + control = value[8] if not doi_body.isdigit(): return False - - odd_result = 0 - even_result = 0 - for index, char in enumerate(doi_body): - if index % 2 == 0: - # Multiply each each odd position doi digit by 2 and sum it all - # together - odd_result += sum(map(int, str(int(char) * 2))) - else: - even_result += int(char) - - res = (10 - (even_result + odd_result) % 10) % 10 - - if first_chr in 'ABEH': # Number type + res = ( + 10 + - sum( + # Multiply each positionally even doi + # digit by 2 and sum it all together + sum(map(int, str(int(char) * 2))) if index % 2 == 0 else int(char) + for index, char in enumerate(doi_body) + ) + % 10 + ) % 10 + if first_chr in "ABEH": # Number type return str(res) == control - elif first_chr in 'PSQW': # Letter type + if first_chr in "PSQW": # Letter type return table[res] == control - elif first_chr not in 'CDFGJNRUV': - return False - - return control == str(res) or control == table[res] + return control in {str(res), table[res]} if first_chr in "CDFGJNRUV" else False @validator -def es_nif(doi): - """ - Validate a Spanish NIF. +def es_nif(value: str, /): + """Validate a Spanish NIF. Each entity, be it person or company in Spain has a distinct NIF. Since we've designated CIF to be a company NIF, this NIF is only for person. - For more information see `wikipedia.org/nif`_. - - This validator is based on `generadordni.es`_. - - .. _generadordni.es: - https://generadordni.es/ + For more information see [wikipedia.org/nif][1]. This validator + is based on [generadordni.es][2]. - .. _wikipedia.org/nif: - https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal - - Examples:: + [1]: https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal + [2]: https://generadordni.es/ + Examples: >>> es_nif('26643189N') - True - + # Output: True >>> es_nif('26643189X') - ValidationFailure(func=es_nif, args=...) + # Output: ValidationFailure(func=es_nif, args=...) - .. versionadded:: 0.13.0 + Args: + value: + DOI string which is to be validated. - :param doi: DOI to validate - """ - number_by_letter = {'L': '0', 'M': '0', 'K': '0'} - special_cases = ['X0000000T', '00000000T', '00000001R'] - return nif_nie_validation(doi, number_by_letter, special_cases) + Returns: + (Literal[True]): + If `value` is a valid DOI string. + (ValidationFailure): + If `value` is an invalid DOI string. - -@validator -def es_nie(doi): + > *New in version 0.13.0*. """ - Validate a Spanish NIE. - - The NIE is a tax identification number in Spain, known in Spanish as the - NIE, or more formally the Número de identidad de extranjero. For more - information see `wikipedia.org/nie`_. + number_by_letter = {"L": "0", "M": "0", "K": "0"} + special_cases = {"X0000000T", "00000000T", "00000001R"} + return _nif_nie_validation(value, number_by_letter, special_cases) - This validator is based on `generadordni.es`_. - .. _generadordni.es: - https://generadordni.es/ +@validator +def es_nie(value: str, /): + """Validate a Spanish NIE. - .. _wikipedia.org/nie: - https://es.wikipedia.org/wiki/N%C3%BAmero_de_identidad_de_extranjero + The NIE is a tax identification number in Spain, known in Spanish + as the NIE, or more formally the Número de identidad de extranjero. + For more information see [wikipedia.org/nie][1]. This validator + is based on [generadordni.es][2]. - Examples:: + [1]: https://es.wikipedia.org/wiki/N%C3%BAmero_de_identidad_de_extranjero + [2]: https://generadordni.es/ + Examples: >>> es_nie('X0095892M') - True - + # Output: True >>> es_nie('X0095892X') - ValidationFailure(func=es_nie, args=...) + # Output: ValidationFailure(func=es_nie, args=...) - .. versionadded:: 0.13.0 + Args: + value: + DOI string which is to be validated. - :param doi: DOI to validate - """ - number_by_letter = {'X': '0', 'Y': '1', 'Z': '2'} - special_cases = ['X0000000T'] + Returns: + (Literal[True]): + If `value` is a valid DOI string. + (ValidationFailure): + If `value` is an invalid DOI string. + > *New in version 0.13.0*. + """ + number_by_letter = {"X": "0", "Y": "1", "Z": "2"} # NIE must must start with X Y or Z - if not doi or doi[0] not in number_by_letter.keys(): - return False - - return nif_nie_validation(doi, number_by_letter, special_cases) + if value and value[0] in number_by_letter: + return _nif_nie_validation(value, number_by_letter, {"X0000000T"}) + return False @validator -def es_doi(doi): - """ - Validate a Spanish DOI. +def es_doi(value: str, /): + """Validate a Spanish DOI. - A DOI in spain is all NIF / CIF / NIE / DNI -- a digital ID. For more - information see `wikipedia.org/doi`_. + A DOI in spain is all NIF / CIF / NIE / DNI -- a digital ID. + For more information see [wikipedia.org/doi][1]. This validator + is based on [generadordni.es][2]. - This validator is based on `generadordni.es`_. - - .. _generadordni.es: - https://generadordni.es/ - - .. _wikipedia.org/doi: - https://es.wikipedia.org/wiki/Identificador_de_objeto_digital - - Examples:: + [1]: https://es.wikipedia.org/wiki/Identificador_de_objeto_digital + [2]: https://generadordni.es/ + Examples: >>> es_doi('X0095892M') - True - + # Output: True >>> es_doi('X0095892X') - ValidationFailure(func=es_doi, args=...) + # Output: ValidationFailure(func=es_doi, args=...) + + Args: + value: + DOI string which is to be validated. - .. versionadded:: 0.13.0 + Returns: + (Literal[True]): + If `value` is a valid DOI string. + (ValidationFailure): + If `value` is an invalid DOI string. - :param doi: DOI to validate + > *New in version 0.13.0*. """ - return es_nie(doi) or es_nif(doi) or es_cif(doi) + return es_nie(value) or es_nif(value) or es_cif(value) diff --git a/validators/i18n/fi.py b/validators/i18n/fi.py index 2e5eb578..6351198d 100644 --- a/validators/i18n/fi.py +++ b/validators/i18n/fi.py @@ -1,94 +1,118 @@ +"""Finland.""" +# -*- coding: utf-8 -*- + +# standard +from functools import lru_cache import re +# local from validators.utils import validator -business_id_pattern = re.compile(r'^[0-9]{7}-[0-9]$') -ssn_checkmarks = '0123456789ABCDEFHJKLMNPRSTUVWXY' -ssn_pattern = re.compile( - r"""^ - (?P(0[1-9]|[1-2]\d|3[01]) - (0[1-9]|1[012]) - (\d{{2}})) - [A+-] - (?P(\d{{3}})) - (?P[{checkmarks}])$""".format(checkmarks=ssn_checkmarks), - re.VERBOSE -) + +@lru_cache +def _business_id_pattern(): + """Business ID Pattern.""" + return re.compile(r"^[0-9]{7}-[0-9]$") + + +@lru_cache +def _ssn_pattern(ssn_check_marks: str): + """SSN Pattern.""" + return re.compile( + r"""^ + (?P(0[1-9]|[1-2]\d|3[01]) + (0[1-9]|1[012]) + (\d{{2}})) + [ABCDEFYXWVU+-] + (?P(\d{{3}})) + (?P[{check_marks}])$""".format( + check_marks=ssn_check_marks + ), + re.VERBOSE, + ) @validator -def fi_business_id(business_id): - """ - Validate a Finnish Business ID. +def fi_business_id(value: str, /): + """Validate a Finnish Business ID. Each company in Finland has a distinct business id. For more - information see `Finnish Trade Register`_ + information see [Finnish Trade Register][1] - .. _Finnish Trade Register: - http://en.wikipedia.org/wiki/Finnish_Trade_Register - - Examples:: + [1]: http://en.wikipedia.org/wiki/Finnish_Trade_Register + Examples: >>> fi_business_id('0112038-9') # Fast Monkeys Ltd - True - + # Output: True >>> fi_business_id('1234567-8') # Bogus ID - ValidationFailure(func=fi_business_id, ...) + # Output: ValidationFailure(func=fi_business_id, ...) + + Args: + value: + Business ID string to be validated. + + Returns: + (Literal[True]): + If `value` is a valid finnish business id. + (ValidationFailure): + If `value` is an invalid finnish business id. - .. versionadded:: 0.4 - .. versionchanged:: 0.5 - Method renamed from ``finnish_business_id`` to ``fi_business_id`` + Note: + - *In version 0.5.0*: + - Function renamed from `finnish_business_id` to `fi_business_id` - :param business_id: business_id to validate + > *New in version 0.4.0*. """ - if not business_id or not re.match(business_id_pattern, business_id): + if not value: + return False + if not re.match(_business_id_pattern(), value): return False factors = [7, 9, 10, 5, 8, 4, 2] - numbers = map(int, business_id[:7]) - checksum = int(business_id[8]) - sum_ = sum(f * n for f, n in zip(factors, numbers)) - modulo = sum_ % 11 - return (11 - modulo == checksum) or (modulo == 0 and checksum == 0) + numbers = map(int, value[:7]) + checksum = int(value[8]) + modulo = sum(f * n for f, n in zip(factors, numbers)) % 11 + return (11 - modulo == checksum) or (modulo == checksum == 0) @validator -def fi_ssn(ssn, allow_temporal_ssn=True): - """ - Validate a Finnish Social Security Number. +def fi_ssn(value: str, /, *, allow_temporal_ssn: bool = True): + """Validate a Finnish Social Security Number. - This validator is based on `django-localflavor-fi`_. + This validator is based on [django-localflavor-fi][1]. - .. _django-localflavor-fi: - https://github.com/django/django-localflavor-fi/ - - Examples:: + [1]: https://github.com/django/django-localflavor-fi/ + Examples: >>> fi_ssn('010101-0101') - True - + # Output: True >>> fi_ssn('101010-0102') - ValidationFailure(func=fi_ssn, args=...) - - .. versionadded:: 0.5 - - :param ssn: Social Security Number to validate - :param allow_temporal_ssn: - Whether to accept temporal SSN numbers. Temporal SSN numbers are the - ones where the serial is in the range [900-999]. By default temporal - SSN numbers are valid. - + # Output: ValidationFailure(func=fi_ssn, args=...) + + Args: + value: + Social Security Number to be validated. + allow_temporal_ssn: + Whether to accept temporal SSN numbers. Temporal SSN numbers are the + ones where the serial is in the range [900-999]. By default temporal + SSN numbers are valid. + + Returns: + (Literal[True]): + If `value` is a valid finnish SSN. + (ValidationFailure): + If `value` is an invalid finnish SSN. + + > *New in version 0.5.0*. """ - if not ssn: + if not value: return False - - result = re.match(ssn_pattern, ssn) - if not result: + ssn_check_marks = "0123456789ABCDEFHJKLMNPRSTUVWXY" + if not (result := re.match(_ssn_pattern(ssn_check_marks), value)): return False gd = result.groupdict() - checksum = int(gd['date'] + gd['serial']) + checksum = int(gd["date"] + gd["serial"]) return ( - int(gd['serial']) >= 2 and - (allow_temporal_ssn or int(gd['serial']) <= 899) and - ssn_checkmarks[checksum % len(ssn_checkmarks)] == - gd['checksum'] + int(gd["serial"]) >= 2 + and (allow_temporal_ssn or int(gd["serial"]) <= 899) + and ssn_check_marks[checksum % len(ssn_check_marks)] == gd["checksum"] )