From 152d4c5a9677cb8b6006cef12556835b44f27ab1 Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Thu, 13 Mar 2025 14:15:54 +0100 Subject: [PATCH] Add type hints --- .github/workflows/test.yml | 4 +- .gitignore | 3 + mypy.ini | 4 + setup.cfg | 15 +++- setup.py | 8 +- stdnum/__init__.py | 1 + stdnum/_types.py | 77 ++++++++++++++++++ stdnum/_typing.py | 119 ++++++++++++++++++++++++++++ stdnum/ad/__init__.py | 1 + stdnum/ad/nrt.py | 9 ++- stdnum/al/__init__.py | 1 + stdnum/al/nipt.py | 7 +- stdnum/ar/__init__.py | 1 + stdnum/ar/cbu.py | 11 +-- stdnum/ar/cuit.py | 11 +-- stdnum/ar/dni.py | 9 ++- stdnum/at/__init__.py | 1 + stdnum/at/businessid.py | 7 +- stdnum/at/postleitzahl.py | 9 ++- stdnum/at/tin.py | 17 ++-- stdnum/at/uid.py | 9 ++- stdnum/at/vnr.py | 9 ++- stdnum/au/__init__.py | 1 + stdnum/au/abn.py | 11 +-- stdnum/au/acn.py | 13 +-- stdnum/au/tfn.py | 11 +-- stdnum/be/__init__.py | 1 + stdnum/be/bis.py | 21 +++-- stdnum/be/eid.py | 11 +-- stdnum/be/iban.py | 15 ++-- stdnum/be/nn.py | 25 +++--- stdnum/be/ssn.py | 26 +++--- stdnum/be/vat.py | 9 ++- stdnum/bg/egn.py | 11 +-- stdnum/bg/pnf.py | 9 ++- stdnum/bg/vat.py | 11 +-- stdnum/bic.py | 9 ++- stdnum/bitcoin.py | 20 ++--- stdnum/br/__init__.py | 2 + stdnum/br/cnpj.py | 11 +-- stdnum/br/cpf.py | 11 +-- stdnum/by/__init__.py | 1 + stdnum/by/unp.py | 11 +-- stdnum/ca/__init__.py | 2 + stdnum/ca/bc_phn.py | 12 +-- stdnum/ca/bn.py | 7 +- stdnum/ca/sin.py | 9 ++- stdnum/casrn.py | 9 ++- stdnum/cfi.py | 9 ++- stdnum/ch/esr.py | 11 +-- stdnum/ch/ssn.py | 9 ++- stdnum/ch/uid.py | 13 +-- stdnum/ch/vat.py | 9 ++- stdnum/cl/__init__.py | 2 + stdnum/cl/rut.py | 11 +-- stdnum/cn/__init__.py | 1 + stdnum/cn/ric.py | 15 ++-- stdnum/cn/uscc.py | 11 +-- stdnum/co/__init__.py | 2 + stdnum/co/nit.py | 11 +-- stdnum/cr/__init__.py | 2 + stdnum/cr/cpf.py | 9 ++- stdnum/cr/cpj.py | 9 ++- stdnum/cr/cr.py | 9 ++- stdnum/cu/ni.py | 12 +-- stdnum/cusip.py | 11 +-- stdnum/cy/vat.py | 9 ++- stdnum/cz/__init__.py | 1 + stdnum/cz/bankaccount.py | 25 +++--- stdnum/cz/dic.py | 11 +-- stdnum/cz/rc.py | 11 +-- stdnum/damm.py | 12 ++- stdnum/de/__init__.py | 1 + stdnum/de/handelsregisternummer.py | 21 +++-- stdnum/de/idnr.py | 11 +-- stdnum/de/stnr.py | 40 +++++----- stdnum/de/vat.py | 7 +- stdnum/de/wkn.py | 9 ++- stdnum/dk/__init__.py | 1 + stdnum/dk/cpr.py | 13 +-- stdnum/dk/cvr.py | 9 ++- stdnum/do/__init__.py | 1 + stdnum/do/cedula.py | 12 +-- stdnum/do/ncf.py | 22 +++-- stdnum/do/rnc.py | 30 ++++--- stdnum/dz/__init__.py | 1 + stdnum/dz/nif.py | 9 ++- stdnum/ean.py | 9 ++- stdnum/ec/__init__.py | 1 + stdnum/ec/ci.py | 12 +-- stdnum/ec/ruc.py | 14 ++-- stdnum/ee/__init__.py | 1 + stdnum/ee/ik.py | 14 ++-- stdnum/ee/kmkr.py | 9 ++- stdnum/ee/registrikood.py | 7 +- stdnum/eg/__init__.py | 1 + stdnum/eg/tn.py | 9 ++- stdnum/es/__init__.py | 1 + stdnum/es/cae.py | 7 +- stdnum/es/ccc.py | 15 ++-- stdnum/es/cif.py | 9 ++- stdnum/es/cups.py | 15 ++-- stdnum/es/dni.py | 9 ++- stdnum/es/iban.py | 7 +- stdnum/es/nie.py | 7 +- stdnum/es/nif.py | 7 +- stdnum/es/postal_code.py | 8 +- stdnum/es/referenciacatastral.py | 13 +-- stdnum/eu/at_02.py | 11 +-- stdnum/eu/banknote.py | 9 ++- stdnum/eu/ecnumber.py | 9 ++- stdnum/eu/eic.py | 9 ++- stdnum/eu/nace.py | 22 +++-- stdnum/eu/oss.py | 8 +- stdnum/eu/vat.py | 20 ++--- stdnum/exceptions.py | 3 +- stdnum/fi/__init__.py | 1 + stdnum/fi/alv.py | 9 ++- stdnum/fi/associationid.py | 9 ++- stdnum/fi/hetu.py | 9 ++- stdnum/fi/veronumero.py | 7 +- stdnum/fi/ytunnus.py | 9 ++- stdnum/figi.py | 9 ++- stdnum/fo/__init__.py | 1 + stdnum/fo/vn.py | 9 ++- stdnum/fr/__init__.py | 1 + stdnum/fr/nif.py | 11 +-- stdnum/fr/nir.py | 11 +-- stdnum/fr/siren.py | 9 ++- stdnum/fr/siret.py | 13 +-- stdnum/fr/tva.py | 7 +- stdnum/gb/nhs.py | 11 +-- stdnum/gb/sedol.py | 11 +-- stdnum/gb/upn.py | 9 ++- stdnum/gb/utr.py | 9 ++- stdnum/gb/vat.py | 11 +-- stdnum/gh/__init__.py | 1 + stdnum/gh/tin.py | 9 ++- stdnum/gn/__init__.py | 1 + stdnum/gn/nifp.py | 9 ++- stdnum/gr/amka.py | 12 +-- stdnum/gr/vat.py | 9 ++- stdnum/grid.py | 13 +-- stdnum/gs1_128.py | 28 ++++--- stdnum/gt/__init__.py | 1 + stdnum/gt/nit.py | 11 +-- stdnum/hr/__init__.py | 1 + stdnum/hr/oib.py | 7 +- stdnum/hu/__init__.py | 1 + stdnum/hu/anum.py | 9 ++- stdnum/iban.py | 18 +++-- stdnum/id/__init__.py | 1 + stdnum/id/nik.py | 11 +-- stdnum/id/npwp.py | 9 ++- stdnum/ie/pps.py | 7 +- stdnum/ie/vat.py | 11 +-- stdnum/il/__init__.py | 1 + stdnum/il/hp.py | 9 ++- stdnum/il/idnr.py | 9 ++- stdnum/imei.py | 17 ++-- stdnum/imo.py | 11 +-- stdnum/imsi.py | 14 ++-- stdnum/in_/__init__.py | 1 + stdnum/in_/aadhaar.py | 11 +-- stdnum/in_/epic.py | 7 +- stdnum/in_/gstin.py | 12 +-- stdnum/in_/pan.py | 16 ++-- stdnum/in_/vid.py | 11 +-- stdnum/is_/__init__.py | 1 + stdnum/is_/kennitala.py | 11 +-- stdnum/is_/vsk.py | 7 +- stdnum/isan.py | 38 +++++---- stdnum/isbn.py | 19 ++--- stdnum/isil.py | 11 +-- stdnum/isin.py | 11 +-- stdnum/ismn.py | 19 ++--- stdnum/isni.py | 10 +-- stdnum/iso11649.py | 9 ++- stdnum/iso6346.py | 11 +-- stdnum/iso7064/mod_11_10.py | 9 ++- stdnum/iso7064/mod_11_2.py | 9 ++- stdnum/iso7064/mod_37_2.py | 9 ++- stdnum/iso7064/mod_37_36.py | 9 ++- stdnum/iso7064/mod_97_10.py | 11 +-- stdnum/isrc.py | 9 ++- stdnum/issn.py | 13 +-- stdnum/it/__init__.py | 1 + stdnum/it/aic.py | 17 ++-- stdnum/it/codicefiscale.py | 14 ++-- stdnum/it/iva.py | 7 +- stdnum/jp/__init__.py | 2 + stdnum/jp/cn.py | 11 +-- stdnum/ke/__init__.py | 1 + stdnum/ke/pin.py | 9 ++- stdnum/kr/__init__.py | 1 + stdnum/kr/brn.py | 9 ++- stdnum/kr/rrn.py | 13 +-- stdnum/lei.py | 7 +- stdnum/li/peid.py | 7 +- stdnum/lt/__init__.py | 1 + stdnum/lt/asmens.py | 7 +- stdnum/lt/pvm.py | 9 ++- stdnum/lu/__init__.py | 1 + stdnum/lu/tva.py | 9 ++- stdnum/luhn.py | 15 ++-- stdnum/lv/__init__.py | 1 + stdnum/lv/pvn.py | 13 +-- stdnum/ma/__init__.py | 1 + stdnum/ma/ice.py | 9 ++- stdnum/mac.py | 27 ++++--- stdnum/mc/__init__.py | 1 + stdnum/mc/tva.py | 7 +- stdnum/md/idno.py | 9 ++- stdnum/me/__init__.py | 1 + stdnum/me/iban.py | 7 +- stdnum/me/pib.py | 11 +-- stdnum/meid.py | 36 +++++---- stdnum/mk/__init__.py | 1 + stdnum/mk/edb.py | 11 +-- stdnum/mt/vat.py | 9 ++- stdnum/mu/nid.py | 11 +-- stdnum/mx/__init__.py | 1 + stdnum/mx/curp.py | 14 ++-- stdnum/mx/rfc.py | 13 +-- stdnum/my/nric.py | 13 +-- stdnum/nl/__init__.py | 1 + stdnum/nl/brin.py | 7 +- stdnum/nl/bsn.py | 11 +-- stdnum/nl/btw.py | 7 +- stdnum/nl/identiteitskaartnummer.py | 7 +- stdnum/nl/onderwijsnummer.py | 5 +- stdnum/nl/postcode.py | 7 +- stdnum/no/__init__.py | 1 + stdnum/no/fodselsnummer.py | 18 +++-- stdnum/no/iban.py | 7 +- stdnum/no/kontonr.py | 13 +-- stdnum/no/mva.py | 9 ++- stdnum/no/orgnr.py | 11 +-- stdnum/numdb.py | 44 +++++----- stdnum/nz/__init__.py | 1 + stdnum/nz/bankaccount.py | 21 ++--- stdnum/nz/ird.py | 11 +-- stdnum/pe/__init__.py | 2 + stdnum/pe/cui.py | 11 +-- stdnum/pe/ruc.py | 11 +-- stdnum/pk/cnic.py | 17 ++-- stdnum/pl/__init__.py | 1 + stdnum/pl/nip.py | 11 +-- stdnum/pl/pesel.py | 14 ++-- stdnum/pl/regon.py | 11 +-- stdnum/pt/__init__.py | 1 + stdnum/pt/cc.py | 16 ++-- stdnum/pt/nif.py | 9 ++- stdnum/py.typed | 0 stdnum/py/__init__.py | 1 + stdnum/py/ruc.py | 11 +-- stdnum/ro/__init__.py | 1 + stdnum/ro/cf.py | 7 +- stdnum/ro/cnp.py | 13 +-- stdnum/ro/cui.py | 9 ++- stdnum/ro/onrc.py | 7 +- stdnum/rs/__init__.py | 1 + stdnum/rs/pib.py | 7 +- stdnum/ru/__init__.py | 1 + stdnum/ru/inn.py | 13 +-- stdnum/ru/ogrn.py | 9 ++- stdnum/se/__init__.py | 1 + stdnum/se/orgnr.py | 9 ++- stdnum/se/personnummer.py | 14 ++-- stdnum/se/postnummer.py | 9 ++- stdnum/se/vat.py | 7 +- stdnum/sg/__init__.py | 1 + stdnum/sg/uen.py | 21 ++--- stdnum/si/__init__.py | 1 + stdnum/si/ddv.py | 9 ++- stdnum/si/emso.py | 18 +++-- stdnum/si/maticna.py | 9 ++- stdnum/sk/__init__.py | 1 + stdnum/sk/dph.py | 9 ++- stdnum/sk/rc.py | 2 + stdnum/sm/__init__.py | 1 + stdnum/sm/coe.py | 7 +- stdnum/sv/__init__.py | 1 + stdnum/sv/nit.py | 11 +-- stdnum/th/moa.py | 9 ++- stdnum/th/pin.py | 11 +-- stdnum/th/tin.py | 15 ++-- stdnum/tn/__init__.py | 1 + stdnum/tn/mf.py | 9 ++- stdnum/tr/__init__.py | 2 + stdnum/tr/tckimlik.py | 11 +-- stdnum/tr/vkn.py | 9 ++- stdnum/tw/__init__.py | 1 + stdnum/tw/ubn.py | 11 +-- stdnum/ua/edrpou.py | 13 +-- stdnum/ua/rntrc.py | 11 +-- stdnum/us/atin.py | 9 ++- stdnum/us/ein.py | 11 +-- stdnum/us/itin.py | 9 ++- stdnum/us/ptin.py | 7 +- stdnum/us/rtn.py | 9 ++- stdnum/us/ssn.py | 9 ++- stdnum/us/tin.py | 15 ++-- stdnum/util.py | 73 +++++++++++------ stdnum/uy/__init__.py | 1 + stdnum/uy/rut.py | 11 +-- stdnum/vatin.py | 18 +++-- stdnum/ve/__init__.py | 1 + stdnum/ve/rif.py | 9 ++- stdnum/verhoeff.py | 14 ++-- stdnum/vn/__init__.py | 1 + stdnum/vn/mst.py | 11 +-- stdnum/za/idnr.py | 16 ++-- stdnum/za/tin.py | 9 ++- tox.ini | 14 +++- 315 files changed, 1897 insertions(+), 1246 deletions(-) create mode 100644 mypy.ini create mode 100644 stdnum/_types.py create mode 100644 stdnum/_typing.py create mode 100644 stdnum/py.typed diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cae9ce31..7c3f987c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7] + python-version: [3.7] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -46,7 +46,7 @@ jobs: strategy: fail-fast: false matrix: - tox_job: [docs, flake8, headers] + tox_job: [docs, flake8, mypy, headers] steps: - uses: actions/checkout@v3 - name: Set up Python diff --git a/.gitignore b/.gitignore index bc90af02..77e8d2c6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,10 @@ __pycache__ # / /.coverage +/.mypy_cache +/.ruff_cache /.tox +/.venv /build /coverage /dist diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..8d6f478c --- /dev/null +++ b/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +python_version = 3.9 +strict = True +warn_unreachable = True diff --git a/setup.cfg b/setup.cfg index 88f795a6..5a5587b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,11 +6,14 @@ owner=root group=root [tool:pytest] -addopts = --doctest-modules --doctest-glob="*.doctest" stdnum tests --ignore=stdnum/iso9362.py --cov=stdnum --cov-report=term-missing:skip-covered --cov-report=html +addopts = --doctest-modules --doctest-glob="*.doctest" stdnum tests --ignore=stdnum/iso9362.py --ignore=stdnum/_types.py --cov=stdnum --cov-report=term-missing:skip-covered --cov-report=html doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL [coverage:run] branch = true +omit = + _types.py + _typing.py [coverage:report] fail_under=100 @@ -32,16 +35,26 @@ ignore = Q003 # don't force "" strings to avoid escaping quotes T001,T201 # we use print statements in the update scripts W504 # we put the binary operator on the preceding line +per-file-ignores = + # typing re-exports + stdnum/_typing.py: F401,I250 max-complexity = 15 max-line-length = 120 extend-exclude = .github + .mypy_cache .pytest_cache + .ruff_cache + .venv build [isort] lines_after_imports = 2 multi_line_output = 4 +extra_standard_library = + typing_extensions +classes = + IO known_third_party = lxml openpyxl diff --git a/setup.py b/setup.py index a96631f5..15da1c0d 100755 --- a/setup.py +++ b/setup.py @@ -63,7 +63,6 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', @@ -77,8 +76,11 @@ 'Topic :: Text Processing :: General', ], packages=find_packages(), - install_requires=[], - package_data={'': ['*.dat', '*.crt']}, + python_requires='>=3.7', + install_requires=[ + 'importlib_resources >= 1.3 ; python_version < "3.9"', + ], + package_data={'': ['*.dat', '*.crt', 'py.typed']}, extras_require={ # The SOAP feature is only required for a number of online tests # of numbers such as the EU VAT VIES lookup, the Dominican Republic diff --git a/stdnum/__init__.py b/stdnum/__init__.py index 5eb2c054..be3e7ddf 100644 --- a/stdnum/__init__.py +++ b/stdnum/__init__.py @@ -36,6 +36,7 @@ Apart from the validate() function, many modules provide extra parsing, validation, formatting or conversion functions. """ +from __future__ import annotations from stdnum.util import get_cc_module diff --git a/stdnum/_types.py b/stdnum/_types.py new file mode 100644 index 00000000..f8973a47 --- /dev/null +++ b/stdnum/_types.py @@ -0,0 +1,77 @@ +# _types.py - module for defining custom types +# +# Copyright (C) 2025 David Salvisberg +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +"""Module containing custom types. + +This module is designed to be accessed through `stdnum._typing` so +you only have the overhead and runtime requirement of Python 3.9 +and `typing_extensions`, when that type is introspected at runtime. + +As such this module should never be accessed directly. +""" + +from __future__ import annotations + +from typing import Protocol +from typing_extensions import Required, TypedDict + + +class NumberValidationModule(Protocol): + """Minimal interface for a number validation module.""" + + def compact(self, number: str) -> str: + """Convert the number to the minimal representation.""" + + def validate(self, number: str) -> str: + """Check if the number provided is a valid number of its type.""" + + def is_valid(self, number: str) -> bool: + """Check if the number provided is a valid number of its type.""" + + +class IMSIInfo(TypedDict, total=False): + """Info `dict` returned by `stdnum.imsi.info`.""" + + number: Required[str] + mcc: Required[str] + mnc: Required[str] + msin: Required[str] + country: str + cc: str + brand: str + operator: str + status: str + bands: str + + +class GSTINInfo(TypedDict): + """Info `dict` returned by `stdnum.in_.gstin.info`.""" + + state: str | None + pan: str + holder_type: str + initial: str + registration_count: int + + +class PANInfo(TypedDict): + """Info `dict` returned by `stdnum.in_.pan.info`.""" + + holder_type: str + initial: str diff --git a/stdnum/_typing.py b/stdnum/_typing.py new file mode 100644 index 00000000..97257cd1 --- /dev/null +++ b/stdnum/_typing.py @@ -0,0 +1,119 @@ +# _typing.py - module for typing shims with reduced runtime overhead +# +# Copyright (C) 2025 David Salvisberg +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +"""Compatibility shims for the Python typing module. + +This module is designed in a way, such, that runtime use of +annotations is possible starting with Python 3.9, but it still +supports Python 3.6 - 3.8 if the package is used normally without +introspecting the annotations of the API. + +You should never import *from* this module, you should always import +the entire module and then access the members via attribute access. + +I.e. use the module like this: +```python +from stdnum import _typing as t + +foo: t.Any = ... +``` + +Instead of like this: +```python +from stdnum._typing import Any + +foo: Any = ... +``` + +The exception to that rule are `TYPE_CHECKING` `cast` and `deprecated` +which can be used at runtime. +""" + +from __future__ import annotations + + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Generator as Generator + from collections.abc import Iterable as Iterable + from collections.abc import Mapping as Mapping + from collections.abc import Sequence as Sequence + from typing import Any as Any + from typing import IO as IO + from typing import Literal as Literal + from typing import cast as cast + from typing_extensions import TypeAlias as TypeAlias + from typing_extensions import deprecated as deprecated + + from stdnum._types import GSTINInfo as GSTINInfo + from stdnum._types import IMSIInfo as IMSIInfo + from stdnum._types import NumberValidationModule as NumberValidationModule + from stdnum._types import PANInfo as PANInfo +else: + def cast(typ, val): + """Cast a value to a type.""" + return val + + class deprecated: # noqa: N801 + """Simplified backport of `warnings.deprecated`. + + This backport doesn't handle classes or async functions. + """ + + def __init__(self, message, category=DeprecationWarning, stacklevel=1): # noqa: D107 + self.message = message + self.category = category + self.stacklevel = stacklevel + + def __call__(self, func): # noqa: D102 + func.__deprecated__ = self.message + + if self.category is None: + return func + + import functools + import warnings + + @functools.wraps(func) + def wrapper(*args, **kwargs): + warnings.warn(self.message, category=self.category, stacklevel=self.stacklevel + 1) + return func(*args, **kwargs) + + wrapper.__deprecated__ = self.message + return wrapper + + def __getattr__(name): + if name in {'Generator', 'Iterable', 'Mapping', 'Sequence'}: + import collections.abc + return getattr(collections.abc, name) + elif name in {'Any', 'IO', 'Literal'}: + import typing + return getattr(typing, name) + elif name == 'TypeAlias': + import sys + if sys.version_info >= (3, 10): + import typing + else: + import typing_extensions as typing + return getattr(typing, name) + elif name in {'GSTINInfo', 'IMSIInfo', 'NumberValidationModule', 'PANInfo'}: + import stdnum._types + return getattr(stdnum._types, name) + else: + raise AttributeError(name) diff --git a/stdnum/ad/__init__.py b/stdnum/ad/__init__.py index 09ccf97c..e0cf3b45 100644 --- a/stdnum/ad/__init__.py +++ b/stdnum/ad/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Andorran numbers.""" +from __future__ import annotations # Provide aliases. from stdnum.ad import nrt as vat # noqa: F401 diff --git a/stdnum/ad/nrt.py b/stdnum/ad/nrt.py index 90c10448..6a091d14 100644 --- a/stdnum/ad/nrt.py +++ b/stdnum/ad/nrt.py @@ -43,12 +43,13 @@ >>> format('D059888N') 'D-059888-N' """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -57,7 +58,7 @@ def compact(number): return clean(number, ' -.').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Andorra NRT number. This checks the length, formatting and other constraints. It does not check @@ -79,7 +80,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Andorra NRT number.""" try: return bool(validate(number)) @@ -87,7 +88,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[0], number[1:-1], number[-1]]) diff --git a/stdnum/al/__init__.py b/stdnum/al/__init__.py index 25d7cd18..e3522f40 100644 --- a/stdnum/al/__init__.py +++ b/stdnum/al/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Albanian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.al import nipt as vat # noqa: F401 diff --git a/stdnum/al/nipt.py b/stdnum/al/nipt.py index 59e0f7fe..30a867ff 100644 --- a/stdnum/al/nipt.py +++ b/stdnum/al/nipt.py @@ -49,6 +49,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations import re @@ -60,7 +61,7 @@ _nipt_re = re.compile(r'^[A-M][0-9]{8}[A-Z]$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').upper().strip() @@ -71,7 +72,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length and formatting.""" number = compact(number) @@ -82,7 +83,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/ar/__init__.py b/stdnum/ar/__init__.py index 6e99d4e9..4ee051f8 100644 --- a/stdnum/ar/__init__.py +++ b/stdnum/ar/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Argentinian numbers.""" +from __future__ import annotations # provide aliases from stdnum.ar import cuit as vat # noqa: F401 diff --git a/stdnum/ar/cbu.py b/stdnum/ar/cbu.py index 08f6f98d..462a1a4d 100644 --- a/stdnum/ar/cbu.py +++ b/stdnum/ar/cbu.py @@ -38,18 +38,19 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (3, 1, 7, 9) check = sum(int(n) * weights[i % 4] @@ -57,7 +58,7 @@ def calc_check_digit(number): return str((10 - check) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid CBU.""" number = compact(number) if len(number) != 22: @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid CBU.""" try: return bool(validate(number)) @@ -79,7 +80,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:8], number[8:])) diff --git a/stdnum/ar/cuit.py b/stdnum/ar/cuit.py index 89522b2d..e929dca0 100644 --- a/stdnum/ar/cuit.py +++ b/stdnum/ar/cuit.py @@ -40,18 +40,19 @@ >>> format('20267565393') '20-26756539-3' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2) check = sum(w * int(n) for w, n in zip(weights, number)) % 11 @@ -66,7 +67,7 @@ def calc_check_digit(number): ) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid CUIT.""" number = compact(number) if len(number) != 11: @@ -80,7 +81,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid CUIT.""" try: return bool(validate(number)) @@ -88,7 +89,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return (number[0:2] + '-' + number[2:10] + '-' + number[10:]) diff --git a/stdnum/ar/dni.py b/stdnum/ar/dni.py index f97cb7a2..c64ee4d9 100644 --- a/stdnum/ar/dni.py +++ b/stdnum/ar/dni.py @@ -37,18 +37,19 @@ >>> format('20123456') '20.123.456' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' .').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid DNI.""" number = compact(number) if not isdigits(number): @@ -58,7 +59,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid DNI.""" try: return bool(validate(number)) @@ -66,7 +67,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '.'.join((number[:-6], number[-6:-3], number[-3:])) diff --git a/stdnum/at/__init__.py b/stdnum/at/__init__.py index 3ee90adb..950290d1 100644 --- a/stdnum/at/__init__.py +++ b/stdnum/at/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Austrian numbers.""" +from __future__ import annotations # provide aliases from stdnum.at import postleitzahl as postal_code # noqa: F401 diff --git a/stdnum/at/businessid.py b/stdnum/at/businessid.py index 04fc5644..788cfc83 100644 --- a/stdnum/at/businessid.py +++ b/stdnum/at/businessid.py @@ -37,6 +37,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations import re @@ -47,7 +48,7 @@ _businessid_re = re.compile('^[0-9]+[a-z]$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace. Preceding "FN" is also removed.""" @@ -57,7 +58,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid company register number. This only checks the formatting.""" number = compact(number) @@ -66,7 +67,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid company register number.""" try: return bool(validate(number)) diff --git a/stdnum/at/postleitzahl.py b/stdnum/at/postleitzahl.py index feafa843..ecbafabb 100644 --- a/stdnum/at/postleitzahl.py +++ b/stdnum/at/postleitzahl.py @@ -44,18 +44,19 @@ ... InvalidFormat: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number).strip() -def info(number): +def info(number: str) -> dict[str, str]: """Return a dictionary of data about the supplied number. This typically returns the location.""" number = compact(number) @@ -63,7 +64,7 @@ def info(number): return numdb.get('at/postleitzahl').info(number)[0][1] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid postal code.""" number = compact(number) if not isdigits(number): @@ -75,7 +76,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid postal code.""" try: return bool(validate(number)) diff --git a/stdnum/at/tin.py b/stdnum/at/tin.py index 88f0037e..7ebc392d 100644 --- a/stdnum/at/tin.py +++ b/stdnum/at/tin.py @@ -43,18 +43,19 @@ >>> format('591199013') '59-119/9013' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -./,').strip() -def _min_fa(office): +def _min_fa(office: str) -> str: """Convert the tax office name to something that we can use for comparison without running into encoding issues.""" return ''.join( @@ -62,7 +63,7 @@ def _min_fa(office): if x in 'bcdefghijklmnopqrstvwxyz') -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" number = compact(number) s = sum( @@ -71,7 +72,7 @@ def calc_check_digit(number): return str((10 - s) % 10) -def info(number): +def info(number: str) -> dict[str, str]: """Return a dictionary of data about the supplied number. This typically returns the the tax office and region.""" number = compact(number) @@ -79,7 +80,7 @@ def info(number): return numdb.get('at/fa').info(number[:2])[0][1] -def validate(number, office=None): +def validate(number: str, office: str | None = None) -> str: """Check if the number is a valid tax identification number. This checks the length and formatting. The tax office can be supplied to check that the number was issued in the specified tax office.""" @@ -93,12 +94,12 @@ def validate(number, office=None): i = info(number) if not i: raise InvalidComponent() - if office and _min_fa(i.get('office')) != _min_fa(office): + if office and _min_fa(i.get('office', '')) != _min_fa(office): raise InvalidComponent() return number -def is_valid(number, office=None): +def is_valid(number: str, office: str | None = None) -> bool: """Check if the number is a valid tax identification number. This checks the length, formatting and check digit.""" try: @@ -107,7 +108,7 @@ def is_valid(number, office=None): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '%s-%s/%s' % (number[:2], number[2:5], number[5:]) diff --git a/stdnum/at/uid.py b/stdnum/at/uid.py index 0ed537bb..ea90d121 100644 --- a/stdnum/at/uid.py +++ b/stdnum/at/uid.py @@ -31,13 +31,14 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -./').upper().strip() @@ -46,13 +47,13 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" return str((6 - luhn.checksum(number[1:])) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -65,7 +66,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/at/vnr.py b/stdnum/at/vnr.py index 73745a03..e4a034d1 100644 --- a/stdnum/at/vnr.py +++ b/stdnum/at/vnr.py @@ -36,25 +36,26 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ') -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The fourth digit in the number is ignored.""" weights = (3, 7, 9, 0, 5, 8, 4, 2, 1, 6) return str(sum(w * int(n) for w, n in zip(weights, number)) % 11) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -67,7 +68,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/au/__init__.py b/stdnum/au/__init__.py index ba522764..e4688b62 100644 --- a/stdnum/au/__init__.py +++ b/stdnum/au/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Australian numbers.""" +from __future__ import annotations # provide aliases from stdnum.au import abn as vat # noqa: F401 diff --git a/stdnum/au/abn.py b/stdnum/au/abn.py index a0d47247..f2039c9d 100644 --- a/stdnum/au/abn.py +++ b/stdnum/au/abn.py @@ -38,18 +38,19 @@ >>> format('51824753556') '51 824 753 556' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits that should be prepended to make the number valid.""" weights = (3, 5, 7, 9, 11, 13, 15, 17, 19) @@ -57,7 +58,7 @@ def calc_check_digits(number): return str(11 + (s - 1) % 89) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ABN. This checks the length, formatting and check digit.""" number = compact(number) @@ -70,7 +71,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ABN.""" try: return bool(validate(number)) @@ -78,7 +79,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[0:2], number[2:5], number[5:8], number[8:])) diff --git a/stdnum/au/acn.py b/stdnum/au/acn.py index 77e62272..fc2b066b 100644 --- a/stdnum/au/acn.py +++ b/stdnum/au/acn.py @@ -40,23 +40,24 @@ >>> to_abn('002 724 334') '43002724334' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the checksum.""" return str((sum(int(n) * (i - 8) for i, n in enumerate(number))) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ACN. This checks the length, formatting and check digit.""" number = compact(number) @@ -69,7 +70,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ACN.""" try: return bool(validate(number)) @@ -77,13 +78,13 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[0:3], number[3:6], number[6:])) -def to_abn(number): +def to_abn(number: str) -> str: """Convert the number to an Australian Business Number (ABN).""" from stdnum.au import abn number = compact(number) diff --git a/stdnum/au/tfn.py b/stdnum/au/tfn.py index d515bac2..76bb8df5 100644 --- a/stdnum/au/tfn.py +++ b/stdnum/au/tfn.py @@ -41,24 +41,25 @@ >>> format('123456782') '123 456 782' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (1, 4, 3, 7, 5, 8, 6, 9, 10) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid TFN. This checks the length, formatting and check digit.""" number = compact(number) @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid TFN.""" try: return bool(validate(number)) @@ -79,7 +80,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[0:3], number[3:6], number[6:])) diff --git a/stdnum/be/__init__.py b/stdnum/be/__init__.py index d1313450..2bcbd5d3 100644 --- a/stdnum/be/__init__.py +++ b/stdnum/be/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Belgian numbers.""" +from __future__ import annotations # provide businessid as an alias from stdnum.be import nn as personalid # noqa: F401 diff --git a/stdnum/be/bis.py b/stdnum/be/bis.py index 77876fe4..2ea462b2 100644 --- a/stdnum/be/bis.py +++ b/stdnum/be/bis.py @@ -64,19 +64,23 @@ >>> get_gender('98.47.28-997.65') 'M' """ +from __future__ import annotations +import datetime + +from stdnum import _typing as t from stdnum.be import nn from stdnum.exceptions import * from stdnum.util import isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return nn.compact(number) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid BIS Number.""" number = compact(number) if not isdigits(number) or int(number) <= 0: @@ -89,7 +93,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid BIS Number.""" try: return bool(validate(number)) @@ -97,29 +101,30 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return nn.format(number) -def get_birth_year(number): +def get_birth_year(number: str) -> int | None: """Return the year of the birth date.""" return nn.get_birth_year(number) -def get_birth_month(number): +def get_birth_month(number: str) -> int | None: """Return the month of the birth date.""" return nn.get_birth_month(number) -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date | None: """Return the date of birth.""" return nn.get_birth_date(number) -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F'] | None: """Get the person's gender ('M' or 'F'), which for BIS numbers is only known if the month was incremented with 40.""" number = compact(number) if int(number[2:4]) >= 40: return nn.get_gender(number) + return None diff --git a/stdnum/be/eid.py b/stdnum/be/eid.py index 96a52121..7f3a9b39 100644 --- a/stdnum/be/eid.py +++ b/stdnum/be/eid.py @@ -53,18 +53,19 @@ >>> format('591191706458') '591-1917064-58' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -./').upper().strip() -def _calc_check_digits(number): +def _calc_check_digits(number: str) -> str: """Calculate the expected check digits for the number, calculated as the remainder of dividing the first 10 digits of the number by 97. If the remainder is 0, the check number is set to 97. @@ -72,7 +73,7 @@ def _calc_check_digits(number): return '%02d' % ((int(number[:10]) % 97) or 97) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ID card number. This checks the length, formatting and check digit.""" number = compact(number) @@ -85,7 +86,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Belgian ID Card number.""" try: return bool(validate(number)) @@ -93,7 +94,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join((number[:3], number[3:10], number[10:])) diff --git a/stdnum/be/iban.py b/stdnum/be/iban.py index ca740db3..2c2ff4bb 100644 --- a/stdnum/be/iban.py +++ b/stdnum/be/iban.py @@ -46,6 +46,7 @@ >>> to_bic('BE83138811735115') is None True """ +from __future__ import annotations from stdnum import iban from stdnum.exceptions import * @@ -58,13 +59,13 @@ format = iban.format -def _calc_check_digits(number): +def _calc_check_digits(number: str) -> str: """Calculate the check digits over the provided part of the number.""" check = int(number) % 97 return '%02d' % (check or 97) -def info(number): +def info(number: str) -> dict[str, str]: """Return a dictionary of data about the supplied number. This typically returns the name of the bank and a BIC if it is valid.""" number = compact(number) @@ -72,14 +73,12 @@ def info(number): return numdb.get('be/banks').info(number[4:7])[0][1] -def to_bic(number): +def to_bic(number: str) -> str | None: """Return the BIC for the bank that this number refers to.""" - bic = info(number).get('bic') - if bic: - return str(bic) + return info(number).get('bic') -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid Belgian IBAN.""" number = iban.validate(number, check_country=False) if not number.startswith('BE'): @@ -91,7 +90,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid Belgian IBAN.""" try: return bool(validate(number)) diff --git a/stdnum/be/nn.py b/stdnum/be/nn.py index 6dea86ba..df764a54 100644 --- a/stdnum/be/nn.py +++ b/stdnum/be/nn.py @@ -81,22 +81,24 @@ >>> get_gender('85.07.30-033 28') 'M' """ +from __future__ import annotations import calendar import datetime +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').strip() return number -def _checksum(number): +def _checksum(number: str) -> int: """Calculate the checksum and return the detected century.""" numbers = [number] if int(number[:2]) + 2000 <= datetime.date.today().year: @@ -107,7 +109,7 @@ def _checksum(number): raise InvalidChecksum() -def _get_birth_date_parts(number): +def _get_birth_date_parts(number: str) -> tuple[int | None, int | None, int | None]: """Check if the number's encoded birth date is valid, and return the contained birth year, month and day of month, accounting for unknown values.""" century = _checksum(number) @@ -138,7 +140,7 @@ def _get_birth_date_parts(number): return (year, month, day) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid National Number.""" number = compact(number) if not isdigits(number) or int(number) <= 0: @@ -151,7 +153,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid National Number.""" try: return bool(validate(number)) @@ -159,7 +161,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ( @@ -167,26 +169,27 @@ def format(number): '-' + '.'.join([number[6:9], number[9:11]])) -def get_birth_year(number): +def get_birth_year(number: str) -> int | None: """Return the year of the birth date.""" year, month, day = _get_birth_date_parts(compact(number)) return year -def get_birth_month(number): +def get_birth_month(number: str) -> int | None: """Return the month of the birth date.""" year, month, day = _get_birth_date_parts(compact(number)) return month -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date | None: """Return the date of birth.""" year, month, day = _get_birth_date_parts(compact(number)) - if None not in (year, month, day): + if year is not None and month is not None and day is not None: return datetime.date(year, month, day) + return None -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the person's gender ('M' or 'F').""" number = compact(number) if int(number[6:9]) % 2: diff --git a/stdnum/be/ssn.py b/stdnum/be/ssn.py index 65045c16..595bbaad 100644 --- a/stdnum/be/ssn.py +++ b/stdnum/be/ssn.py @@ -88,7 +88,11 @@ 'M' """ +from __future__ import annotations +import datetime + +from stdnum import _typing as t from stdnum.be import bis, nn from stdnum.exceptions import * @@ -96,13 +100,13 @@ _ssn_modules = (nn, bis) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return nn.compact(number) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Belgian SSN. This searches for the proper sub-type and validates using that.""" try: @@ -113,7 +117,7 @@ def validate(number): return nn.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Belgian SSN number.""" try: return bool(validate(number)) @@ -121,35 +125,37 @@ def is_valid(number): return False -def guess_type(number): +def guess_type(number: str) -> str | None: """Return the Belgian SSN type for which this number is valid.""" for mod in _ssn_modules: if mod.is_valid(number): return mod.__name__.rsplit('.', 1)[-1] + return None -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return nn.format(number) -def get_birth_year(number): +def get_birth_year(number: str) -> int | None: """Return the year of the birth date.""" return nn.get_birth_year(number) -def get_birth_month(number): +def get_birth_month(number: str) -> int | None: """Return the month of the birth date.""" return nn.get_birth_month(number) -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date | None: """Return the date of birth.""" return nn.get_birth_date(number) -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F'] | None: """Get the person's gender ('M' or 'F').""" for mod in _ssn_modules: if mod.is_valid(number): - return mod.get_gender(number) + return mod.get_gender(number) # type: ignore[no-any-return] + return None diff --git a/stdnum/be/vat.py b/stdnum/be/vat.py index 17220223..357fc425 100644 --- a/stdnum/be/vat.py +++ b/stdnum/be/vat.py @@ -34,12 +34,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -./').upper().strip() @@ -52,12 +53,12 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" return (int(number[:-2]) + int(number[-2:])) % 97 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -70,7 +71,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/bg/egn.py b/stdnum/bg/egn.py index 9591182b..f507e08d 100644 --- a/stdnum/bg/egn.py +++ b/stdnum/bg/egn.py @@ -40,6 +40,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations import datetime @@ -47,20 +48,20 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" weights = (2, 4, 8, 5, 10, 9, 7, 3, 6) return str(sum(w * int(n) for w, n in zip(weights, number)) % 11 % 10) -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) year = int(number[0:2]) + 1900 @@ -78,7 +79,7 @@ def get_birth_date(number): raise InvalidComponent() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid national identification number. This checks the length, formatting, embedded date and check digit.""" number = compact(number) @@ -95,7 +96,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid national identification number.""" try: return bool(validate(number)) diff --git a/stdnum/bg/pnf.py b/stdnum/bg/pnf.py index 9486371e..eeb24dc5 100644 --- a/stdnum/bg/pnf.py +++ b/stdnum/bg/pnf.py @@ -34,25 +34,26 @@ ... InvalidFormat: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" weights = (21, 19, 17, 13, 11, 9, 7, 3, 1) return str(sum(w * int(n) for w, n in zip(weights, number)) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid national identification number. This checks the length, formatting, embedded date and check digit.""" number = compact(number) @@ -65,7 +66,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid national identification number.""" try: return bool(validate(number)) diff --git a/stdnum/bg/vat.py b/stdnum/bg/vat.py index c3717c73..af06f8a6 100644 --- a/stdnum/bg/vat.py +++ b/stdnum/bg/vat.py @@ -33,13 +33,14 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.bg import egn, pnf from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').upper().strip() @@ -48,7 +49,7 @@ def compact(number): return number -def calc_check_digit_legal(number): +def calc_check_digit_legal(number: str) -> str: """Calculate the check digit for legal entities. The number passed should not have the check digit included.""" check = sum((i + 1) * int(n) for i, n in enumerate(number)) % 11 @@ -57,14 +58,14 @@ def calc_check_digit_legal(number): return str(check % 10) -def calc_check_digit_other(number): +def calc_check_digit_other(number: str) -> str: """Calculate the check digit for others. The number passed should not have the check digit included.""" weights = (4, 3, 2, 7, 6, 5, 4, 3, 2) return str((11 - sum(w * int(n) for w, n in zip(weights, number))) % 11) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -84,7 +85,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/bic.py b/stdnum/bic.py index a4f2a500..ed1b896c 100644 --- a/stdnum/bic.py +++ b/stdnum/bic.py @@ -46,6 +46,7 @@ 'AGRIFRPP' """ +from __future__ import annotations import re @@ -56,13 +57,13 @@ _bic_re = re.compile(r'^[A-Z]{6}[0-9A-Z]{2}([0-9A-Z]{3})?$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any surrounding whitespace.""" return clean(number, ' -').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid routing number. This checks the length and characters in each position.""" number = compact(number) @@ -74,7 +75,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid BIC.""" try: return bool(validate(number)) @@ -82,6 +83,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/bitcoin.py b/stdnum/bitcoin.py index 6fffa266..87ba269b 100644 --- a/stdnum/bitcoin.py +++ b/stdnum/bitcoin.py @@ -41,16 +41,18 @@ ... InvalidChecksum: ... """ +from __future__ import annotations import hashlib import struct from functools import reduce +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').strip() @@ -63,7 +65,7 @@ def compact(number): _base58_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -def b58decode(s): +def b58decode(s: str) -> bytes: """Decode a Base58 encoded string to a bytestring.""" value = reduce(lambda a, c: a * 58 + _base58_alphabet.index(c), s, 0) result = b'' @@ -84,18 +86,18 @@ def b58decode(s): (1 << 3, 0x3d4233dd), (1 << 4, 0x2a1462b3)) -def bech32_checksum(values): +def bech32_checksum(values: t.Iterable[int]) -> int: """Calculate the Bech32 checksum.""" chk = 1 for value in values: top = chk >> 25 chk = (chk & 0x1ffffff) << 5 | value - for t, v in _bech32_generator: - chk ^= v if top & t else 0 + for test, val in _bech32_generator: + chk ^= val if top & test else 0 return chk -def b32decode(data): +def b32decode(data: t.Iterable[int]) -> bytes: """Decode a list of Base32 values to a bytestring.""" acc, bits = 0, 0 result = b'' @@ -110,12 +112,12 @@ def b32decode(data): return result -def _expand_hrp(hrp): +def _expand_hrp(hrp: str) -> list[int]: """Convert the human-readable part to format for checksum calculation.""" return [ord(c) >> 5 for c in hrp] + [0] + [ord(c) & 31 for c in hrp] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and check digit.""" number = compact(number) @@ -150,7 +152,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length and check digit.""" try: diff --git a/stdnum/br/__init__.py b/stdnum/br/__init__.py index 3b2d22fa..030796a7 100644 --- a/stdnum/br/__init__.py +++ b/stdnum/br/__init__.py @@ -19,4 +19,6 @@ # 02110-1301 USA """Collection of Brazilian numbers.""" +from __future__ import annotations + from stdnum.br import cnpj as vat # noqa: F401 diff --git a/stdnum/br/cnpj.py b/stdnum/br/cnpj.py index e2fbcdbe..a5885bdf 100644 --- a/stdnum/br/cnpj.py +++ b/stdnum/br/cnpj.py @@ -37,18 +37,19 @@ >>> format('16727230000197') '16.727.230/0001-97' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -./').strip() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the number.""" d1 = (11 - sum(((3 - i) % 8 + 2) * int(n) for i, n in enumerate(number[:12]))) % 11 % 10 @@ -58,7 +59,7 @@ def calc_check_digits(number): return '%d%d' % (d1, d2) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid CNPJ. This checks the length and whether the check digits are correct.""" number = compact(number) @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid CNPJ.""" try: return bool(validate(number)) @@ -79,7 +80,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return (number[0:2] + '.' + number[2:5] + '.' + number[5:8] + '/' + diff --git a/stdnum/br/cpf.py b/stdnum/br/cpf.py index d0e5dbb8..ac4198cd 100644 --- a/stdnum/br/cpf.py +++ b/stdnum/br/cpf.py @@ -41,18 +41,19 @@ >>> format('23100299900') '231.002.999-00' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').strip() -def _calc_check_digits(number): +def _calc_check_digits(number: str) -> str: """Calculate the check digits for the number.""" d1 = sum((10 - i) * int(number[i]) for i in range(9)) d1 = (11 - d1) % 11 % 10 @@ -61,7 +62,7 @@ def _calc_check_digits(number): return '%d%d' % (d1, d2) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid CPF. This checks the length and whether the check digit is correct.""" number = compact(number) @@ -74,7 +75,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid CPF.""" try: return bool(validate(number)) @@ -82,7 +83,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:3] + '.' + number[3:6] + '.' + number[6:-2] + '-' + number[-2:] diff --git a/stdnum/by/__init__.py b/stdnum/by/__init__.py index 893f3864..3b85edf6 100644 --- a/stdnum/by/__init__.py +++ b/stdnum/by/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Belarusian numbers.""" +from __future__ import annotations # provide aliases from stdnum.by import unp as vat # noqa: F401 diff --git a/stdnum/by/unp.py b/stdnum/by/unp.py index b9c7ebc3..0537b8d5 100644 --- a/stdnum/by/unp.py +++ b/stdnum/by/unp.py @@ -40,6 +40,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -52,7 +53,7 @@ )) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').upper().strip() @@ -63,7 +64,7 @@ def compact(number): return ''.join(_cyrillic_to_latin.get(x, x) for x in number) -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number.""" number = compact(number) alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -76,7 +77,7 @@ def calc_check_digit(number): return str(c) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid number. This checks the length, formatting and check digit.""" number = compact(number) @@ -93,7 +94,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid number.""" try: return bool(validate(number)) @@ -101,7 +102,7 @@ def is_valid(number): return False -def check_nalog(number, timeout=30, verify=True): # pragma: no cover (not part of normal test suite) +def check_nalog(number, timeout=30, verify=True): # type: ignore # pragma: no cover (not part of normal test suite) """Retrieve registration information from the portal.nalog.gov.by web site. The `timeout` argument specifies the network timeout in seconds. diff --git a/stdnum/ca/__init__.py b/stdnum/ca/__init__.py index 5d44d1e4..241943ec 100644 --- a/stdnum/ca/__init__.py +++ b/stdnum/ca/__init__.py @@ -19,4 +19,6 @@ # 02110-1301 USA """Collection of Canadian numbers.""" +from __future__ import annotations + from stdnum.ca import bn as vat # noqa: F401 diff --git a/stdnum/ca/bc_phn.py b/stdnum/ca/bc_phn.py index e2cfa674..e0b064e9 100644 --- a/stdnum/ca/bc_phn.py +++ b/stdnum/ca/bc_phn.py @@ -54,19 +54,19 @@ ... InvalidFormat: ... """ # noqa: E501 - +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '- ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" weights = (2, 4, 8, 5, 10, 9, 7, 3) @@ -74,7 +74,7 @@ def calc_check_digit(number): return str((11 - s) % 11) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid PHN. This checks the length, formatting and check digit.""" number = compact(number) @@ -89,7 +89,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid PHN.""" try: return bool(validate(number)) @@ -97,7 +97,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[0:4], number[4:7], number[7:])) diff --git a/stdnum/ca/bn.py b/stdnum/ca/bn.py index 0345d835..4ba86b50 100644 --- a/stdnum/ca/bn.py +++ b/stdnum/ca/bn.py @@ -42,19 +42,20 @@ ... InvalidFormat: ... """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '- ').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid BN or BN15. This checks the length, formatting and check digit.""" number = compact(number) @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid BN or BN15.""" try: return bool(validate(number)) diff --git a/stdnum/ca/sin.py b/stdnum/ca/sin.py index 102b57ea..06738f1f 100644 --- a/stdnum/ca/sin.py +++ b/stdnum/ca/sin.py @@ -46,19 +46,20 @@ >>> format('123456782') '123-456-782' """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '- ').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid SIN. This checks the length, formatting and check digit.""" number = compact(number) @@ -71,7 +72,7 @@ def validate(number): return luhn.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid SIN.""" try: return bool(validate(number)) @@ -79,7 +80,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join((number[0:3], number[3:6], number[6:])) diff --git a/stdnum/casrn.py b/stdnum/casrn.py index f4f1dc6e..284b4e86 100644 --- a/stdnum/casrn.py +++ b/stdnum/casrn.py @@ -37,6 +37,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations import re @@ -47,7 +48,7 @@ _cas_re = re.compile(r'^[1-9][0-9]{1,6}-[0-9]{2}-[0-9]$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" number = clean(number, ' ').strip() if '-' not in number: @@ -55,7 +56,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number. The passed number should not have the check digit included.""" number = number.replace('-', '') @@ -63,7 +64,7 @@ def calc_check_digit(number): sum((i + 1) * int(n) for i, n in enumerate(reversed(number))) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid CAS RN.""" number = compact(number) if not 7 <= len(number) <= 12: @@ -75,7 +76,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid CAS RN.""" try: return bool(validate(number)) diff --git a/stdnum/cfi.py b/stdnum/cfi.py index f4ffeaa8..33fd4485 100644 --- a/stdnum/cfi.py +++ b/stdnum/cfi.py @@ -46,6 +46,7 @@ "group": "Limited partnership units" } """ +from __future__ import annotations from stdnum import numdb from stdnum.exceptions import * @@ -56,13 +57,13 @@ _cfidb = numdb.get('cfi') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def info(number): +def info(number: str) -> dict[str, str]: """Look up information about the number.""" number = compact(number) info = _cfidb.info(number) @@ -79,7 +80,7 @@ def info(number): return properties -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and format.""" number = compact(number) @@ -91,7 +92,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length and check digit.""" try: diff --git a/stdnum/ch/esr.py b/stdnum/ch/esr.py index 8d62380f..6ea772ad 100644 --- a/stdnum/ch/esr.py +++ b/stdnum/ch/esr.py @@ -48,18 +48,19 @@ >>> format('18 78583') '00 00000 00000 00000 00018 78583' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separators.""" return clean(number, ' ').lstrip('0') -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for number. The number passed should not have the check digit included.""" _digits = (0, 9, 4, 6, 8, 2, 7, 1, 3, 5) @@ -69,7 +70,7 @@ def calc_check_digit(number): return str((10 - c) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ESR. This checks the length, formatting and check digit.""" number = compact(number) @@ -82,7 +83,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ESR.""" try: return bool(validate(number)) @@ -90,7 +91,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number).zfill(27) return number[:2] + ' ' + ' '.join( diff --git a/stdnum/ch/ssn.py b/stdnum/ch/ssn.py index a59dc4fe..a6afd8a1 100644 --- a/stdnum/ch/ssn.py +++ b/stdnum/ch/ssn.py @@ -45,25 +45,26 @@ >>> format('7569217076985') '756.9217.0769.85' """ +from __future__ import annotations from stdnum import ean from stdnum.exceptions import * from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' .').strip() -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '.'.join((number[:3], number[3:7], number[7:11], number[11:])) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Swiss Sozialversicherungsnummer.""" number = compact(number) if len(number) != 13: @@ -73,7 +74,7 @@ def validate(number): return ean.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Swiss Sozialversicherungsnummer.""" try: return bool(validate(number)) diff --git a/stdnum/ch/uid.py b/stdnum/ch/uid.py index 97a0f5f1..aaccc180 100644 --- a/stdnum/ch/uid.py +++ b/stdnum/ch/uid.py @@ -45,12 +45,13 @@ >>> format('CHE100155212') 'CHE-100.155.212' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, get_soap_client, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separators.""" number = clean(number, ' -.').strip().upper() @@ -59,7 +60,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for organisations. The number passed should not have the check digit included.""" weights = (5, 4, 3, 2, 7, 6, 5, 4) @@ -67,7 +68,7 @@ def calc_check_digit(number): return str((11 - s) % 11) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid UID. This checks the length, formatting and check digit.""" number = compact(number) @@ -82,7 +83,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid UID.""" try: return bool(validate(number)) @@ -90,7 +91,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:3] + '-' + '.'.join( @@ -100,7 +101,7 @@ def format(number): uid_wsdl = 'https://www.uid-wse.admin.ch/V5.0/PublicServices.svc?wsdl' -def check_uid(number, timeout=30, verify=True): # pragma: no cover +def check_uid(number, timeout=30, verify=True): # type: ignore # pragma: no cover """Look up information via the Swiss Federal Statistical Office web service. This uses the UID registry web service run by the the Swiss Federal diff --git a/stdnum/ch/vat.py b/stdnum/ch/vat.py index d6501f6d..e19fdaa8 100644 --- a/stdnum/ch/vat.py +++ b/stdnum/ch/vat.py @@ -42,18 +42,19 @@ >>> format('CHE107787577IVA') 'CHE-107.787.577 IVA' """ +from __future__ import annotations from stdnum.ch import uid from stdnum.exceptions import * -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separators.""" return uid.compact(number) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -65,7 +66,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) @@ -73,7 +74,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return uid.format(number[:12]) + ' ' + number[12:] diff --git a/stdnum/cl/__init__.py b/stdnum/cl/__init__.py index f31595e6..13b5694c 100644 --- a/stdnum/cl/__init__.py +++ b/stdnum/cl/__init__.py @@ -19,6 +19,8 @@ # 02110-1301 USA """Collection of Chilean numbers.""" +from __future__ import annotations + # provide vat and run as an alias from stdnum.cl import rut as vat # noqa: F401, isort:skip diff --git a/stdnum/cl/rut.py b/stdnum/cl/rut.py index d15dba42..5d978d66 100644 --- a/stdnum/cl/rut.py +++ b/stdnum/cl/rut.py @@ -41,12 +41,13 @@ >>> format('125319092') '12.531.909-2' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').upper().strip() @@ -55,14 +56,14 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" s = sum(int(n) * (4 + (5 - i) % 6) for i, n in enumerate(number[::-1])) return '0123456789K'[s % 11] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid RUT. This checks the length, formatting and check digit.""" number = compact(number) @@ -75,7 +76,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid RUT.""" try: return bool(validate(number)) @@ -83,7 +84,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return (number[:-7] + '.' + number[-7:-4] + '.' + diff --git a/stdnum/cn/__init__.py b/stdnum/cn/__init__.py index 93a6f5e4..cac8875c 100644 --- a/stdnum/cn/__init__.py +++ b/stdnum/cn/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of China (PRC) numbers.""" +from __future__ import annotations # Provide vat as an alias. from stdnum.cn import uscc as vat # noqa: F401 diff --git a/stdnum/cn/ric.py b/stdnum/cn/ric.py index df70c067..34a2f352 100644 --- a/stdnum/cn/ric.py +++ b/stdnum/cn/ric.py @@ -31,6 +31,7 @@ >>> validate('360426199101010071') '360426199101010071' """ +from __future__ import annotations import datetime @@ -38,13 +39,13 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number).upper().strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date. Note that in some cases it may return the registration date instead of the birth date and it may be a century off.""" @@ -58,7 +59,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_birth_place(number): +def get_birth_place(number: str) -> dict[str, str]: """Use the number to look up the place of birth of the person.""" from stdnum import numdb number = compact(number) @@ -68,14 +69,14 @@ def get_birth_place(number): return results -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should have the check digit included.""" checksum = (1 - 2 * int(number[:-1], 13)) % 11 return 'X' if checksum == 10 else str(checksum) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid RIC number. This checks the length, formatting and birth date and place.""" number = compact(number) @@ -90,7 +91,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid RIC number.""" try: return bool(validate(number)) @@ -98,6 +99,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/cn/uscc.py b/stdnum/cn/uscc.py index a04a553b..e5a0b095 100644 --- a/stdnum/cn/uscc.py +++ b/stdnum/cn/uscc.py @@ -70,6 +70,7 @@ >>> format('9 1 110000 600037341L') '91110000600037341L' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -78,7 +79,7 @@ _alphabet = '0123456789ABCDEFGHJKLMNPQRTUWXY' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -87,7 +88,7 @@ def compact(number): return clean(number, ' -').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number.""" weights = (1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28) number = compact(number) @@ -95,7 +96,7 @@ def calc_check_digit(number): return _alphabet[(31 - total) % 31] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid USCC. This checks the length, formatting and check digit. @@ -112,7 +113,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid USCC.""" try: return bool(validate(number)) @@ -120,6 +121,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/co/__init__.py b/stdnum/co/__init__.py index de40a0c5..df2e9acd 100644 --- a/stdnum/co/__init__.py +++ b/stdnum/co/__init__.py @@ -19,6 +19,8 @@ # 02110-1301 USA """Collection of Colombian numbers.""" +from __future__ import annotations + # provide vat and rut as an alias from stdnum.co import nit as vat # noqa: F401, isort:skip diff --git a/stdnum/co/nit.py b/stdnum/co/nit.py index 2b182056..e4998979 100644 --- a/stdnum/co/nit.py +++ b/stdnum/co/nit.py @@ -34,18 +34,19 @@ >>> format('2131234321') '213.123.432-1' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separation dash.""" return clean(number, '.,- ').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" weights = (3, 7, 13, 17, 19, 23, 29, 37, 41, 43, 47, 53, 59, 67, 71) @@ -53,7 +54,7 @@ def calc_check_digit(number): return '01987654321'[s] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid NIT. This checks the length, formatting and check digit.""" number = compact(number) @@ -66,7 +67,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid NIT.""" try: return bool(validate(number)) @@ -74,7 +75,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '.'.join( diff --git a/stdnum/cr/__init__.py b/stdnum/cr/__init__.py index d9b149a8..d62c87b3 100644 --- a/stdnum/cr/__init__.py +++ b/stdnum/cr/__init__.py @@ -19,4 +19,6 @@ # 02110-1301 USA """Collection of Costa Rican numbers.""" +from __future__ import annotations + from stdnum.cr import cpj as vat # noqa: F401 diff --git a/stdnum/cr/cpf.py b/stdnum/cr/cpf.py index 1d09887e..16a75a6d 100644 --- a/stdnum/cr/cpf.py +++ b/stdnum/cr/cpf.py @@ -52,12 +52,13 @@ >>> format('1-613-584') '01-0613-0584' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -76,7 +77,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Costa Rica CPF number. This checks the length and formatting. @@ -91,7 +92,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Costa Rica CPF number.""" try: return bool(validate(number)) @@ -99,7 +100,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:2], number[2:6], number[6:]]) diff --git a/stdnum/cr/cpj.py b/stdnum/cr/cpj.py index 2df838bd..4ead17c7 100644 --- a/stdnum/cr/cpj.py +++ b/stdnum/cr/cpj.py @@ -48,12 +48,13 @@ >>> format('4 000 042138') '4-000-042138' """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -62,7 +63,7 @@ def compact(number): return clean(number, ' -').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Costa Rica CPJ number. This checks the length and formatting. @@ -89,7 +90,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Costa Rica CPJ number.""" try: return bool(validate(number)) @@ -97,7 +98,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[0], number[1:4], number[4:]]) diff --git a/stdnum/cr/cr.py b/stdnum/cr/cr.py index a63e4148..7aa39939 100644 --- a/stdnum/cr/cr.py +++ b/stdnum/cr/cr.py @@ -50,12 +50,13 @@ >>> format('122200569906') '122200569906' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -64,7 +65,7 @@ def compact(number): return clean(number, ' -').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Costa Rica CR number. This checks the length and formatting. @@ -79,7 +80,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Costa Rica CR number.""" try: return bool(validate(number)) @@ -87,6 +88,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/cu/ni.py b/stdnum/cu/ni.py index 2202e683..96941c88 100644 --- a/stdnum/cu/ni.py +++ b/stdnum/cu/ni.py @@ -49,20 +49,22 @@ ... InvalidComponent: ... """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separation dash.""" return clean(number, ' ').strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the date of birth.""" number = compact(number) year = int(number[0:2]) @@ -80,7 +82,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the gender (M/F) from the person's NI.""" number = compact(number) if int(number[9]) % 2: @@ -89,7 +91,7 @@ def get_gender(number): return 'M' -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid NI. This checks the length, formatting and check digit.""" number = compact(number) @@ -101,7 +103,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid NI.""" try: return bool(validate(number)) diff --git a/stdnum/cusip.py b/stdnum/cusip.py index feaad0ce..856a3e4f 100644 --- a/stdnum/cusip.py +++ b/stdnum/cusip.py @@ -38,12 +38,13 @@ >>> to_isin('91324PAE2') 'US91324PAE25' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip().upper() @@ -52,7 +53,7 @@ def compact(number): _alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digits for the number.""" # convert to numeric first, then sum individual digits number = ''.join( @@ -60,7 +61,7 @@ def calc_check_digit(number): return str((10 - sum(int(n) for n in number)) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and check digit.""" number = compact(number) @@ -73,7 +74,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length and check digit.""" try: @@ -82,7 +83,7 @@ def is_valid(number): return False -def to_isin(number): +def to_isin(number: str) -> str: """Convert the number to an ISIN.""" from stdnum import isin return isin.from_natid('US', number) diff --git a/stdnum/cy/vat.py b/stdnum/cy/vat.py index ce9115cd..c58c6c35 100644 --- a/stdnum/cy/vat.py +++ b/stdnum/cy/vat.py @@ -32,12 +32,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -46,7 +47,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" translation = { @@ -59,7 +60,7 @@ def calc_check_digit(number): ) % 26] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -74,7 +75,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/cz/__init__.py b/stdnum/cz/__init__.py index fc749019..bed5e93f 100644 --- a/stdnum/cz/__init__.py +++ b/stdnum/cz/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Czech numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.cz import dic as vat # noqa: F401 diff --git a/stdnum/cz/bankaccount.py b/stdnum/cz/bankaccount.py index 3100c8a8..eb97b50f 100644 --- a/stdnum/cz/bankaccount.py +++ b/stdnum/cz/bankaccount.py @@ -47,6 +47,7 @@ >>> to_bic('34278-727558021/0100') 'KOMBCZPP' """ +from __future__ import annotations import re @@ -58,7 +59,7 @@ r'((?P[0-9]{0,6})-)?(?P[0-9]{2,10})\/(?P[0-9]{4})') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number).strip() @@ -71,7 +72,7 @@ def compact(number): return number -def _split(number): +def _split(number: str) -> tuple[str | None, str, str]: """Split valid numbers into prefix, root and bank parts of the number.""" match = _bankaccount_re.match(number) if not match: @@ -79,7 +80,7 @@ def _split(number): return match.group('prefix'), match.group('root'), match.group('bank') -def _info(bank): +def _info(bank: str) -> dict[str, str]: """Look up information for the bank.""" from stdnum import numdb info = {} @@ -88,29 +89,29 @@ def _info(bank): return info -def info(number): +def info(number: str) -> dict[str, str]: """Return a dictionary of data about the supplied number. This typically returns the name of the bank and branch and a BIC if it is valid.""" prefix, root, bank = _split(compact(number)) return _info(bank) -def to_bic(number): +def to_bic(number: str) -> str | None: """Return the BIC for the bank that this number refers to.""" - bic = info(number).get('bic') - if bic: - return str(bic) + return info(number).get('bic') -def _calc_checksum(number): +def _calc_checksum(number: str) -> int: weights = (6, 3, 7, 9, 10, 5, 8, 4, 2, 1) return sum(w * int(n) for w, n in zip(weights, number.zfill(10))) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid bank account number.""" number = compact(number) prefix, root, bank = _split(number) + # guaranteed to be present if compact is called and _split doesn't raise + assert prefix if _calc_checksum(prefix) != 0: raise InvalidChecksum() if _calc_checksum(root) != 0: @@ -120,7 +121,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid bank account number.""" try: return bool(validate(number)) @@ -128,6 +129,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/cz/dic.py b/stdnum/cz/dic.py index 475521d7..346580ab 100644 --- a/stdnum/cz/dic.py +++ b/stdnum/cz/dic.py @@ -39,13 +39,14 @@ >>> validate('640903926') # special case '640903926' """ +from __future__ import annotations from stdnum.cz import rc from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' /').upper().strip() @@ -54,21 +55,21 @@ def compact(number): return number -def calc_check_digit_legal(number): +def calc_check_digit_legal(number: str) -> str: """Calculate the check digit for 8 digit legal entities. The number passed should not have the check digit included.""" check = (11 - sum((8 - i) * int(n) for i, n in enumerate(number))) % 11 return str((check or 1) % 10) -def calc_check_digit_special(number): +def calc_check_digit_special(number: str) -> str: """Calculate the check digit for special cases. The number passed should not have the first and last digits included.""" check = sum((8 - i) * int(n) for i, n in enumerate(number)) % 11 return str((8 - (10 - check) % 11) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -92,7 +93,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/cz/rc.py b/stdnum/cz/rc.py index 627c3122..432083ab 100644 --- a/stdnum/cz/rc.py +++ b/stdnum/cz/rc.py @@ -46,6 +46,7 @@ >>> format('7103192745') '710319/2745' """ +from __future__ import annotations import datetime @@ -53,13 +54,13 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' /').upper().strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) year = 1900 + int(number[0:2]) @@ -81,7 +82,7 @@ def get_birth_date(number): raise InvalidComponent() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid birth number. This checks the length, formatting, embedded date and check digit.""" number = compact(number) @@ -103,7 +104,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid birth number.""" try: return bool(validate(number)) @@ -111,7 +112,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:6] + '/' + number[6:] diff --git a/stdnum/damm.py b/stdnum/damm.py index e5fb2565..ecedc51c 100644 --- a/stdnum/damm.py +++ b/stdnum/damm.py @@ -52,10 +52,14 @@ >>> checksum('816', table=table) 9 """ +from __future__ import annotations +from stdnum import _typing as t from stdnum.exceptions import * +Table: t.TypeAlias = 't.Sequence[t.Sequence[int]]' + _operation_table = ( (0, 3, 1, 7, 5, 9, 8, 6, 4, 2), (7, 0, 9, 2, 1, 5, 4, 8, 6, 3), @@ -69,7 +73,7 @@ (2, 5, 8, 1, 4, 3, 6, 7, 9, 0)) -def checksum(number, table=None): +def checksum(number: str, table: Table | None = None) -> int: """Calculate the Damm checksum over the provided number. The checksum is returned as an integer value and should be 0 when valid.""" table = table or _operation_table @@ -79,7 +83,7 @@ def checksum(number, table=None): return i -def validate(number, table=None): +def validate(number: str, table: Table | None = None) -> str: """Check if the number provided passes the Damm algorithm.""" if not bool(number): raise InvalidFormat() @@ -92,7 +96,7 @@ def validate(number, table=None): return number -def is_valid(number, table=None): +def is_valid(number: str, table: Table | None = None) -> bool: """Check if the number provided passes the Damm algorithm.""" try: return bool(validate(number, table=table)) @@ -100,7 +104,7 @@ def is_valid(number, table=None): return False -def calc_check_digit(number, table=None): +def calc_check_digit(number: str, table: Table | None = None) -> str: """Calculate the extra digit that should be appended to the number to make it a valid number.""" return str(checksum(number, table=table)) diff --git a/stdnum/de/__init__.py b/stdnum/de/__init__.py index 1449961d..6268b819 100644 --- a/stdnum/de/__init__.py +++ b/stdnum/de/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of German numbers.""" +from __future__ import annotations # provide businessid as an alias from stdnum.de import handelsregisternummer as businessid # noqa: F401 diff --git a/stdnum/de/handelsregisternummer.py b/stdnum/de/handelsregisternummer.py index cce30433..ea34bcc6 100644 --- a/stdnum/de/handelsregisternummer.py +++ b/stdnum/de/handelsregisternummer.py @@ -49,10 +49,12 @@ ... InvalidComponent: ... """ +from __future__ import annotations import re import unicodedata +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean @@ -213,7 +215,7 @@ ) -def _to_min(court): +def _to_min(court: str) -> str: """Convert the court name for quick comparison without encoding issues.""" return ''.join( x for x in unicodedata.normalize('NFD', court.lower()) @@ -280,7 +282,7 @@ def _to_min(court): ] -def _split(number): +def _split(number: str) -> tuple[str, str, str, str | None]: """Split the number into a court, registry, register number and optionally qualifier.""" number = clean(number).strip() @@ -291,17 +293,18 @@ def _split(number): raise InvalidFormat() -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" court, registry, number, qualifier = _split(number) return ' '.join(x for x in [court, registry, number, qualifier] if x) -def validate(number, company_form=None): +def validate(number: str, company_form: str | None = None) -> str: """Check if the number is a valid company registry number. If a company_form (eg. GmbH or PartG) is given, the number is validated to have the correct registry type.""" + court: str | None court, registry, number, qualifier = _split(number) court = _courts.get(_to_min(court)) if not court: @@ -311,7 +314,7 @@ def validate(number, company_form=None): return ' '.join(x for x in [court, registry, number, qualifier] if x) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid company registry number.""" try: return bool(validate(number)) @@ -323,7 +326,11 @@ def is_valid(number): _offeneregister_url = 'https://db.offeneregister.de/openregister.json' -def check_offeneregister(number, timeout=30, verify=True): # pragma: no cover (not part of normal test suite) +def check_offeneregister( + number: str, + timeout: float = 30, + verify: bool = True, +) -> dict[str, t.Any] | None: # pragma: no cover (not part of normal test suite) """Retrieve registration information from the OffeneRegister.de web site. The `timeout` argument specifies the network timeout in seconds. @@ -373,4 +380,4 @@ def check_offeneregister(number, timeout=30, verify=True): # pragma: no cover ( json = response.json() return dict(zip(json['columns'], json['rows'][0])) except (KeyError, IndexError) as e: # noqa: F841 - return # number not found + return None # number not found diff --git a/stdnum/de/idnr.py b/stdnum/de/idnr.py index 5c5a1797..b34764b9 100644 --- a/stdnum/de/idnr.py +++ b/stdnum/de/idnr.py @@ -44,6 +44,7 @@ >>> format('36574261809') '36 574 261 809' """ +from __future__ import annotations from collections import defaultdict @@ -52,13 +53,13 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -./,').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid tax identification number. This checks the length, formatting and check digit.""" number = compact(number) @@ -70,7 +71,7 @@ def validate(number): raise InvalidFormat() # In the first 10 digits exactly one digit must be repeated two or # three times and other digits can appear only once. - counter = defaultdict(int) + counter: dict[str, int] = defaultdict(int) for n in number[:10]: counter[n] += 1 counts = [c for c in counter.values() if c > 1] @@ -79,7 +80,7 @@ def validate(number): return mod_11_10.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid tax identification number. This checks the length, formatting and check digit.""" try: @@ -88,7 +89,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:2], number[2:5], number[5:8], number[8:])) diff --git a/stdnum/de/stnr.py b/stdnum/de/stnr.py index 6c511f36..6b97a38d 100644 --- a/stdnum/de/stnr.py +++ b/stdnum/de/stnr.py @@ -49,15 +49,17 @@ ... InvalidLength: ... """ +from __future__ import annotations import re +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits # The number formats per region (regional and country-wide format) -_number_formats_per_region = { +_number_formats_per_region_raw = { 'Baden-Württemberg': ['FFBBBUUUUP', '28FF0BBBUUUUP'], 'Bayern': ['FFFBBBUUUUP', '9FFF0BBBUUUUP'], 'Berlin': ['FFBBBUUUUP', '11FF0BBBUUUUP'], @@ -76,11 +78,11 @@ 'Thüringen': ['1FFBBBUUUUP', '41FF0BBBUUUUP'], } -REGIONS = sorted(_number_formats_per_region.keys()) +REGIONS = sorted(_number_formats_per_region_raw.keys()) """Valid regions recognised by this module.""" -def _clean_region(region): +def _clean_region(region: str) -> str: """Convert the region name to something that we can use for comparison without running into encoding issues.""" return ''.join( @@ -90,28 +92,28 @@ def _clean_region(region): class _Format(): - def __init__(self, fmt): + def __init__(self, fmt: str) -> None: self._fmt = fmt self._re = re.compile('^%s$' % re.sub( r'([FBUP])\1*', lambda x: r'(\d{%d})' % len(x.group(0)), fmt)) - def match(self, number): + def match(self, number: str) -> re.Match[str] | None: return self._re.match(number) - def replace(self, f, b, u, p): + def replace(self, f: str, b: str, u: str, p: str) -> str: items = iter([f, b, u, p]) return re.sub(r'([FBUP])\1*', lambda x: next(items), self._fmt) # Convert the structure to something that we can easily use _number_formats_per_region = dict( - (_clean_region(region), [ - region, _Format(formats[0]), _Format(formats[1])]) - for region, formats in _number_formats_per_region.items()) + (_clean_region(region), ( + region, _Format(formats[0]), _Format(formats[1]))) + for region, formats in _number_formats_per_region_raw.items()) -def _get_formats(region=None): +def _get_formats(region: str | None = None) -> t.Iterable[tuple[str, _Format, _Format]]: """Return the formats for the region.""" if region: region = _clean_region(region) @@ -121,13 +123,13 @@ def _get_formats(region=None): return _number_formats_per_region.values() -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -./,').strip() -def validate(number, region=None): +def validate(number: str, region: str | None = None) -> str: """Check if the number is a valid tax number. This checks the length and formatting. The region can be supplied to verify that the number is assigned in that region.""" @@ -142,7 +144,7 @@ def validate(number, region=None): return number -def is_valid(number, region=None): +def is_valid(number: str, region: str | None = None) -> bool: """Check if the number is a valid tax number. This checks the length and formatting. The region can be supplied to verify that the number is assigned in that region.""" @@ -152,7 +154,7 @@ def is_valid(number, region=None): return False -def guess_regions(number): +def guess_regions(number: str) -> list[str]: """Return a list of regions this number is valid for.""" number = compact(number) return sorted( @@ -160,7 +162,7 @@ def guess_regions(number): if region_fmt.match(number) or country_fmt.match(number)) -def to_regional_number(number): +def to_regional_number(number: str) -> str: """Convert the number to a regional (10 or 11 digit) number.""" number = compact(number) for _region, region_fmt, country_fmt in _get_formats(): @@ -170,16 +172,16 @@ def to_regional_number(number): raise InvalidFormat() -def to_country_number(number, region=None): +def to_country_number(number: str, region: str | None = None) -> str: """Convert the number to the nationally unique number. The region is needed if the number is not only valid for one particular region.""" number = compact(number) - formats = ( + formats_iter = ( (region_fmt.match(number), country_fmt) for _region, region_fmt, country_fmt in _get_formats(region)) formats = [ (region_match, country_fmt) - for region_match, country_fmt in formats + for region_match, country_fmt in formats_iter if region_match] if not formats: raise InvalidFormat() @@ -188,7 +190,7 @@ def to_country_number(number, region=None): return formats[0][1].replace(*formats[0][0].groups()) -def format(number, region=None): +def format(number: str, region: str | None = None) -> str: """Reformat the passed number to the standard format.""" number = compact(number) for _region, region_fmt, _country_fmt in _get_formats(region): diff --git a/stdnum/de/vat.py b/stdnum/de/vat.py index f2e9ca69..73b17a62 100644 --- a/stdnum/de/vat.py +++ b/stdnum/de/vat.py @@ -31,13 +31,14 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_11_10 from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -./,').upper().strip() @@ -46,7 +47,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -58,7 +59,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/de/wkn.py b/stdnum/de/wkn.py index f2242bd6..f6921792 100644 --- a/stdnum/de/wkn.py +++ b/stdnum/de/wkn.py @@ -33,12 +33,13 @@ >>> to_isin('SKWM02') 'DE000SKWM021' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip().upper() @@ -48,7 +49,7 @@ def compact(number): _alphabet = '0123456789ABCDEFGH JKLMN PQRSTUVWXYZ' -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and check digit.""" number = compact(number) @@ -59,7 +60,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length and check digit.""" try: @@ -68,7 +69,7 @@ def is_valid(number): return False -def to_isin(number): +def to_isin(number: str) -> str: """Convert the number to an ISIN.""" from stdnum import isin return isin.from_natid('DE', number) diff --git a/stdnum/dk/__init__.py b/stdnum/dk/__init__.py index 4298d222..591e55ab 100644 --- a/stdnum/dk/__init__.py +++ b/stdnum/dk/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Danish numbers.""" +from __future__ import annotations # provide aliases from stdnum.dk import cpr as personalid # noqa: F401 diff --git a/stdnum/dk/cpr.py b/stdnum/dk/cpr.py index 97eaf2b6..975d6596 100644 --- a/stdnum/dk/cpr.py +++ b/stdnum/dk/cpr.py @@ -55,6 +55,7 @@ >>> format('2110625629') '211062-5629' """ +from __future__ import annotations import datetime @@ -62,20 +63,20 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum. Note that the checksum isn't actually used any more. Valid numbers used to have a checksum of 0.""" weights = (4, 3, 2, 7, 6, 5, 4, 3, 2, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) day = int(number[0:2]) @@ -93,7 +94,7 @@ def get_birth_date(number): raise InvalidComponent('The number does not contain valid birth date information.') -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid CPR number. This checks the length, formatting, embedded date and check digit.""" number = compact(number) @@ -107,7 +108,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid CPR number. This checks the length, formatting, embedded date and check digit.""" try: @@ -116,7 +117,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join((number[:6], number[6:])) diff --git a/stdnum/dk/cvr.py b/stdnum/dk/cvr.py index aa80402d..762b39b9 100644 --- a/stdnum/dk/cvr.py +++ b/stdnum/dk/cvr.py @@ -29,12 +29,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.,/:').upper().strip() @@ -43,13 +44,13 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (2, 7, 6, 5, 4, 3, 2, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -62,7 +63,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/do/__init__.py b/stdnum/do/__init__.py index aa210e59..f8e2f302 100644 --- a/stdnum/do/__init__.py +++ b/stdnum/do/__init__.py @@ -19,5 +19,6 @@ # 02110-1301 USA """Collection of Dominican Republic numbers.""" +from __future__ import annotations from stdnum.do import rnc as vat # noqa: F401 diff --git a/stdnum/do/cedula.py b/stdnum/do/cedula.py index 7cf46271..0a79a0e6 100644 --- a/stdnum/do/cedula.py +++ b/stdnum/do/cedula.py @@ -36,7 +36,9 @@ >>> format('22400022111') '224-0002211-1' """ +from __future__ import annotations +from stdnum import _typing as t from stdnum import luhn from stdnum.do import rnc from stdnum.exceptions import * @@ -145,13 +147,13 @@ '''.split()) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid cedula.""" number = compact(number) if not isdigits(number): @@ -163,7 +165,7 @@ def validate(number): return luhn.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid cedula.""" try: return bool(validate(number)) @@ -171,13 +173,13 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join((number[:3], number[3:-1], number[-1])) -def check_dgii(number, timeout=30): # pragma: no cover +def check_dgii(number: str, timeout: float = 30) -> dict[str, t.Any] | None: # pragma: no cover """Lookup the number using the DGII online web service. This uses the validation service run by the the Dirección General de diff --git a/stdnum/do/ncf.py b/stdnum/do/ncf.py index 39c17a78..83cc1f66 100644 --- a/stdnum/do/ncf.py +++ b/stdnum/do/ncf.py @@ -55,12 +55,14 @@ ... InvalidFormat: ... """ +from __future__ import annotations +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip().upper() @@ -95,7 +97,7 @@ def compact(number): ) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid NCF.""" number = compact(number) if len(number) == 13: @@ -118,7 +120,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid NCF.""" try: return bool(validate(number)) @@ -126,7 +128,7 @@ def is_valid(number): return False -def _convert_result(result): # pragma: no cover +def _convert_result(result: t.Mapping[str, str]) -> dict[str, str]: # pragma: no cover """Translate SOAP result entries into dictionaries.""" translation = { 'NOMBRE': 'name', @@ -157,7 +159,14 @@ def _convert_result(result): # pragma: no cover for key, value in result.items()) -def check_dgii(rnc, ncf, buyer_rnc=None, security_code=None, timeout=30, verify=True): # pragma: no cover +def check_dgii( + rnc: str, + ncf: str, + buyer_rnc: str | None = None, + security_code: str | None = None, + timeout: float = 30, + verify: bool = True, +) -> dict[str, str] | None: # pragma: no cover """Validate the RNC, NCF combination on using the DGII online web service. This uses the validation service run by the the Dirección General de @@ -198,7 +207,7 @@ def check_dgii(rnc, ncf, buyer_rnc=None, security_code=None, timeout=30, verify= } Will return None if the number is invalid or unknown.""" - import lxml.html + import lxml.html # type: ignore import requests from stdnum.do.rnc import compact as rnc_compact # noqa: I003 rnc = rnc_compact(rnc) @@ -240,3 +249,4 @@ def check_dgii(rnc, ncf, buyer_rnc=None, security_code=None, timeout=30, verify= [x.text.strip() for x in result.findall('.//th') if x.text], [x.text.strip() for x in result.findall('.//td/span') if x.text])) return _convert_result(data) + return None diff --git a/stdnum/do/rnc.py b/stdnum/do/rnc.py index fdb4f04b..15ec9e09 100644 --- a/stdnum/do/rnc.py +++ b/stdnum/do/rnc.py @@ -38,9 +38,11 @@ >>> format('131246796') '1-31-24679-6' """ +from __future__ import annotations import json +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, get_soap_client, isdigits @@ -58,20 +60,20 @@ """The WSDL URL of DGII validation service.""" -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (7, 9, 8, 6, 5, 4, 3, 2) check = sum(w * int(n) for w, n in zip(weights, number)) % 11 return str((10 - check) % 9 + 1) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid RNC.""" number = compact(number) if not isdigits(number): @@ -85,7 +87,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid RNC.""" try: return bool(validate(number)) @@ -93,13 +95,13 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join((number[:1], number[1:3], number[3:-1], number[-1])) -def _convert_result(result): # pragma: no cover +def _convert_result(result: str) -> dict[str, t.Any]: # pragma: no cover """Translate SOAP result entries into dicts.""" translation = { 'RGE_RUC': 'rnc', @@ -115,7 +117,11 @@ def _convert_result(result): # pragma: no cover for key, value in json.loads(result.replace('\n', '\\n').replace('\t', '\\t')).items()) -def check_dgii(number, timeout=30, verify=True): # pragma: no cover +def check_dgii( + number: str, + timeout: float = 30, + verify: bool = True, +) -> dict[str, t.Any] | None: # pragma: no cover """Lookup the number using the DGII online web service. This uses the validation service run by the the Dirección General de @@ -153,12 +159,18 @@ def check_dgii(number, timeout=30, verify=True): # pragma: no cover if result and 'GetContribuyentesResult' in result: result = result['GetContribuyentesResult'] # PySimpleSOAP only if result == '0': - return + return None result = [x for x in result.split('@@@')] return _convert_result(result[0]) -def search_dgii(keyword, end_at=10, start_at=1, timeout=30, verify=True): # pragma: no cover +def search_dgii( + keyword: str, + end_at: int = 10, + start_at: int = 1, + timeout: float = 30, + verify: bool = True, +) -> list[dict[str, t.Any]]: # pragma: no cover """Search the DGII online web service using the keyword. This uses the validation service run by the the Dirección General de diff --git a/stdnum/dz/__init__.py b/stdnum/dz/__init__.py index c0e8e9f8..27daf443 100644 --- a/stdnum/dz/__init__.py +++ b/stdnum/dz/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Algerian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.dz import nif as vat # noqa: F401 diff --git a/stdnum/dz/nif.py b/stdnum/dz/nif.py index 48a16fd2..d5f42bad 100644 --- a/stdnum/dz/nif.py +++ b/stdnum/dz/nif.py @@ -59,12 +59,13 @@ >>> format('000 216 001 808 337 13010') '00021600180833713010' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators, removes surrounding @@ -73,7 +74,7 @@ def compact(number): return clean(number, ' ') -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Algeria NIF number. This checks the length and formatting. @@ -86,7 +87,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Algeria NIF number.""" try: return bool(validate(number)) @@ -94,6 +95,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/ean.py b/stdnum/ean.py index f7e4b63c..f601fc44 100644 --- a/stdnum/ean.py +++ b/stdnum/ean.py @@ -29,25 +29,26 @@ >>> validate('98412345678908') # GTIN format '98412345678908' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the EAN to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the EAN check digit for 13-digit numbers. The number passed should not have the check bit included.""" return str((10 - sum((3, 1)[i % 2] * int(n) for i, n in enumerate(reversed(number)))) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid EAN-13. This checks the length and the check bit but does not check whether a known GS1 Prefix and company identifier are referenced.""" @@ -61,7 +62,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid EAN-13. This checks the length and the check bit but does not check whether a known GS1 Prefix and company identifier are referenced.""" diff --git a/stdnum/ec/__init__.py b/stdnum/ec/__init__.py index 6e919064..d020d6c5 100644 --- a/stdnum/ec/__init__.py +++ b/stdnum/ec/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Ecuadorian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.ec import ruc as vat # noqa: F401 diff --git a/stdnum/ec/ci.py b/stdnum/ec/ci.py index c97c1937..d554af57 100644 --- a/stdnum/ec/ci.py +++ b/stdnum/ec/ci.py @@ -34,25 +34,27 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def _checksum(number): +def _checksum(number: str) -> int: """Calculate a checksum over the number.""" - fold = lambda x: x - 9 if x > 9 else x + def fold(x: int) -> int: + return x - 9 if x > 9 else x return sum(fold((2, 1)[i % 2] * int(n)) for i, n in enumerate(number)) % 10 -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid CI number. This checks the length, formatting and check digit.""" number = compact(number) @@ -69,7 +71,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid CI number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/ec/ruc.py b/stdnum/ec/ruc.py index c49b4fc5..bc4bffef 100644 --- a/stdnum/ec/ruc.py +++ b/stdnum/ec/ruc.py @@ -35,7 +35,9 @@ ... InvalidLength: ... """ +from __future__ import annotations +from stdnum import _typing as t from stdnum.ec import ci from stdnum.exceptions import * from stdnum.util import isdigits @@ -48,12 +50,12 @@ compact = ci.compact -def _checksum(number, weights): +def _checksum(number: str, weights: t.Iterable[int]) -> int: """Calculate a checksum over the number given the weights.""" return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def _validate_natural(number): +def _validate_natural(number: str) -> str: """Check if the number is a valid natural RUC (CI plus establishment).""" if number[-3:] == '000': raise InvalidComponent() # establishment number wrong @@ -61,7 +63,7 @@ def _validate_natural(number): return number -def _validate_public(number): +def _validate_public(number: str) -> str: """Check if the number is a valid public RUC.""" if number[-4:] == '0000': raise InvalidComponent() # establishment number wrong @@ -70,7 +72,7 @@ def _validate_public(number): return number -def _validate_juridical(number): +def _validate_juridical(number: str) -> str: """Check if the number is a valid juridical RUC.""" if number[-3:] == '000': raise InvalidComponent() # establishment number wrong @@ -79,7 +81,7 @@ def _validate_juridical(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid RUC number. This checks the length, formatting, check digit and check sum.""" number = compact(number) @@ -109,7 +111,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid RUC number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/ee/__init__.py b/stdnum/ee/__init__.py index 7728e2d6..fcca7d61 100644 --- a/stdnum/ee/__init__.py +++ b/stdnum/ee/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Estonian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.ee import kmkr as vat # noqa: F401 diff --git a/stdnum/ee/ik.py b/stdnum/ee/ik.py index be68fc6b..80798aed 100644 --- a/stdnum/ee/ik.py +++ b/stdnum/ee/ik.py @@ -38,20 +38,22 @@ >>> get_birth_date('36805280109') datetime.date(1968, 5, 28) """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) if number[0] in '12': @@ -73,7 +75,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the person's birth gender ('M' or 'F').""" number = compact(number) if number[0] in '1357': @@ -84,7 +86,7 @@ def get_gender(number): raise InvalidComponent() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" check = sum(((i % 9) + 1) * int(n) for i, n in enumerate(number[:-1])) % 11 @@ -94,7 +96,7 @@ def calc_check_digit(number): return str(check % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length, formatting, embedded date and check digit.""" number = compact(number) @@ -108,7 +110,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length, formatting, embedded date and check digit.""" try: diff --git a/stdnum/ee/kmkr.py b/stdnum/ee/kmkr.py index 24ddbb38..12ade7d7 100644 --- a/stdnum/ee/kmkr.py +++ b/stdnum/ee/kmkr.py @@ -29,12 +29,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').upper().strip() @@ -43,13 +44,13 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (3, 7, 1, 3, 7, 1, 3, 7, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 10 -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -62,7 +63,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/ee/registrikood.py b/stdnum/ee/registrikood.py index 8f0ebc1a..c717a56a 100644 --- a/stdnum/ee/registrikood.py +++ b/stdnum/ee/registrikood.py @@ -46,19 +46,20 @@ ... InvalidComponent: ... """ +from __future__ import annotations from stdnum.ee.ik import calc_check_digit from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and check digit.""" number = compact(number) @@ -73,7 +74,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length and check digit.""" try: diff --git a/stdnum/eg/__init__.py b/stdnum/eg/__init__.py index dfa5d085..bab3340d 100644 --- a/stdnum/eg/__init__.py +++ b/stdnum/eg/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Egypt numbers.""" +from __future__ import annotations # provide aliases from stdnum.eg import tn as vat # noqa: F401 diff --git a/stdnum/eg/tn.py b/stdnum/eg/tn.py index aef9ca54..b6573a58 100644 --- a/stdnum/eg/tn.py +++ b/stdnum/eg/tn.py @@ -43,6 +43,7 @@ >>> format('100531385') '100-531-385' """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -74,7 +75,7 @@ } -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -83,7 +84,7 @@ def compact(number): return ''.join((_ARABIC_NUMBERS_MAP.get(c, c) for c in clean(number, ' -/').strip())) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Egypt Tax Number number. This checks the length and formatting. @@ -96,7 +97,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Egypt Tax Number number.""" try: return bool(validate(number)) @@ -104,7 +105,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:3], number[3:-3], number[-3:]]) diff --git a/stdnum/es/__init__.py b/stdnum/es/__init__.py index e74786c8..f97eaace 100644 --- a/stdnum/es/__init__.py +++ b/stdnum/es/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Spanish numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.es import nif as vat # noqa: F401 diff --git a/stdnum/es/cae.py b/stdnum/es/cae.py index 19b97173..dcd5453c 100644 --- a/stdnum/es/cae.py +++ b/stdnum/es/cae.py @@ -49,6 +49,7 @@ >>> compact('ES00008V1488Q') 'ES00008V1488Q' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -203,13 +204,13 @@ } -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number).upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid CAE number. This checks the length and formatting.""" number = compact(number) @@ -230,7 +231,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid CAE number. This checks the length and formatting.""" try: diff --git a/stdnum/es/ccc.py b/stdnum/es/ccc.py index 3306c200..9f64c13f 100644 --- a/stdnum/es/ccc.py +++ b/stdnum/es/ccc.py @@ -61,18 +61,19 @@ >>> to_iban('21000418450200051331') 'ES2121000418450200051331' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join([ @@ -84,13 +85,13 @@ def format(number): ]) -def _calc_check_digit(number): +def _calc_check_digit(number: str) -> str: """Calculate a single check digit on the provided part of the number.""" check = sum(int(n) * 2 ** i for i, n in enumerate(number)) % 11 return str(check if check < 2 else 11 - check) -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the number. The supplied number should have check digits included but are ignored.""" number = compact(number) @@ -98,7 +99,7 @@ def calc_check_digits(number): _calc_check_digit('00' + number[:8]) + _calc_check_digit(number[10:])) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid CCC.""" number = compact(number) if len(number) != 20: @@ -110,7 +111,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid CCC.""" try: return bool(validate(number)) @@ -118,7 +119,7 @@ def is_valid(number): return False -def to_iban(number): +def to_iban(number: str) -> str: """Convert the number to an IBAN.""" from stdnum import iban separator = ' ' if ' ' in number else '' diff --git a/stdnum/es/cif.py b/stdnum/es/cif.py index 395db81c..86599070 100644 --- a/stdnum/es/cif.py +++ b/stdnum/es/cif.py @@ -49,6 +49,7 @@ >>> split('A13 585 625') ('A', '13', '58562', '5') """ +from __future__ import annotations from stdnum import luhn from stdnum.es import dni @@ -63,7 +64,7 @@ compact = dni.compact -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the specified number. The number passed should not have the check digit included. This function returns both the number and character check digit candidates.""" @@ -71,7 +72,7 @@ def calc_check_digits(number): return check + 'JABCDEFGHI'[int(check)] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid DNI number. This checks the length, formatting and check digit.""" number = compact(number) @@ -91,7 +92,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid DNI number. This checks the length, formatting and check digit.""" try: @@ -100,7 +101,7 @@ def is_valid(number): return False -def split(number): +def split(number: str) -> tuple[str, str, str, str]: """Split the provided number into a letter to define the type of organisation, two digits that specify a province, a 5 digit sequence number within the province and a check digit.""" diff --git a/stdnum/es/cups.py b/stdnum/es/cups.py index cbbcacd2..3792d60d 100644 --- a/stdnum/es/cups.py +++ b/stdnum/es/cups.py @@ -52,18 +52,19 @@ >>> format('ES1234123456789012JY1F') 'ES 1234 1234 5678 9012 JY 1F' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join(( @@ -77,14 +78,14 @@ def format(number): )).strip() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the number.""" alphabet = 'TRWAGMYFPDXBNJZSQVHLCKE' check0, check1 = divmod(int(number[2:18]) % 529, 23) return alphabet[check0] + alphabet[check1] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid CUPS. This checks length, formatting and check digits.""" number = compact(number) @@ -94,8 +95,8 @@ def validate(number): raise InvalidComponent() if not isdigits(number[2:18]): raise InvalidFormat() - if number[20:]: - pnumber, ptype = number[20:] + if len(number) == 22: + pnumber, ptype = number[20], number[21] if not isdigits(pnumber): raise InvalidFormat() if ptype not in 'FPRCXYZ': @@ -105,7 +106,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid CUPS.""" try: return bool(validate(number)) diff --git a/stdnum/es/dni.py b/stdnum/es/dni.py index 379ac2f6..f9896f19 100644 --- a/stdnum/es/dni.py +++ b/stdnum/es/dni.py @@ -37,24 +37,25 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" return 'TRWAGMYFPDXBNJZSQVHLCKE'[int(number) % 23] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid DNI number. This checks the length, formatting and check digit.""" number = compact(number) @@ -67,7 +68,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid DNI number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/es/iban.py b/stdnum/es/iban.py index 9c312969..876b7505 100644 --- a/stdnum/es/iban.py +++ b/stdnum/es/iban.py @@ -43,6 +43,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum import iban from stdnum.es import ccc @@ -56,7 +57,7 @@ format = iban.format -def to_ccc(number): +def to_ccc(number: str) -> str: """Return the CCC (Código Cuenta Corriente) part of the number.""" number = compact(number) if not number.startswith('ES'): @@ -64,14 +65,14 @@ def to_ccc(number): return number[4:] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid Spanish IBAN.""" number = iban.validate(number, check_country=False) ccc.validate(to_ccc(number)) return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid Spanish IBAN.""" try: return bool(validate(number)) diff --git a/stdnum/es/nie.py b/stdnum/es/nie.py index 0c0a73bf..14975c69 100644 --- a/stdnum/es/nie.py +++ b/stdnum/es/nie.py @@ -39,6 +39,7 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum.es import dni from stdnum.exceptions import * @@ -52,7 +53,7 @@ compact = dni.compact -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" # replace XYZ with 012 @@ -60,7 +61,7 @@ def calc_check_digit(number): return dni.calc_check_digit(number) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid NIE. This checks the length, formatting and check digit.""" number = compact(number) @@ -73,7 +74,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid NIE. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/es/nif.py b/stdnum/es/nif.py index 76f05706..13cd4036 100644 --- a/stdnum/es/nif.py +++ b/stdnum/es/nif.py @@ -43,13 +43,14 @@ >>> validate('M-1234567-L') # foreign person without NIE 'M1234567L' """ +from __future__ import annotations from stdnum.es import cif, dni, nie from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -58,7 +59,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -85,7 +86,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/es/postal_code.py b/stdnum/es/postal_code.py index 0087fd1d..b04a458e 100644 --- a/stdnum/es/postal_code.py +++ b/stdnum/es/postal_code.py @@ -53,18 +53,18 @@ ... InvalidLength: ... """ - +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" return clean(number, ' ').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid postal code.""" number = compact(number) if len(number) != 5: @@ -76,7 +76,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid postal code.""" try: return bool(validate(number)) diff --git a/stdnum/es/referenciacatastral.py b/stdnum/es/referenciacatastral.py index 3e7eae35..05b55284 100644 --- a/stdnum/es/referenciacatastral.py +++ b/stdnum/es/referenciacatastral.py @@ -53,6 +53,7 @@ >>> format('4A08169P03PRAT0001LR') # BCN Airport '4A08169 P03PRAT 0001 LR' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean @@ -61,13 +62,13 @@ alphabet = 'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ0123456789' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join([ @@ -81,7 +82,7 @@ def format(number): # implementation by Vicente Sancho that can be found at # https://trellat.es/validar-la-referencia-catastral-en-javascript/ -def _check_digit(number): +def _check_digit(number: str) -> str: """Calculate a single check digit on the provided part of the number.""" weights = (13, 15, 12, 5, 4, 17, 9, 21, 3, 7, 1) s = sum(w * (int(n) if n.isdigit() else alphabet.find(n) + 1) @@ -89,7 +90,7 @@ def _check_digit(number): return 'MQWERTYUIOPASDFGHJKLBZX'[s % 23] -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the number.""" number = compact(number) return ( @@ -97,7 +98,7 @@ def calc_check_digits(number): _check_digit(number[7:14] + number[14:18])) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Cadastral Reference. This checks the length, formatting and check digits.""" number = compact(number) @@ -110,7 +111,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Cadastral Reference.""" try: return bool(validate(number)) diff --git a/stdnum/eu/at_02.py b/stdnum/eu/at_02.py index acafa909..66619172 100644 --- a/stdnum/eu/at_02.py +++ b/stdnum/eu/at_02.py @@ -37,6 +37,7 @@ >>> calc_check_digits('ESXXZZZ47690558N') '23' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_97_10 @@ -47,20 +48,20 @@ _alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' -def compact(number): +def compact(number: str) -> str: """Convert the AT-02 number to the minimal representation. This strips the number of any valid separators and removes invalid characters.""" return clean(number, ' -/?:().m\'+"').strip().upper() -def _to_base10(number): +def _to_base10(number: str) -> str: """Prepare the number to its base10 representation so it can be checked with the ISO 7064 Mod 97, 10 algorithm. That means excluding positions 5 to 7 and moving the first four digits to the end.""" return ''.join(str(_alphabet.index(x)) for x in number[7:] + number[:4]) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid AT-02.""" number = compact(number) try: @@ -72,7 +73,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid AT-02.""" try: return bool(validate(number)) @@ -80,7 +81,7 @@ def is_valid(number): return False -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits that should be put in the number to make it valid. Check digits in the supplied number are ignored.""" number = compact(number) diff --git a/stdnum/eu/banknote.py b/stdnum/eu/banknote.py index 24860e0f..a5ac7b67 100644 --- a/stdnum/eu/banknote.py +++ b/stdnum/eu/banknote.py @@ -33,24 +33,25 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').upper().strip() -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum over the number.""" # replace letters by their ASCII number return sum(int(x) if isdigits(x) else ord(x) for x in number) % 9 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid banknote serial number.""" number = compact(number) if not number[:2].isalnum() or not isdigits(number[2:]): @@ -64,7 +65,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid banknote serial number.""" try: return bool(validate(number)) diff --git a/stdnum/eu/ecnumber.py b/stdnum/eu/ecnumber.py index a4054960..0029eed5 100644 --- a/stdnum/eu/ecnumber.py +++ b/stdnum/eu/ecnumber.py @@ -37,6 +37,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations import re @@ -47,7 +48,7 @@ _ec_number_re = re.compile(r'^[0-9]{3}-[0-9]{3}-[0-9]$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" number = clean(number, ' ').strip() if '-' not in number: @@ -55,7 +56,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number. The passed number should not have the check digit included.""" number = compact(number).replace('-', '') @@ -63,7 +64,7 @@ def calc_check_digit(number): sum((i + 1) * int(n) for i, n in enumerate(number)) % 11)[0] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid EC Number.""" number = compact(number) if not len(number) == 9: @@ -75,7 +76,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid EC Number.""" try: return bool(validate(number)) diff --git a/stdnum/eu/eic.py b/stdnum/eu/eic.py index 63425183..fc3ecad4 100644 --- a/stdnum/eu/eic.py +++ b/stdnum/eu/eic.py @@ -43,6 +43,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean @@ -51,20 +52,20 @@ _alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding white space.""" return clean(number, ' ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number.""" number = compact(number) s = sum((16 - i) * _alphabet.index(n) for i, n in enumerate(number[:15])) return _alphabet[36 - ((s - 1) % 37)] -def validate(number): +def validate(number: str) -> str: """Check if the number is valid. This checks the length, format and check digit.""" number = compact(number) @@ -79,7 +80,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is valid. This checks the length, format and check digit.""" try: diff --git a/stdnum/eu/nace.py b/stdnum/eu/nace.py index d667f4f3..9625e308 100644 --- a/stdnum/eu/nace.py +++ b/stdnum/eu/nace.py @@ -51,20 +51,20 @@ >>> format('6201') '62.01' """ # noqa: E501 +from __future__ import annotations -import warnings - +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '.').strip() -def info(number): +def info(number: str) -> dict[str, str]: """Lookup information about the specified NACE. This returns a dict.""" number = compact(number) from stdnum import numdb @@ -76,20 +76,18 @@ def info(number): return info -def get_label(number): +def get_label(number: str) -> str: """Lookup the category label for the number.""" return info(number)['label'] -def label(number): # pragma: no cover (deprecated function) +@t.deprecated('label() has been renamed to get_label()') +def label(number: str) -> str: # pragma: no cover (deprecated function) """DEPRECATED: use `get_label()` instead.""" # noqa: D40 - warnings.warn( - 'label() has been to get_label()', - DeprecationWarning, stacklevel=2) return get_label(number) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid NACE. This checks the format and searches the registry to see if it exists.""" number = compact(number) @@ -105,7 +103,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid NACE. This checks the format and searches the registry to see if it exists.""" try: @@ -114,6 +112,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return '.'.join((number[:2], number[2:])).strip('.') diff --git a/stdnum/eu/oss.py b/stdnum/eu/oss.py index e9d03492..e8ddbe71 100644 --- a/stdnum/eu/oss.py +++ b/stdnum/eu/oss.py @@ -49,7 +49,7 @@ >>> validate('EU 372022452') 'EU372022452' """ - +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -88,12 +88,12 @@ """The collection of member state codes (for MSI) that may make up a VAT number.""" -def compact(number): +def compact(number: str) -> str: """Compact European VAT Number""" return clean(number, ' -').upper().strip() -def validate(number): +def validate(number: str) -> str: """Validate European VAT Number""" number = compact(number) if number.startswith('EU'): @@ -111,7 +111,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number. This performs the country-specific check for the number.""" try: diff --git a/stdnum/eu/vat.py b/stdnum/eu/vat.py index cb5d417d..b95ff922 100644 --- a/stdnum/eu/vat.py +++ b/stdnum/eu/vat.py @@ -38,7 +38,9 @@ >>> guess_country('00449544B01') ['nl'] """ +from __future__ import annotations +from stdnum import _typing as t from stdnum.eu import oss from stdnum.exceptions import * from stdnum.util import clean, get_cc_module, get_soap_client @@ -59,7 +61,7 @@ """The WSDL URL of the VAT Information Exchange System (VIES).""" -def _get_cc_module(cc): +def _get_cc_module(cc: str) -> t.NumberValidationModule | None: """Get the VAT number module based on the country code.""" # Greece uses a "wrong" country code cc = cc.lower() @@ -68,7 +70,7 @@ def _get_cc_module(cc): if cc == 'el': cc = 'gr' if cc not in MEMBER_STATES: - return + return None if cc == 'xi': cc = 'gb' if cc not in _country_modules: @@ -76,7 +78,7 @@ def _get_cc_module(cc): return _country_modules[cc] -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, '').upper().strip() @@ -90,7 +92,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This performs the country-specific check for the number.""" number = clean(number, '').upper().strip() @@ -104,7 +106,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number. This performs the country-specific check for the number.""" try: @@ -113,17 +115,17 @@ def is_valid(number): return False -def guess_country(number): +def guess_country(number: str) -> list[str]: """Guess the country code based on the number. This checks the number against each of the validation routines and returns the list of countries for which it is valid. This returns lower case codes and returns gr (not el) for Greece.""" return [cc for cc in MEMBER_STATES - if _get_cc_module(cc).is_valid(number)] + if _get_cc_module(cc).is_valid(number)] # type: ignore[union-attr] -def check_vies(number, timeout=30, verify=True): # pragma: no cover (not part of normal test suite) +def check_vies(number, timeout=30, verify=True): # type: ignore # pragma: no cover (not part of normal test suite) """Use the EU VIES service to validate the provided number. Query the online European Commission VAT Information Exchange System @@ -145,7 +147,7 @@ def check_vies(number, timeout=30, verify=True): # pragma: no cover (not part o return client.checkVat(number[:2], number[2:]) -def check_vies_approx(number, requester, timeout=30, verify=True): # pragma: no cover +def check_vies_approx(number, requester, timeout=30, verify=True): # type: ignore # pragma: no cover """Use the EU VIES service to validate the provided number. Query the online European Commission VAT Information Exchange System diff --git a/stdnum/exceptions.py b/stdnum/exceptions.py index 7a8aacb6..725689ad 100644 --- a/stdnum/exceptions.py +++ b/stdnum/exceptions.py @@ -23,6 +23,7 @@ The validation functions of stdnum should raise one of the below exceptions when validation of the number fails. """ +from __future__ import annotations __all__ = ['ValidationError', 'InvalidFormat', 'InvalidChecksum', @@ -35,7 +36,7 @@ class ValidationError(ValueError): This exception should normally not be raised, only subclasses of this exception.""" - def __str__(self): + def __str__(self) -> str: """Return the exception message.""" return ''.join(self.args[:1]) or getattr(self, 'message', '') diff --git a/stdnum/fi/__init__.py b/stdnum/fi/__init__.py index 80129fc8..c3fc7b58 100644 --- a/stdnum/fi/__init__.py +++ b/stdnum/fi/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Finnish numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.fi import alv as vat # noqa: F401 diff --git a/stdnum/fi/alv.py b/stdnum/fi/alv.py index ca90bad2..6594bec6 100644 --- a/stdnum/fi/alv.py +++ b/stdnum/fi/alv.py @@ -29,12 +29,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -43,13 +44,13 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (7, 9, 10, 5, 8, 4, 2, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -62,7 +63,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/fi/associationid.py b/stdnum/fi/associationid.py index 4374671b..88c97327 100644 --- a/stdnum/fi/associationid.py +++ b/stdnum/fi/associationid.py @@ -41,6 +41,7 @@ >>> format('1234') '1.234' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -53,13 +54,13 @@ 83, 84, 85, 89, 92)) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -._+').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Finnish association register number. This checks the length and format.""" number = compact(number) @@ -72,7 +73,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid association register number.""" try: return bool(validate(number)) @@ -80,7 +81,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) if len(number) <= 3: diff --git a/stdnum/fi/hetu.py b/stdnum/fi/hetu.py index b2b866fc..62f75a3d 100644 --- a/stdnum/fi/hetu.py +++ b/stdnum/fi/hetu.py @@ -40,6 +40,7 @@ >>> compact('131052a308t') '131052A308T' """ +from __future__ import annotations import datetime import re @@ -62,17 +63,17 @@ r'(?P[0-9ABCDEFHJKLMNPRSTUVWXY])$') -def compact(number): +def compact(number: str) -> str: """Convert the HETU to the minimal representation. This strips surrounding whitespace and converts it to upper case.""" return clean(number, '').upper().strip() -def _calc_checksum(number): +def _calc_checksum(number: str) -> str: return '0123456789ABCDEFHJKLMNPRSTUVWXY'[int(number) % 31] -def validate(number, allow_temporary=False): +def validate(number: str, allow_temporary: bool = False) -> str: """Check if the number is a valid HETU. It checks the format, whether a valid date is given and whether the check digit is correct. Allows temporary identifier range for individuals (900-999) if allow_temporary @@ -104,7 +105,7 @@ def validate(number, allow_temporary=False): return number -def is_valid(number, allow_temporary=False): +def is_valid(number: str, allow_temporary: bool = False) -> bool: """Check if the number is a valid HETU.""" try: return bool(validate(number, allow_temporary)) diff --git a/stdnum/fi/veronumero.py b/stdnum/fi/veronumero.py index 4513df9b..31236526 100644 --- a/stdnum/fi/veronumero.py +++ b/stdnum/fi/veronumero.py @@ -42,18 +42,19 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the Veronumero to the minimal representation. This strips surrounding whitespace and removes separators.""" return clean(number, ' ').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid tax number. This checks the length and formatting.""" number = compact(number) @@ -65,7 +66,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid tax number.""" try: return bool(validate(number)) diff --git a/stdnum/fi/ytunnus.py b/stdnum/fi/ytunnus.py index 2f38241d..bd514fa9 100644 --- a/stdnum/fi/ytunnus.py +++ b/stdnum/fi/ytunnus.py @@ -32,24 +32,25 @@ >>> format('2077474-0') '2077474-0' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.fi import alv -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return alv.compact(number) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid business identifier. This checks the length, formatting and check digit.""" return alv.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid business identifier.""" try: return bool(validate(number)) @@ -57,7 +58,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:7] + '-' + number[7:] diff --git a/stdnum/figi.py b/stdnum/figi.py index de5d6482..3554cd85 100644 --- a/stdnum/figi.py +++ b/stdnum/figi.py @@ -36,18 +36,19 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip().upper() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digits for the number.""" # we use the full alphabet for the check digit calculation alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -58,7 +59,7 @@ def calc_check_digit(number): return str((10 - sum(int(n) for n in number)) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid FIGI.""" number = compact(number) if not all(x in '0123456789BCDFGHJKLMNPQRSTVWXYZ' for x in number): @@ -76,7 +77,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid FIGI.""" try: return bool(validate(number)) diff --git a/stdnum/fo/__init__.py b/stdnum/fo/__init__.py index 3c9b274d..5db26381 100644 --- a/stdnum/fo/__init__.py +++ b/stdnum/fo/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Faroe Islands numbers.""" +from __future__ import annotations # provide aliases from stdnum.fo import vn as vat # noqa: F401 diff --git a/stdnum/fo/vn.py b/stdnum/fo/vn.py index a634571d..c0a18f62 100644 --- a/stdnum/fo/vn.py +++ b/stdnum/fo/vn.py @@ -41,12 +41,13 @@ >>> format('602 590') '602590' """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -58,7 +59,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Faroe Islands V-number number. This checks the length and formatting. @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Faroe Islands V-number number.""" try: return bool(validate(number)) @@ -79,6 +80,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/fr/__init__.py b/stdnum/fr/__init__.py index 14abaa51..fbc5edb3 100644 --- a/stdnum/fr/__init__.py +++ b/stdnum/fr/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of French numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.fr import tva as vat # noqa: F401 diff --git a/stdnum/fr/nif.py b/stdnum/fr/nif.py index d0436744..55d6d624 100644 --- a/stdnum/fr/nif.py +++ b/stdnum/fr/nif.py @@ -47,23 +47,24 @@ >>> format('3023217600053') '30 23 217 600 053' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the number.""" return '%03d' % (int(number[:10]) % 511) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid NIF.""" number = compact(number) if not isdigits(number): @@ -77,7 +78,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid NIF.""" try: return bool(validate(number)) @@ -85,7 +86,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:2], number[2:4], number[4:7], diff --git a/stdnum/fr/nir.py b/stdnum/fr/nir.py index 5f8bf44f..1508dde3 100644 --- a/stdnum/fr/nir.py +++ b/stdnum/fr/nir.py @@ -60,18 +60,19 @@ >>> format('295109912611193') '2 95 10 99 126 111 93' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' .').strip().upper() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the number.""" department = number[5:7] if department == '2A': @@ -81,7 +82,7 @@ def calc_check_digits(number): return '%02d' % (97 - (int(number[:13]) % 97)) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and check digits.""" number = compact(number) @@ -96,7 +97,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid.""" try: return bool(validate(number)) @@ -104,7 +105,7 @@ def is_valid(number): return False -def format(number, separator=' '): +def format(number: str, separator: str = ' ') -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return separator.join(( diff --git a/stdnum/fr/siren.py b/stdnum/fr/siren.py index afd83682..bbb3555f 100644 --- a/stdnum/fr/siren.py +++ b/stdnum/fr/siren.py @@ -35,6 +35,7 @@ >>> to_tva('443 121 975') '46 443 121 975' """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * @@ -47,13 +48,13 @@ # https://avis-situation-sirene.insee.fr/ -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' .').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid SIREN. This checks the length, formatting and check digit.""" number = compact(number) @@ -65,7 +66,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid SIREN.""" try: return bool(validate(number)) @@ -73,7 +74,7 @@ def is_valid(number): return False -def to_tva(number): +def to_tva(number: str) -> str: """Return a TVA that prepends the two extra check digits to the SIREN.""" # note that this always returns numeric check digits # it is unclean when the alphabetic ones are used diff --git a/stdnum/fr/siret.py b/stdnum/fr/siret.py index 7e728f74..2cc187da 100644 --- a/stdnum/fr/siret.py +++ b/stdnum/fr/siret.py @@ -46,6 +46,7 @@ >>> format('73282932000074') '732 829 320 00074' """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * @@ -53,13 +54,13 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' .').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid SIRET. This checks the length, formatting and check digit.""" number = compact(number) @@ -78,7 +79,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid SIRET.""" try: return bool(validate(number)) @@ -86,7 +87,7 @@ def is_valid(number): return False -def to_siren(number): +def to_siren(number: str) -> str: """Convert the SIRET number to a SIREN number. The SIREN number is the 9 first digits of the SIRET number. @@ -101,7 +102,7 @@ def to_siren(number): return ''.join(_siren) -def to_tva(number): +def to_tva(number: str) -> str: """Convert the SIRET number to a TVA number. The TVA number is built from the SIREN number, prepended by two extra @@ -110,7 +111,7 @@ def to_tva(number): return siren.to_tva(to_siren(number)) -def format(number, separator=' '): +def format(number: str, separator: str = ' ') -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return separator.join((number[0:3], number[3:6], number[6:9], number[9:])) diff --git a/stdnum/fr/tva.py b/stdnum/fr/tva.py index b224aae2..1073ad01 100644 --- a/stdnum/fr/tva.py +++ b/stdnum/fr/tva.py @@ -42,6 +42,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.fr import siren @@ -52,7 +53,7 @@ _alphabet = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').upper().strip() @@ -61,7 +62,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -93,7 +94,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/gb/nhs.py b/stdnum/gb/nhs.py index ac6382d6..1364188d 100644 --- a/stdnum/gb/nhs.py +++ b/stdnum/gb/nhs.py @@ -40,24 +40,25 @@ >>> format('9434765870') '943 476 5870' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum. The checksum is only used for the 9 digits of the number and the result can either be 0 or 42.""" return sum(i * int(n) for i, n in enumerate(reversed(number), 1)) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number is valid. This checks the length and check digit.""" number = compact(number) @@ -70,7 +71,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is valid.""" try: return bool(validate(number)) @@ -78,7 +79,7 @@ def is_valid(number): return False -def format(number, separator=' '): +def format(number: str, separator: str = ' ') -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return separator.join((number[0:3], number[3:6], number[6:])) diff --git a/stdnum/gb/sedol.py b/stdnum/gb/sedol.py index a9c27272..2f1f1ee1 100644 --- a/stdnum/gb/sedol.py +++ b/stdnum/gb/sedol.py @@ -32,6 +32,7 @@ >>> to_isin('B15KXQ8') 'GB00B15KXQ89' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -41,20 +42,20 @@ _alphabet = '0123456789 BCD FGH JKLMN PQRST VWXYZ' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip().upper() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digits for the number.""" weights = (1, 3, 1, 7, 3, 9) s = sum(w * _alphabet.index(n) for w, n in zip(weights, number)) return str((10 - s) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is valid. This checks the length and check digit.""" number = compact(number) @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is valid.""" try: return bool(validate(number)) @@ -79,7 +80,7 @@ def is_valid(number): return False -def to_isin(number): +def to_isin(number: str) -> str: """Convert the number to an ISIN.""" from stdnum import isin return isin.from_natid('GB', number) diff --git a/stdnum/gb/upn.py b/stdnum/gb/upn.py index c0684bee..27166b0c 100644 --- a/stdnum/gb/upn.py +++ b/stdnum/gb/upn.py @@ -47,6 +47,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -72,20 +73,20 @@ 919, 921, 925, 926, 928, 929, 931, 933, 935, 936, 937, 938)) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number.""" check = sum(i * _alphabet.index(n) for i, n in enumerate(number[-12:], 2)) % 23 return _alphabet[check] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid UPN. This checks length, formatting and check digits.""" number = compact(number) @@ -100,7 +101,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid UPN.""" try: return bool(validate(number)) diff --git a/stdnum/gb/utr.py b/stdnum/gb/utr.py index 1c4ee70e..1032086f 100644 --- a/stdnum/gb/utr.py +++ b/stdnum/gb/utr.py @@ -34,25 +34,26 @@ ... InvalidChecksum: .. """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').upper().strip().lstrip('K') -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number. The passed number should not have the check digit (the first one) included.""" weights = (6, 7, 8, 9, 10, 5, 4, 3, 2) return '21987654321'[sum(int(n) * w for n, w in zip(number, weights)) % 11] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid UTR.""" number = compact(number) if not isdigits(number): @@ -64,7 +65,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid UTR.""" try: return bool(validate(number)) diff --git a/stdnum/gb/vat.py b/stdnum/gb/vat.py index c8f70866..f3abb6d7 100644 --- a/stdnum/gb/vat.py +++ b/stdnum/gb/vat.py @@ -34,12 +34,13 @@ >>> format('980780684') '980 7806 84' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').upper().strip() @@ -48,14 +49,14 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum. The checksum is only used for the 9 digits of the number and the result can either be 0 or 42.""" weights = (8, 7, 6, 5, 4, 3, 2, 10, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 97 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -100,7 +101,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) @@ -108,7 +109,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) if len(number) == 5: diff --git a/stdnum/gh/__init__.py b/stdnum/gh/__init__.py index 41922b43..d19f1eeb 100644 --- a/stdnum/gh/__init__.py +++ b/stdnum/gh/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Ghana numbers.""" +from __future__ import annotations # provide aliases from stdnum.gh import tin as vat # noqa: F401 diff --git a/stdnum/gh/tin.py b/stdnum/gh/tin.py index 2b0130c0..6aff7370 100644 --- a/stdnum/gh/tin.py +++ b/stdnum/gh/tin.py @@ -46,6 +46,7 @@ ... InvalidChecksum: ... """ # noqa: E501 +from __future__ import annotations import re @@ -56,7 +57,7 @@ _gh_tin_re = re.compile(r'^[PCGQV]{1}00[A-Z0-9]{8}$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -65,13 +66,13 @@ def compact(number): return clean(number, ' ').upper() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the TIN.""" check = sum((i + 1) * int(n) for i, n in enumerate(number[1:10])) % 11 return 'X' if check == 10 else str(check) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Ghana TIN.""" number = compact(number) if len(number) != 11: @@ -83,7 +84,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Ghana TIN.""" try: return bool(validate(number)) diff --git a/stdnum/gn/__init__.py b/stdnum/gn/__init__.py index 263e886a..eb139e1e 100644 --- a/stdnum/gn/__init__.py +++ b/stdnum/gn/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Guinea numbers.""" +from __future__ import annotations # provide aliases from stdnum.gn import nifp as vat # noqa: F401 diff --git a/stdnum/gn/nifp.py b/stdnum/gn/nifp.py index f5ad688c..5f199c73 100644 --- a/stdnum/gn/nifp.py +++ b/stdnum/gn/nifp.py @@ -43,13 +43,14 @@ >>> format('693770885') '693-770-885' """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -58,7 +59,7 @@ def compact(number): return clean(number, ' -').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Guinea NIFp number. This checks the length, formatting and check digit. @@ -72,7 +73,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Guinea NIFp number.""" try: return bool(validate(number)) @@ -80,7 +81,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:3], number[3:-3], number[-3:]]) diff --git a/stdnum/gr/amka.py b/stdnum/gr/amka.py index e04efeb4..feb9d7ad 100644 --- a/stdnum/gr/amka.py +++ b/stdnum/gr/amka.py @@ -40,21 +40,23 @@ >>> get_gender('01013099997') 'M' """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the date of birth. Since only two digits are used for the year, the century may be incorrect.""" @@ -71,7 +73,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the gender (M/F) from the person's AMKA.""" number = compact(number) if int(number[9]) % 2: @@ -80,7 +82,7 @@ def get_gender(number): return 'F' -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid AMKA. This checks the length, formatting and check digit.""" number = compact(number) @@ -93,7 +95,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid AMKA.""" try: return bool(validate(number)) diff --git a/stdnum/gr/vat.py b/stdnum/gr/vat.py index dc13c0c4..1d459474 100644 --- a/stdnum/gr/vat.py +++ b/stdnum/gr/vat.py @@ -31,12 +31,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -./:').upper().strip() @@ -47,7 +48,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" checksum = 0 @@ -56,7 +57,7 @@ def calc_check_digit(number): return str(checksum * 2 % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -69,7 +70,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/grid.py b/stdnum/grid.py index cb948796..2c251f6d 100644 --- a/stdnum/grid.py +++ b/stdnum/grid.py @@ -36,12 +36,13 @@ >>> format('A12425GABC1234002M') 'A1-2425G-ABC1234002-M' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the GRid to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').strip().upper() @@ -50,7 +51,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid GRid.""" from stdnum.iso7064 import mod_37_36 number = compact(number) @@ -59,7 +60,7 @@ def validate(number): return mod_37_36.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid GRid.""" try: return bool(validate(number)) @@ -67,8 +68,8 @@ def is_valid(number): return False -def format(number, separator='-'): +def format(number: str, separator: str = '-') -> str: """Reformat the number to the standard presentation format.""" number = compact(number) - number = (number[0:2], number[2:7], number[7:17], number[17:]) - return separator.join(x for x in number if x) + parts = (number[0:2], number[2:7], number[7:17], number[17:]) + return separator.join(x for x in parts if x) diff --git a/stdnum/gs1_128.py b/stdnum/gs1_128.py index 473a68cb..77fff45a 100644 --- a/stdnum/gs1_128.py +++ b/stdnum/gs1_128.py @@ -46,11 +46,13 @@ >>> validate('(17)181119(01)38425876095074(37)1') '013842587609507417181119371' """ +from __future__ import annotations import datetime import decimal import re +from stdnum import _typing as t from stdnum import numdb from stdnum.exceptions import * from stdnum.util import clean @@ -68,7 +70,7 @@ } -def compact(number): +def compact(number: str) -> str: """Convert the GS1-128 to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -78,11 +80,12 @@ def compact(number): return clean(number, '()').strip() -def _encode_value(fmt, _type, value): +def _encode_value(fmt: str, _type: str, value: object) -> str: """Encode the specified value given the format and type.""" if _type == 'decimal': if isinstance(value, (list, tuple)) and fmt.startswith('N3+'): number = _encode_value(fmt[3:], _type, value[1]) + assert isinstance(value[0], str) return number[0] + value[0].rjust(3, '0') + number[1:] value = str(value) if fmt.startswith('N..'): @@ -126,22 +129,25 @@ def _encode_value(fmt, _type, value): return str(value) -def _max_length(fmt, _type): +def _max_length(fmt: str, _type: str) -> int: """Determine the maximum length based on the format ad type.""" - length = sum(int(re.match(r'^[NXY][0-9]*?[.]*([0-9]+)[\[\]]?$', x).group(1)) for x in fmt.split('+')) + length = sum( + int(re.match(r'^[NXY][0-9]*?[.]*([0-9]+)[\[\]]?$', x).group(1)) # type: ignore[misc, union-attr] + for x in fmt.split('+') + ) if _type == 'decimal': length += 1 return length -def _pad_value(fmt, _type, value): +def _pad_value(fmt: str, _type: str, value: str) -> str: """Pad the value to the maximum length for the format.""" if _type in ('decimal', 'int'): return value.rjust(_max_length(fmt, _type), '0') return value.ljust(_max_length(fmt, _type)) -def _decode_value(fmt, _type, value): +def _decode_value(fmt: str, _type: str, value: str) -> t.Any: """Decode the specified value given the fmt and type.""" if _type == 'decimal': if fmt.startswith('N3+'): @@ -173,7 +179,7 @@ def _decode_value(fmt, _type, value): return value.strip() -def info(number, separator=''): +def info(number: str, separator: str = '') -> dict[str, t.Any]: """Return a dictionary containing the information from the GS1-128 code. The returned dictionary maps application identifiers to values with the @@ -197,7 +203,7 @@ def info(number, separator=''): number = number[len(ai):] # figure out the value part value = number[:_max_length(info['format'], info['type'])] - if separator and info.get('fnc1', False): + if separator and info.get('fnc1'): idx = number.find(separator) if idx > 0: value = number[:idx] @@ -214,7 +220,7 @@ def info(number, separator=''): return data -def encode(data, separator='', parentheses=False): +def encode(data: t.Mapping[str, object], separator: str = '', parentheses: bool = False) -> str: """Generate a GS1-128 for the application identifiers supplied. The provided dictionary is expected to map application identifiers to @@ -259,7 +265,7 @@ def encode(data, separator='', parentheses=False): ]) -def validate(number, separator=''): +def validate(number: str, separator: str = '') -> str: """Check if the number provided is a valid GS1-128. This checks formatting of the number and values and returns a stable @@ -280,7 +286,7 @@ def validate(number, separator=''): raise InvalidFormat() -def is_valid(number, separator=''): +def is_valid(number: str, separator: str = '') -> bool: """Check if the number provided is a valid GS1-128.""" try: return bool(validate(number)) diff --git a/stdnum/gt/__init__.py b/stdnum/gt/__init__.py index c1f38d93..8175dbc2 100644 --- a/stdnum/gt/__init__.py +++ b/stdnum/gt/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Guatemalan numbers.""" +from __future__ import annotations # provide aliases from stdnum.gt import nit as vat # noqa: F401 diff --git a/stdnum/gt/nit.py b/stdnum/gt/nit.py index efbe1b05..1d45baee 100644 --- a/stdnum/gt/nit.py +++ b/stdnum/gt/nit.py @@ -47,25 +47,26 @@ >>> format('39525503') '3952550-3' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip().lstrip('0') -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" c = -sum(i * int(n) for i, n in enumerate(reversed(number), 2)) % 11 return 'K' if c == 10 else str(c) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Guatemala NIT number. This checks the length, formatting and check digit. @@ -82,7 +83,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Guatemala NIT number.""" try: return bool(validate(number)) @@ -90,7 +91,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:-1], number[-1]]) diff --git a/stdnum/hr/__init__.py b/stdnum/hr/__init__.py index 54a21c2f..27723232 100644 --- a/stdnum/hr/__init__.py +++ b/stdnum/hr/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Croatian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.hr import oib as vat # noqa: F401 diff --git a/stdnum/hr/oib.py b/stdnum/hr/oib.py index 72801a4f..4175ddaf 100644 --- a/stdnum/hr/oib.py +++ b/stdnum/hr/oib.py @@ -31,13 +31,14 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_11_10 from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -46,7 +47,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid OIB number. This checks the length, formatting and check digit.""" number = compact(number) @@ -58,7 +59,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid OIB number.""" try: return bool(validate(number)) diff --git a/stdnum/hu/__init__.py b/stdnum/hu/__init__.py index 58735a03..f0f2371c 100644 --- a/stdnum/hu/__init__.py +++ b/stdnum/hu/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Hungarian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.hu import anum as vat # noqa: F401 diff --git a/stdnum/hu/anum.py b/stdnum/hu/anum.py index bb9b863c..06f18d52 100644 --- a/stdnum/hu/anum.py +++ b/stdnum/hu/anum.py @@ -30,12 +30,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -44,13 +45,13 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum. Valid numbers should have a checksum of 0.""" weights = (9, 7, 3, 1, 9, 7, 3, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 10 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -63,7 +64,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/iban.py b/stdnum/iban.py index ccc5626f..0a2a2bc0 100644 --- a/stdnum/iban.py +++ b/stdnum/iban.py @@ -43,9 +43,11 @@ >>> calc_check_digits('BExx435411161155') '31' """ +from __future__ import annotations import re +from stdnum import _typing as t from stdnum import numdb from stdnum.exceptions import * from stdnum.iso7064 import mod_97_10 @@ -62,23 +64,23 @@ _country_modules = {} -def compact(number): +def compact(number: str) -> str: """Convert the iban number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').strip().upper() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits that should be put in the number to make it valid. Check digits in the supplied number are ignored.""" number = compact(number) return mod_97_10.calc_check_digits(number[4:] + number[:2]) -def _struct_to_re(structure): +def _struct_to_re(structure: str) -> re.Pattern[str]: """Convert an IBAN structure to a regular expression that can be used to validate the number.""" - def conv(match): + def conv(match: re.Match[str]) -> str: chars = { 'n': '[0-9]', 'a': '[A-Z]', @@ -88,7 +90,7 @@ def conv(match): return re.compile('^%s$' % _struct_re.sub(conv, structure)) -def _get_cc_module(cc): +def _get_cc_module(cc: str) -> t.NumberValidationModule | None: """Get the IBAN module based on the country code.""" cc = cc.lower() if cc not in _country_modules: @@ -96,7 +98,7 @@ def _get_cc_module(cc): return _country_modules[cc] -def validate(number, check_country=True): +def validate(number: str, check_country: bool = True) -> str: """Check if the number provided is a valid IBAN. The country-specific check can be disabled with the check_country argument.""" number = compact(number) @@ -119,7 +121,7 @@ def validate(number, check_country=True): return number -def is_valid(number, check_country=True): +def is_valid(number: str, check_country: bool = True) -> bool: """Check if the number provided is a valid IBAN.""" try: return bool(validate(number, check_country=check_country)) @@ -127,7 +129,7 @@ def is_valid(number, check_country=True): return False -def format(number, separator=' '): +def format(number: str, separator: str = ' ') -> str: """Reformat the passed number to the space-separated format.""" number = compact(number) return separator.join(number[i:i + 4] for i in range(0, len(number), 4)) diff --git a/stdnum/id/__init__.py b/stdnum/id/__init__.py index a574bd54..6c12c473 100644 --- a/stdnum/id/__init__.py +++ b/stdnum/id/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Indonesian numbers.""" +from __future__ import annotations # provide aliases from stdnum.id import npwp as vat # noqa: F401 diff --git a/stdnum/id/nik.py b/stdnum/id/nik.py index 18cbb0d8..e702e068 100644 --- a/stdnum/id/nik.py +++ b/stdnum/id/nik.py @@ -54,6 +54,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations import datetime @@ -61,7 +62,7 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes @@ -70,7 +71,7 @@ def compact(number): return clean(number, ' -.').strip() -def get_birth_date(number, minyear=1920): +def get_birth_date(number: str, minyear: int = 1920) -> datetime.date: """Get the birth date from the person's NIK. Note that the number only encodes the last two digits of the year so @@ -90,7 +91,7 @@ def get_birth_date(number, minyear=1920): raise InvalidComponent() -def _check_registration_place(number): +def _check_registration_place(number: str) -> dict[str, str]: """Use the number to look up the place of registration of the person.""" from stdnum import numdb results = numdb.get('id/loc').info(number[:4])[0][1] @@ -99,7 +100,7 @@ def _check_registration_place(number): return results -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Indonesian NIK.""" number = compact(number) if not isdigits(number): @@ -111,7 +112,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Indonesian NIK.""" try: return bool(validate(number)) diff --git a/stdnum/id/npwp.py b/stdnum/id/npwp.py index effa8ecf..a5bc0fe4 100644 --- a/stdnum/id/npwp.py +++ b/stdnum/id/npwp.py @@ -51,6 +51,7 @@ >>> format('013000666091000') '01.300.066.6-091.000' """ # noqa: E501 +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * @@ -58,7 +59,7 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes @@ -67,7 +68,7 @@ def compact(number): return clean(number, ' -.').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Indonesia NPWP number.""" number = compact(number) if not isdigits(number): @@ -86,7 +87,7 @@ def validate(number): raise InvalidLength() -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Indonesia NPWP number.""" try: return bool(validate(number)) @@ -94,7 +95,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '%s.%s.%s.%s-%s.%s' % ( diff --git a/stdnum/ie/pps.py b/stdnum/ie/pps.py index a52d8f30..34d5f5b2 100644 --- a/stdnum/ie/pps.py +++ b/stdnum/ie/pps.py @@ -50,6 +50,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations import re @@ -62,13 +63,13 @@ """Regular expression used to check syntax of PPS numbers.""" -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid PPS number. This checks the length, formatting and check digit.""" number = compact(number) @@ -85,7 +86,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid PPS number. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/ie/vat.py b/stdnum/ie/vat.py index 8db582c1..6faf864d 100644 --- a/stdnum/ie/vat.py +++ b/stdnum/ie/vat.py @@ -40,12 +40,13 @@ >>> convert('1F23456T') '0234561T' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -57,7 +58,7 @@ def compact(number): _alphabet = 'WABCDEFGHIJKLMNOPQRSTUV' -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" number = compact(number).zfill(7) @@ -66,7 +67,7 @@ def calc_check_digit(number): 9 * _alphabet.index(number[7:])) % 23] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -89,7 +90,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VAT number. This checks the length, formatting and check digit.""" try: @@ -98,7 +99,7 @@ def is_valid(number): return False -def convert(number): +def convert(number: str) -> str: """Convert an "old" style 8-digit VAT number where the second character is a letter to the new 8-digit format where only the last digit is a character.""" diff --git a/stdnum/il/__init__.py b/stdnum/il/__init__.py index b002751c..b4bed5b5 100644 --- a/stdnum/il/__init__.py +++ b/stdnum/il/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Israeli numbers.""" +from __future__ import annotations # provide aliases from stdnum.il import hp as vat # noqa: F401 diff --git a/stdnum/il/hp.py b/stdnum/il/hp.py index a396c15a..8da7c2c2 100644 --- a/stdnum/il/hp.py +++ b/stdnum/il/hp.py @@ -47,19 +47,20 @@ ... InvalidComponent: ... """ # noqa: E501 +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid ID. This checks the length, formatting and check digit.""" number = compact(number) @@ -73,7 +74,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ID. This checks the length, formatting and check digit.""" try: @@ -82,6 +83,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/il/idnr.py b/stdnum/il/idnr.py index 545f5fc6..e5f18022 100644 --- a/stdnum/il/idnr.py +++ b/stdnum/il/idnr.py @@ -42,19 +42,20 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().zfill(9) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid ID. This checks the length, formatting and check digit.""" number = compact(number) @@ -66,7 +67,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ID. This checks the length, formatting and check digit.""" try: @@ -75,7 +76,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:-1] + '-' + number[-1:] diff --git a/stdnum/imei.py b/stdnum/imei.py index 2ea1210d..3ba873c4 100644 --- a/stdnum/imei.py +++ b/stdnum/imei.py @@ -45,19 +45,20 @@ >>> split('35686800-004141') ('35686800', '004141', '') """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the IMEI number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid IMEI (or IMEISV) number.""" number = compact(number) if not isdigits(number): @@ -71,7 +72,7 @@ def validate(number): return number -def imei_type(number): +def imei_type(number: str) -> str | None: """Check the passed number and return 'IMEI', 'IMEISV' or None (for invalid) for checking the type of number passed.""" try: @@ -84,7 +85,7 @@ def imei_type(number): return 'IMEISV' -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid IMEI (or IMEISV) number.""" try: return bool(validate(number)) @@ -92,7 +93,7 @@ def is_valid(number): return False -def split(number): +def split(number: str) -> tuple[str, str, str]: """Split the number into a Type Allocation Code (TAC), serial number and either the checksum (for IMEI) or the software version number (for IMEISV).""" @@ -100,10 +101,10 @@ def split(number): return (number[:8], number[8:14], number[14:]) -def format(number, separator='-', add_check_digit=False): +def format(number: str, separator: str = '-', add_check_digit: bool = False) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) if len(number) == 14 and add_check_digit: number += luhn.calc_check_digit(number) - number = (number[:2], number[2:8], number[8:14], number[14:]) - return separator.join(x for x in number if x) + parts = (number[:2], number[2:8], number[8:14], number[14:]) + return separator.join(x for x in parts if x) diff --git a/stdnum/imo.py b/stdnum/imo.py index c4b380ac..14c75ac9 100644 --- a/stdnum/imo.py +++ b/stdnum/imo.py @@ -39,12 +39,13 @@ >>> format('8814275') 'IMO 8814275' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').upper().strip() @@ -53,12 +54,12 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digits for the number.""" return str(sum(int(n) * (7 - i) for i, n in enumerate(number[:6])) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and check digit.""" number = compact(number) @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length and check digit.""" try: @@ -80,6 +81,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return 'IMO ' + compact(number) diff --git a/stdnum/imsi.py b/stdnum/imsi.py index 952615bb..b1f367f6 100644 --- a/stdnum/imsi.py +++ b/stdnum/imsi.py @@ -38,18 +38,20 @@ >>> info('460001234567890')['country'] 'China' """ +from __future__ import annotations +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the IMSI number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def split(number): +def split(number: str) -> tuple[str, ...]: """Split the specified IMSI into a Mobile Country Code (MCC), a Mobile Network Code (MNC), a Mobile Station Identification Number (MSIN).""" # clean up number @@ -59,7 +61,7 @@ def split(number): return tuple(numdb.get('imsi').split(number)) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid IMSI.""" number = compact(number) if not isdigits(number): @@ -71,7 +73,7 @@ def validate(number): return number -def info(number): +def info(number: str) -> t.IMSIInfo: """Return a dictionary of data about the supplied number.""" # clean up number number = compact(number) @@ -85,10 +87,10 @@ def info(number): info.update(mnc_info[1]) info['msin'] = msin_info[0] info.update(msin_info[1]) - return info + return t.cast('t.IMSIInfo', info) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid IMSI.""" try: return bool(validate(number)) diff --git a/stdnum/in_/__init__.py b/stdnum/in_/__init__.py index ec870c8d..5b4e9395 100644 --- a/stdnum/in_/__init__.py +++ b/stdnum/in_/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Indian numbers.""" +from __future__ import annotations # provide aliases from stdnum.in_ import gstin as vat # noqa: F401 diff --git a/stdnum/in_/aadhaar.py b/stdnum/in_/aadhaar.py index cf5d97f0..667df6b1 100644 --- a/stdnum/in_/aadhaar.py +++ b/stdnum/in_/aadhaar.py @@ -57,6 +57,7 @@ >>> mask('234123412346') 'XXXX XXXX 2346' """ +from __future__ import annotations import re @@ -69,13 +70,13 @@ """Regular expression used to check syntax of Aadhaar numbers.""" -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid Aadhaar number. This checks the length, formatting and check digit.""" number = compact(number) @@ -89,7 +90,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid Aadhaar number. This checks the length, formatting and check digit.""" try: @@ -98,13 +99,13 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:4], number[4:8], number[8:])) -def mask(number): +def mask(number: str) -> str: """Masks the first 8 digits as per Ministry of Electronics and Information Technology (MeitY) guidelines.""" number = compact(number) diff --git a/stdnum/in_/epic.py b/stdnum/in_/epic.py index 14ec89de..10464107 100644 --- a/stdnum/in_/epic.py +++ b/stdnum/in_/epic.py @@ -51,6 +51,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations import re @@ -62,13 +63,13 @@ _EPIC_RE = re.compile(r'^[A-Z]{3}[0-9]{7}$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid EPIC number. This checks the length, formatting and checksum.""" number = compact(number) @@ -80,7 +81,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid EPIC number. This checks the length, formatting and checksum.""" try: diff --git a/stdnum/in_/gstin.py b/stdnum/in_/gstin.py index 84d8c07a..0273afe7 100644 --- a/stdnum/in_/gstin.py +++ b/stdnum/in_/gstin.py @@ -58,9 +58,11 @@ >>> info('27AAPFU0939F1ZV')['state'] 'Maharashtra' """ +from __future__ import annotations import re +from stdnum import _typing as t from stdnum import luhn from stdnum.exceptions import * from stdnum.in_ import pan @@ -112,13 +114,13 @@ } -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid GSTIN. This checks the length, formatting and check digit.""" number = compact(number) @@ -133,7 +135,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid GSTIN. This checks the length, formatting and check digit.""" try: @@ -142,13 +144,13 @@ def is_valid(number): return False -def to_pan(number): +def to_pan(number: str) -> str: """Convert the number to a PAN.""" number = compact(number) return number[2:12] -def info(number): +def info(number: str) -> t.GSTINInfo: """Provide information that can be decoded locally from GSTIN (without API).""" number = validate(number) diff --git a/stdnum/in_/pan.py b/stdnum/in_/pan.py index f3aedbe6..83af8072 100644 --- a/stdnum/in_/pan.py +++ b/stdnum/in_/pan.py @@ -58,9 +58,11 @@ >>> info('AAPPV8261K')['holder_type'] 'Individual' """ +from __future__ import annotations import re +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean @@ -83,13 +85,13 @@ # Type 'K' may have been discontinued, not listed on Income Text Dept website. -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid PAN. This checks the length and formatting.""" number = compact(number) @@ -103,7 +105,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid PAN. This checks the length and formatting.""" try: @@ -112,20 +114,22 @@ def is_valid(number): return False -def info(number): +def info(number: str) -> t.PANInfo: """Provide information that can be decoded from the PAN.""" number = compact(number) holder_type = _pan_holder_types.get(number[3]) if not holder_type: raise InvalidComponent() - return { + # we exclude the backwards compatibility key from the typed dict + # so people that use type hints can migrate to the new key + return { # type: ignore[typeddict-unknown-key] 'holder_type': holder_type, 'card_holder_type': holder_type, # for backwards compatibility 'initial': number[4], } -def mask(number): +def mask(number: str) -> str: """Mask the PAN as per Central Board of Direct Taxes (CBDT) masking standard.""" number = compact(number) diff --git a/stdnum/in_/vid.py b/stdnum/in_/vid.py index 6142ed90..d63c7e07 100644 --- a/stdnum/in_/vid.py +++ b/stdnum/in_/vid.py @@ -55,6 +55,7 @@ >>> mask('2341234123412342') 'XXXX XXXX XXXX 2342' """ +from __future__ import annotations import re @@ -67,13 +68,13 @@ """Regular expression used to check syntax of VID numbers.""" -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid VID number. This checks the length, formatting and check digit.""" number = compact(number) @@ -87,7 +88,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VID number. This checks the length, formatting and check digit.""" try: @@ -96,13 +97,13 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:4], number[4:8], number[8:12], number[12:])) -def mask(number): +def mask(number: str) -> str: """Masks the first 8 digits as per Ministry of Electronics and Information Technology (MeitY) guidelines.""" number = compact(number) diff --git a/stdnum/is_/__init__.py b/stdnum/is_/__init__.py index ef061478..b8033029 100644 --- a/stdnum/is_/__init__.py +++ b/stdnum/is_/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Icelandic numbers.""" +from __future__ import annotations # provide aliases from stdnum.is_ import kennitala as personalid # noqa: F401 diff --git a/stdnum/is_/kennitala.py b/stdnum/is_/kennitala.py index e8109157..26bc485e 100644 --- a/stdnum/is_/kennitala.py +++ b/stdnum/is_/kennitala.py @@ -39,6 +39,7 @@ >>> format('1201743399') '120174-3399' """ +from __future__ import annotations import datetime import re @@ -58,20 +59,20 @@ r'(?P[09])$') -def compact(number): +def compact(number: str) -> str: """Convert the kennitala to the minimal representation. This strips surrounding whitespace and separation dash, and converts it to upper case.""" return clean(number, '-').upper().strip() -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (3, 2, 7, 6, 5, 4, 3, 2, 1, 0) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid kennitala. It checks the format, whether a valid date is given and whether the check digit is correct.""" @@ -100,7 +101,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid HETU. It checks the format, whether a valid date is given and whether the check digit is correct.""" try: @@ -109,7 +110,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:6] + '-' + number[6:] diff --git a/stdnum/is_/vsk.py b/stdnum/is_/vsk.py index 6fd6ddd8..5c36f66f 100644 --- a/stdnum/is_/vsk.py +++ b/stdnum/is_/vsk.py @@ -29,12 +29,13 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').upper().strip() @@ -43,7 +44,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid VAT number. This checks the length and formatting.""" number = compact(number) @@ -54,7 +55,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid VAT number. This checks the length and formatting.""" try: diff --git a/stdnum/isan.py b/stdnum/isan.py index 4f982182..ba696e28 100644 --- a/stdnum/isan.py +++ b/stdnum/isan.py @@ -46,13 +46,14 @@ >>> to_xml('1881-66C7-3420-6541-Y-9F3A-0245-O') '' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_37_36 from stdnum.util import clean -def split(number): +def split(number: str) -> tuple[str, str, str, str, str]: """Split the number into a root, an episode or part, a check digit a version and another check digit. If any of the parts are missing an empty string is returned.""" @@ -65,17 +66,17 @@ def split(number): return number[0:12], number[12:16], number[16:], '', '' -def compact(number, strip_check_digits=True): +def compact(number: str, strip_check_digits: bool = True) -> str: """Convert the ISAN to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace. The check digits are removed by default.""" - number = list(split(number)) + digits = list(split(number)) if strip_check_digits: - number[2] = number[4] = '' - return ''.join(number) + digits[2] = digits[4] = '' + return ''.join(digits) -def validate(number, strip_check_digits=False, add_check_digits=False): +def validate(number: str, strip_check_digits: bool = False, add_check_digits: bool = False) -> str: """Check if the number provided is a valid ISAN. If check digits are present in the number they are validated. If strip_check_digits is True any existing check digits will be removed (after checking). If @@ -106,7 +107,7 @@ def validate(number, strip_check_digits=False, add_check_digits=False): return root + episode + check1 + version + check2 -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISAN. If check digits are present in the number they are validated.""" try: @@ -115,7 +116,12 @@ def is_valid(number): return False -def format(number, separator='-', strip_check_digits=False, add_check_digits=True): +def format( + number: str, + separator: str = '-', + strip_check_digits: bool = False, + add_check_digits: bool = True, +) -> str: """Reformat the number to the standard presentation format. If add_check_digits is True the check digit will be added if they are not present yet. If both strip_check_digits and add_check_digits are True the @@ -127,30 +133,30 @@ def format(number, separator='-', strip_check_digits=False, add_check_digits=Tru check1 = mod_37_36.calc_check_digit(root + episode) if add_check_digits and not check2 and version: check2 = mod_37_36.calc_check_digit(root + episode + version) - number = [root[i:i + 4] for i in range(0, 12, 4)] + [episode] + parts = [root[i:i + 4] for i in range(0, 12, 4)] + [episode] if check1: - number.append(check1) + parts.append(check1) if version: - number.extend((version[0:4], version[4:])) + parts.extend((version[0:4], version[4:])) if check2: - number.append(check2) - return separator.join(number) + parts.append(check2) + return separator.join(parts) -def to_binary(number): +def to_binary(number: str) -> bytes: """Convert the number to its binary representation (without the check digits).""" from binascii import a2b_hex return a2b_hex(compact(number, strip_check_digits=True)) -def to_xml(number): +def to_xml(number: str) -> str: """Return the XML form of the ISAN as a string.""" number = format(number, strip_check_digits=True, add_check_digits=False) return '' % ( number[0:14], number[15:19], number[20:]) -def to_urn(number): +def to_urn(number: str) -> str: """Return the URN representation of the ISAN.""" return 'URN:ISAN:' + format(number, add_check_digits=True) diff --git a/stdnum/isbn.py b/stdnum/isbn.py index 0a366612..f15ea449 100644 --- a/stdnum/isbn.py +++ b/stdnum/isbn.py @@ -60,13 +60,14 @@ >>> to_isbn10('978-1-85798-218-3') '1-85798-218-5' """ +from __future__ import annotations from stdnum import ean from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number, convert=False): +def compact(number: str, convert: bool = False) -> str: """Convert the ISBN to the minimal representation. This strips the number of any valid ISBN separators and removes surrounding whitespace. If the convert parameter is True the number is also converted to ISBN-13 @@ -79,7 +80,7 @@ def compact(number, convert=False): return number -def _calc_isbn10_check_digit(number): +def _calc_isbn10_check_digit(number: str) -> str: """Calculate the ISBN check digit for 10-digit numbers. The number passed should not have the check digit included.""" check = sum((i + 1) * int(n) @@ -87,7 +88,7 @@ def _calc_isbn10_check_digit(number): return 'X' if check == 10 else str(check) -def validate(number, convert=False): +def validate(number: str, convert: bool = False) -> str: """Check if the number provided is a valid ISBN (either a legacy 10-digit one or a 13-digit one). This checks the length and the check digit but does not check if the group and publisher are valid (use split() for that).""" @@ -108,7 +109,7 @@ def validate(number, convert=False): return number -def isbn_type(number): +def isbn_type(number: str) -> str | None: """Check the passed number and return 'ISBN13', 'ISBN10' or None (for invalid) for checking the type of number passed.""" try: @@ -121,7 +122,7 @@ def isbn_type(number): return 'ISBN13' -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISBN (either a legacy 10-digit one or a 13-digit one). This checks the length and the check digit but does not check if the group and publisher are valid (use split() for that).""" @@ -131,7 +132,7 @@ def is_valid(number): return False -def to_isbn13(number): +def to_isbn13(number: str) -> str: """Convert the number to ISBN-13 format.""" number = number.strip() min_number = clean(number, ' -') @@ -150,7 +151,7 @@ def to_isbn13(number): return '978' + number -def to_isbn10(number): +def to_isbn10(number: str) -> str: """Convert the number to ISBN-10 format.""" number = number.strip() min_number = compact(number, convert=False) @@ -172,7 +173,7 @@ def to_isbn10(number): return number + digit -def split(number, convert=False): +def split(number: str, convert: bool = False) -> tuple[str, str, str, str, str]: """Split the specified ISBN into an EAN.UCC prefix, a group prefix, a registrant, an item number and a check digit. If the number is in ISBN-10 format the returned EAN.UCC prefix is '978'. If the convert parameter is @@ -195,7 +196,7 @@ def split(number, convert=False): return ('' if delprefix else prefix, group, publisher, itemnr, number[-1]) -def format(number, separator='-', convert=False): +def format(number: str, separator: str = '-', convert: bool = False) -> str: """Reformat the number to the standard presentation format with the EAN.UCC prefix (if any), the group prefix, the registrant, the item number and the check digit separated (if possible) by the specified diff --git a/stdnum/isil.py b/stdnum/isil.py index fb8ab8e9..9de855f1 100644 --- a/stdnum/isil.py +++ b/stdnum/isil.py @@ -54,6 +54,7 @@ >>> format('it-RM0267') 'IT-RM0267' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean @@ -64,13 +65,13 @@ '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-:/') -def compact(number): +def compact(number: str) -> str: """Convert the ISIL to the minimal representation. This strips surrounding whitespace.""" return clean(number, '').strip() -def _is_known_agency(agency): +def _is_known_agency(agency: str) -> bool: """Check whether the specified agency is valid.""" # look it up in the db from stdnum import numdb @@ -79,7 +80,7 @@ def _is_known_agency(agency): return len(results) == 1 and bool(results[0][1]) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid ISIL.""" number = compact(number) if not all(x in _alphabet for x in number): @@ -91,7 +92,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISIL.""" try: return bool(validate(number)) @@ -99,7 +100,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) parts = number.split('-') diff --git a/stdnum/isin.py b/stdnum/isin.py index 1d48f880..cc4f147e 100644 --- a/stdnum/isin.py +++ b/stdnum/isin.py @@ -40,6 +40,7 @@ >>> from_natid('gb', 'BYXJL75') 'GB00BYXJL758' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean @@ -88,13 +89,13 @@ _alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip().upper() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digits for the number.""" # convert to numeric first, then double some, then sum individual digits number = ''.join(str(_alphabet.index(n)) for n in number) @@ -103,7 +104,7 @@ def calc_check_digit(number): return str((10 - sum(int(n) for n in number)) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length and check digit.""" number = compact(number) @@ -118,7 +119,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length and check digit.""" try: @@ -127,7 +128,7 @@ def is_valid(number): return False -def from_natid(country_code, number): +def from_natid(country_code: str, number: str) -> str: """Generate an ISIN from a national security identifier.""" number = country_code.upper() + compact(number).zfill(9) return number + calc_check_digit(number) diff --git a/stdnum/ismn.py b/stdnum/ismn.py index bedc31df..d41bd0a5 100644 --- a/stdnum/ismn.py +++ b/stdnum/ismn.py @@ -41,19 +41,20 @@ >>> to_ismn13('M230671187') '9790230671187' """ +from __future__ import annotations from stdnum import ean from stdnum.exceptions import * from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the ISMN to the minimal representation. This strips the number of any valid ISMN separators and removes surrounding whitespace.""" return clean(number, ' -.').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid ISMN (either a legacy 10-digit one or a 13-digit one). This checks the length and the check bit but does not check if the publisher is known.""" @@ -71,7 +72,7 @@ def validate(number): return number -def ismn_type(number): +def ismn_type(number: str) -> str | None: """Check the type of ISMN number passed and return 'ISMN13', 'ISMN10' or None (for invalid).""" try: @@ -84,7 +85,7 @@ def ismn_type(number): return 'ISMN13' -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISMN (either a legacy 10-digit one or a 13-digit one). This checks the length and the check bit but does not check if the publisher is known.""" @@ -94,10 +95,9 @@ def is_valid(number): return False -def to_ismn13(number): +def to_ismn13(number: str) -> str: """Convert the number to ISMN13 (EAN) format.""" - number = number.strip() - min_number = compact(number) + min_number = validate(number) if len(min_number) == 13: return number # nothing to do, already 13 digit format # add prefix and strip the M @@ -115,7 +115,7 @@ def to_ismn13(number): (6, '700000', '899999'), (7, '9000000', '9999999')) -def split(number): +def split(number: str) -> tuple[str, str, str, str, str]: """Split the specified ISMN into a bookland prefix (979), an ISMN prefix (0), a publisher element (3 to 7 digits), an item element (2 to 6 digits) and a check digit.""" @@ -126,9 +126,10 @@ def split(number): if low <= number[4:4 + length] <= high: return (number[:3], number[3], number[4:4 + length], number[4 + length:-1], number[-1]) + raise AssertionError('unreachable') # pragma: no cover -def format(number, separator='-'): +def format(number: str, separator: str = '-') -> str: """Reformat the number to the standard presentation format with the prefixes, the publisher element, the item element and the check-digit separated by the specified separator. The number is converted to the diff --git a/stdnum/isni.py b/stdnum/isni.py index 5b07a013..c2684538 100644 --- a/stdnum/isni.py +++ b/stdnum/isni.py @@ -42,20 +42,20 @@ >>> format('000000012281955X') '0000 0001 2281 955X' """ - +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_11_2 from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the ISNI to the minimal representation. This strips the number of any valid ISNI separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ISNI. This checks the length and whether the check digit is correct.""" number = compact(number) @@ -67,7 +67,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISNI.""" try: return bool(validate(number)) @@ -75,7 +75,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:4], number[4:8], number[8:12], number[12:16])) diff --git a/stdnum/iso11649.py b/stdnum/iso11649.py index d0b784fe..40fe4a68 100644 --- a/stdnum/iso11649.py +++ b/stdnum/iso11649.py @@ -44,19 +44,20 @@ >>> format('RF18539007547034') 'RF18 5390 0754 7034' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_97_10 from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any invalid separators and removes surrounding whitespace.""" return clean(number, ' -.,/:').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid ISO 11649 structured creditor reference number.""" number = compact(number) @@ -68,7 +69,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISO 11649 structured creditor number. This checks the length, formatting and check digits.""" try: @@ -77,7 +78,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Format the number provided for output. Blocks of 4 characters, the last block can be less than 4 characters. See diff --git a/stdnum/iso6346.py b/stdnum/iso6346.py index 225b8a98..ae5a2510 100644 --- a/stdnum/iso6346.py +++ b/stdnum/iso6346.py @@ -41,6 +41,7 @@ >>> format('tasu117 000 0') 'TASU 117000 0' """ +from __future__ import annotations import re @@ -51,13 +52,13 @@ _iso6346_re = re.compile(r'^\w{3}(U|J|Z|R)\d{7}$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip().upper() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate check digit and return it for the 10 digit owner code and serial number.""" number = compact(number) @@ -67,7 +68,7 @@ def calc_check_digit(number): for i, n in enumerate(number)) % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Validate the given number (unicode) for conformity to ISO 6346.""" number = compact(number) if len(number) != 11: @@ -79,7 +80,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check whether the number conforms to the standard ISO6346. Unlike the validate function, this will not raise ValidationError(s).""" try: @@ -88,7 +89,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:4], number[4:-1], number[-1:])) diff --git a/stdnum/iso7064/mod_11_10.py b/stdnum/iso7064/mod_11_10.py index 32f52b16..b3cf4efd 100644 --- a/stdnum/iso7064/mod_11_10.py +++ b/stdnum/iso7064/mod_11_10.py @@ -34,11 +34,12 @@ >>> validate('002006673085') '002006673085' """ +from __future__ import annotations from stdnum.exceptions import * -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum. A valid number should have a checksum of 1.""" check = 5 for n in number: @@ -46,13 +47,13 @@ def checksum(number): return check -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the extra digit that should be appended to the number to make it a valid number.""" return str((1 - ((checksum(number) or 10) * 2) % 11) % 10) -def validate(number): +def validate(number: str) -> str: """Check whether the check digit is valid.""" try: valid = checksum(number) == 1 @@ -63,7 +64,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check whether the check digit is valid.""" try: return bool(validate(number)) diff --git a/stdnum/iso7064/mod_11_2.py b/stdnum/iso7064/mod_11_2.py index 166162de..541b5e14 100644 --- a/stdnum/iso7064/mod_11_2.py +++ b/stdnum/iso7064/mod_11_2.py @@ -36,11 +36,12 @@ >>> checksum('079X') 1 """ +from __future__ import annotations from stdnum.exceptions import * -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum. A valid number should have a checksum of 1.""" check = 0 for n in number: @@ -48,14 +49,14 @@ def checksum(number): return check -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the extra digit that should be appended to the number to make it a valid number.""" c = (1 - 2 * checksum(number)) % 11 return 'X' if c == 10 else str(c) -def validate(number): +def validate(number: str) -> str: """Check whether the check digit is valid.""" try: valid = checksum(number) == 1 @@ -66,7 +67,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check whether the check digit is valid.""" try: return bool(validate(number)) diff --git a/stdnum/iso7064/mod_37_2.py b/stdnum/iso7064/mod_37_2.py index df740ff4..c4de8fbe 100644 --- a/stdnum/iso7064/mod_37_2.py +++ b/stdnum/iso7064/mod_37_2.py @@ -39,11 +39,12 @@ >>> checksum('079X', alphabet='0123456789X') 1 """ +from __future__ import annotations from stdnum.exceptions import * -def checksum(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*'): +def checksum(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*') -> int: """Calculate the checksum. A valid number should have a checksum of 1.""" modulus = len(alphabet) check = 0 @@ -52,14 +53,14 @@ def checksum(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*'): return check -def calc_check_digit(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*'): +def calc_check_digit(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*') -> str: """Calculate the extra digit that should be appended to the number to make it a valid number.""" modulus = len(alphabet) return alphabet[(1 - 2 * checksum(number, alphabet)) % modulus] -def validate(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*'): +def validate(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*') -> str: """Check whether the check digit is valid.""" try: valid = checksum(number, alphabet) == 1 @@ -70,7 +71,7 @@ def validate(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*'): return number -def is_valid(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*'): +def is_valid(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*') -> bool: """Check whether the check digit is valid.""" try: return bool(validate(number, alphabet)) diff --git a/stdnum/iso7064/mod_37_36.py b/stdnum/iso7064/mod_37_36.py index d05531ff..091aeb77 100644 --- a/stdnum/iso7064/mod_37_36.py +++ b/stdnum/iso7064/mod_37_36.py @@ -37,11 +37,12 @@ >>> validate('002006673085', alphabet='0123456789') '002006673085' """ +from __future__ import annotations from stdnum.exceptions import * -def checksum(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'): +def checksum(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') -> int: """Calculate the checksum. A valid number should have a checksum of 1.""" modulus = len(alphabet) check = modulus // 2 @@ -50,14 +51,14 @@ def checksum(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'): return check -def calc_check_digit(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'): +def calc_check_digit(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') -> str: """Calculate the extra digit that should be appended to the number to make it a valid number.""" modulus = len(alphabet) return alphabet[(1 - ((checksum(number, alphabet) or modulus) * 2) % (modulus + 1)) % modulus] -def validate(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'): +def validate(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') -> str: """Check whether the check digit is valid.""" try: valid = checksum(number, alphabet) == 1 @@ -68,7 +69,7 @@ def validate(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'): return number -def is_valid(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'): +def is_valid(number: str, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') -> bool: """Check whether the check digit is valid.""" try: return bool(validate(number, alphabet)) diff --git a/stdnum/iso7064/mod_97_10.py b/stdnum/iso7064/mod_97_10.py index 21111c34..700f4fa7 100644 --- a/stdnum/iso7064/mod_97_10.py +++ b/stdnum/iso7064/mod_97_10.py @@ -33,28 +33,29 @@ >>> calc_check_digits('22181321402534321446701611') '35' """ +from __future__ import annotations from stdnum.exceptions import * -def _to_base10(number): +def _to_base10(number: str) -> str: """Prepare the number to its base10 representation.""" return ''.join( str(int(x, 36)) for x in number) -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum. A valid number should have a checksum of 1.""" return int(_to_base10(number)) % 97 -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the extra digits that should be appended to the number to make it a valid number.""" return '%02d' % (98 - checksum(number + '00')) -def validate(number): +def validate(number: str) -> str: """Check whether the check digit is valid.""" try: valid = checksum(number) == 1 @@ -65,7 +66,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check whether the check digit is valid.""" try: return bool(validate(number)) diff --git a/stdnum/isrc.py b/stdnum/isrc.py index 5fc4e01c..8fa4db60 100644 --- a/stdnum/isrc.py +++ b/stdnum/isrc.py @@ -35,6 +35,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations import re @@ -74,13 +75,13 @@ ]) -def compact(number): +def compact(number: str) -> str: """Convert the ISRC to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid ISRC. This checks the length, the alphabet, and the country code but does not check if the registrant code is known.""" @@ -95,7 +96,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISRC.""" try: return bool(validate(number)) @@ -103,7 +104,7 @@ def is_valid(number): return False -def format(number, separator='-'): +def format(number: str, separator: str = '-') -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return separator.join((number[0:2], number[2:5], number[5:7], number[7:])) diff --git a/stdnum/issn.py b/stdnum/issn.py index 7b821a75..caff7c61 100644 --- a/stdnum/issn.py +++ b/stdnum/issn.py @@ -48,19 +48,20 @@ >>> to_ean('0264-3596') '9770264359008' """ +from __future__ import annotations from stdnum import ean from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the ISSN to the minimal representation. This strips the number of any valid ISSN separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the ISSN check digit for 8-digit numbers. The number passed should not have the check digit included.""" check = (11 - sum((8 - i) * int(n) @@ -68,7 +69,7 @@ def calc_check_digit(number): return 'X' if check == 10 else str(check) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ISSN. This checks the length and whether the check digit is correct.""" number = compact(number) @@ -81,7 +82,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ISSN.""" try: return bool(validate(number)) @@ -89,13 +90,13 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:4] + '-' + number[4:] -def to_ean(number, issue_code='00'): +def to_ean(number: str, issue_code: str = '00') -> str: """Convert the number to EAN-13 format.""" number = '977' + validate(number)[:-1] + issue_code return number + ean.calc_check_digit(number) diff --git a/stdnum/it/__init__.py b/stdnum/it/__init__.py index dbf24799..7cc58a67 100644 --- a/stdnum/it/__init__.py +++ b/stdnum/it/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Italian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.it import iva as vat # noqa: F401 diff --git a/stdnum/it/aic.py b/stdnum/it/aic.py index 777f98d9..236931dd 100644 --- a/stdnum/it/aic.py +++ b/stdnum/it/aic.py @@ -46,6 +46,7 @@ >>> from_base32('009CVD') '000307052' """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -55,12 +56,12 @@ _base32_alphabet = '0123456789BCDFGHJKLMNPQRSTUVWXYZ' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" return clean(number, ' ').upper().strip() -def from_base32(number): +def from_base32(number: str) -> str: """Convert a BASE32 representation of an AIC to a BASE10 one.""" number = compact(number) if not all(x in _base32_alphabet for x in number): @@ -70,7 +71,7 @@ def from_base32(number): return str(s).zfill(9) -def to_base32(number): +def to_base32(number: str) -> str: """Convert a BASE10 representation of an AIC to a BASE32 one.""" number = compact(number) if not isdigits(number): @@ -84,7 +85,7 @@ def to_base32(number): return res.zfill(6) -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the BASE10 AIC code.""" number = compact(number) weights = (1, 2, 1, 2, 1, 2, 1, 2) @@ -92,7 +93,7 @@ def calc_check_digit(number): for x in (w * int(n) for w, n in zip(weights, number))) % 10) -def validate_base10(number): +def validate_base10(number: str) -> str: """Check if a string is a valid BASE10 representation of an AIC.""" number = compact(number) if len(number) != 9: @@ -106,7 +107,7 @@ def validate_base10(number): return number -def validate_base32(number): +def validate_base32(number: str) -> str: """Check if a string is a valid BASE32 representation of an AIC.""" number = compact(number) if len(number) != 6: @@ -114,7 +115,7 @@ def validate_base32(number): return validate_base10(from_base32(number)) -def validate(number): +def validate(number: str) -> str: """Check if a string is a valid AIC. BASE10 is the canonical form and is 9 chars long, while BASE32 is 6 chars.""" number = compact(number) @@ -124,7 +125,7 @@ def validate(number): return validate_base10(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the given string is a valid AIC code.""" try: return bool(validate(number)) diff --git a/stdnum/it/codicefiscale.py b/stdnum/it/codicefiscale.py index b38022c7..ef581da7 100644 --- a/stdnum/it/codicefiscale.py +++ b/stdnum/it/codicefiscale.py @@ -54,10 +54,12 @@ >>> calc_check_digit('RCCMNL83S18D969') 'H' """ +from __future__ import annotations import datetime import re +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.it import iva from stdnum.util import clean @@ -91,13 +93,13 @@ del values -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -:').strip().upper() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Compute the control code for the given personal number. The passed number should be the first 15 characters of a fiscal code.""" code = sum(_odd_values[x] if n % 2 == 0 else _even_values[x] @@ -105,7 +107,7 @@ def calc_check_digit(number): return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[code % 26] -def get_birth_date(number, minyear=1920): +def get_birth_date(number: str, minyear: int = 1920) -> datetime.date: """Get the birth date from the person's fiscal code. Only the last two digits of the year are stored in the number. The dates @@ -132,7 +134,7 @@ def get_birth_date(number, minyear=1920): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the gender of the person's fiscal code. >>> get_gender('RCCMNL83S18D969H') @@ -146,7 +148,7 @@ def get_gender(number): return 'M' if int(number[9:11]) < 32 else 'F' -def validate(number): +def validate(number: str) -> str: """Check if the given fiscal code is valid. This checks the length and whether the check digit is correct.""" number = compact(number) @@ -163,7 +165,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the given fiscal code is valid.""" try: return bool(validate(number)) diff --git a/stdnum/it/iva.py b/stdnum/it/iva.py index 5c7aecde..c9007d37 100644 --- a/stdnum/it/iva.py +++ b/stdnum/it/iva.py @@ -33,13 +33,14 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -:').upper().strip() @@ -48,7 +49,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -63,7 +64,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/jp/__init__.py b/stdnum/jp/__init__.py index 9b99e0e9..b9058a61 100644 --- a/stdnum/jp/__init__.py +++ b/stdnum/jp/__init__.py @@ -19,4 +19,6 @@ # 02110-1301 USA """Collection of Japanese numbers.""" +from __future__ import annotations + from stdnum.jp import cn as vat # noqa: F401 diff --git a/stdnum/jp/cn.py b/stdnum/jp/cn.py index ff31e356..dfff937c 100644 --- a/stdnum/jp/cn.py +++ b/stdnum/jp/cn.py @@ -39,18 +39,19 @@ >>> format('5835678256246') '5-8356-7825-6246' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '- ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" weights = (1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2) @@ -58,7 +59,7 @@ def calc_check_digit(number): return str(9 - s) -def validate(number): +def validate(number: str) -> str: """Check if the number is valid. This checks the length and check digit.""" number = compact(number) @@ -71,7 +72,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid CN.""" try: return bool(validate(number)) @@ -79,7 +80,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join( diff --git a/stdnum/ke/__init__.py b/stdnum/ke/__init__.py index 1b33710a..1b36e8f6 100644 --- a/stdnum/ke/__init__.py +++ b/stdnum/ke/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Kenyan numbers.""" +from __future__ import annotations # provide aliases from stdnum.ke import pin as vat # noqa: F401 diff --git a/stdnum/ke/pin.py b/stdnum/ke/pin.py index 82e49184..87f38480 100644 --- a/stdnum/ke/pin.py +++ b/stdnum/ke/pin.py @@ -49,6 +49,7 @@ >>> format('a004416331m') 'A004416331M' """ +from __future__ import annotations import re @@ -62,13 +63,13 @@ _pin_re = re.compile(r'^[A|P]{1}[0-9]{9}[A-Z]{1}$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Kenya PIN number. This checks the length and formatting. @@ -82,7 +83,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Kenya PIN number.""" try: return bool(validate(number)) @@ -90,6 +91,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/kr/__init__.py b/stdnum/kr/__init__.py index b8d75f2f..f8f64dae 100644 --- a/stdnum/kr/__init__.py +++ b/stdnum/kr/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of South Korean numbers.""" +from __future__ import annotations # provide aliases from stdnum.kr import brn as vat # noqa: F401 diff --git a/stdnum/kr/brn.py b/stdnum/kr/brn.py index ccb997c0..2053cc9a 100644 --- a/stdnum/kr/brn.py +++ b/stdnum/kr/brn.py @@ -43,12 +43,13 @@ >>> format('1348672683') '134-86-72683' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -57,7 +58,7 @@ def compact(number): return clean(number, ' -').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid South Korea BRN number. This checks the length and formatting. @@ -72,7 +73,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid South Korea BRN number.""" try: return bool(validate(number)) @@ -80,7 +81,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:3], number[3:5], number[5:]]) diff --git a/stdnum/kr/rrn.py b/stdnum/kr/rrn.py index 804a6c10..c30a4d2d 100644 --- a/stdnum/kr/rrn.py +++ b/stdnum/kr/rrn.py @@ -44,6 +44,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations import datetime @@ -51,20 +52,20 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5) check = sum(w * int(n) for w, n in zip(weights, number)) return str((11 - (check % 11)) % 10) -def get_birth_date(number, allow_future=True): +def get_birth_date(number: str, allow_future: bool = True) -> datetime.date: """Split the date parts from the number and return the birth date. If allow_future is False birth dates in the future are rejected.""" number = compact(number) @@ -90,7 +91,7 @@ def get_birth_date(number, allow_future=True): return date_of_birth -def validate(number, allow_future=True): +def validate(number: str, allow_future: bool = True) -> str: """Check if the number is a valid RNN. This checks the length, formatting and check digit. If allow_future is False birth dates in the future are rejected.""" @@ -110,7 +111,7 @@ def validate(number, allow_future=True): return number -def is_valid(number, allow_future=True): +def is_valid(number: str, allow_future: bool = True) -> bool: """Check if the number provided is valid.""" try: return bool(validate(number, allow_future)) @@ -118,7 +119,7 @@ def is_valid(number, allow_future=True): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) if len(number) == 13: diff --git a/stdnum/lei.py b/stdnum/lei.py index 057404b5..882ac8f9 100644 --- a/stdnum/lei.py +++ b/stdnum/lei.py @@ -39,19 +39,20 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_97_10 from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding white space.""" return clean(number, ' -').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number is valid. This checks the length, format and check digits.""" number = compact(number) @@ -59,7 +60,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is valid.""" try: return bool(validate(number)) diff --git a/stdnum/li/peid.py b/stdnum/li/peid.py index 4ed228c9..5d7f95ce 100644 --- a/stdnum/li/peid.py +++ b/stdnum/li/peid.py @@ -38,18 +38,19 @@ ... InvalidLength: The number has an invalid length. """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' .').strip().lstrip('0') -def validate(number): +def validate(number: str) -> str: """Check if the given fiscal code is valid.""" number = compact(number) if len(number) < 4 or len(number) > 12: @@ -60,7 +61,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the given fiscal code is valid.""" try: return bool(validate(number)) diff --git a/stdnum/lt/__init__.py b/stdnum/lt/__init__.py index 3a02b292..f4c3bf2e 100644 --- a/stdnum/lt/__init__.py +++ b/stdnum/lt/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Lithuanian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.lt import pvm as vat # noqa: F401 diff --git a/stdnum/lt/asmens.py b/stdnum/lt/asmens.py index a45dcdfc..2f901456 100644 --- a/stdnum/lt/asmens.py +++ b/stdnum/lt/asmens.py @@ -36,19 +36,20 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.ee.ik import calc_check_digit, get_birth_date from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def validate(number, validate_birth_date=True): +def validate(number: str, validate_birth_date: bool = True) -> str: """Check if the number provided is valid. This checks the length, formatting, embedded date and check digit.""" number = compact(number) @@ -63,7 +64,7 @@ def validate(number, validate_birth_date=True): return number -def is_valid(number, validate_birth_date=True): +def is_valid(number: str, validate_birth_date: bool = True) -> bool: """Check if the number provided is valid. This checks the length, formatting, embedded date and check digit.""" try: diff --git a/stdnum/lt/pvm.py b/stdnum/lt/pvm.py index a0984f4c..8e27ac22 100644 --- a/stdnum/lt/pvm.py +++ b/stdnum/lt/pvm.py @@ -36,12 +36,13 @@ >>> validate('100004801610') # second step in check digit calculation '100004801610' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -50,7 +51,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" check = sum((1 + i % 9) * int(n) for i, n in enumerate(number)) % 11 @@ -59,7 +60,7 @@ def calc_check_digit(number): return str(check % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -80,7 +81,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/lu/__init__.py b/stdnum/lu/__init__.py index 3c28de8b..33171cda 100644 --- a/stdnum/lu/__init__.py +++ b/stdnum/lu/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Luxembourgian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.lu import tva as vat # noqa: F401 diff --git a/stdnum/lu/tva.py b/stdnum/lu/tva.py index 4ec80946..6ebf4318 100644 --- a/stdnum/lu/tva.py +++ b/stdnum/lu/tva.py @@ -31,12 +31,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' :.-').upper().strip() @@ -45,12 +46,12 @@ def compact(number): return number -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the number.""" return '%02d' % (int(number) % 89) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -63,7 +64,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/luhn.py b/stdnum/luhn.py index e41ce78e..919299a8 100644 --- a/stdnum/luhn.py +++ b/stdnum/luhn.py @@ -43,22 +43,23 @@ >>> checksum('1234', alphabet='0123456789abcdef') 14 """ +from __future__ import annotations from stdnum.exceptions import * -def checksum(number, alphabet='0123456789'): +def checksum(number: str, alphabet: str = '0123456789') -> int: """Calculate the Luhn checksum over the provided number. The checksum is returned as an int. Valid numbers should have a checksum of 0.""" n = len(alphabet) - number = tuple(alphabet.index(i) + digits = tuple(alphabet.index(i) for i in reversed(str(number))) - return (sum(number[::2]) + + return (sum(digits[::2]) + sum(sum(divmod(i * 2, n)) - for i in number[1::2])) % n + for i in digits[1::2])) % n -def validate(number, alphabet='0123456789'): +def validate(number: str, alphabet: str = '0123456789') -> str: """Check if the number provided passes the Luhn checksum.""" if not bool(number): raise InvalidFormat() @@ -71,7 +72,7 @@ def validate(number, alphabet='0123456789'): return number -def is_valid(number, alphabet='0123456789'): +def is_valid(number: str, alphabet: str = '0123456789') -> bool: """Check if the number passes the Luhn checksum.""" try: return bool(validate(number, alphabet)) @@ -79,7 +80,7 @@ def is_valid(number, alphabet='0123456789'): return False -def calc_check_digit(number, alphabet='0123456789'): +def calc_check_digit(number: str, alphabet: str = '0123456789') -> str: """Calculate the extra digit that should be appended to the number to make it a valid number.""" ck = checksum(str(number) + alphabet[0], alphabet) diff --git a/stdnum/lv/__init__.py b/stdnum/lv/__init__.py index e6de3598..a014c0be 100644 --- a/stdnum/lv/__init__.py +++ b/stdnum/lv/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Latvian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.lv import pvn as vat # noqa: F401 diff --git a/stdnum/lv/pvn.py b/stdnum/lv/pvn.py index 006b5c0c..e4db741c 100644 --- a/stdnum/lv/pvn.py +++ b/stdnum/lv/pvn.py @@ -38,6 +38,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations import datetime @@ -50,7 +51,7 @@ # https://www6.vid.gov.lv/VID_PDB?aspxerrorpath=/vid_pdb/pvn.asp -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -59,13 +60,13 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum for legal entities.""" weights = (9, 1, 4, 8, 3, 10, 2, 5, 7, 6, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def calc_check_digit_pers(number): +def calc_check_digit_pers(number: str) -> str: """Calculate the check digit for personal codes. The number passed should not have the check digit included.""" # note that this algorithm has not been confirmed by an independent source @@ -74,7 +75,7 @@ def calc_check_digit_pers(number): return str(check % 11 % 10) -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) day = int(number[0:2]) @@ -87,7 +88,7 @@ def get_birth_date(number): raise InvalidComponent() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -108,7 +109,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/ma/__init__.py b/stdnum/ma/__init__.py index d2b0ff2d..30f3536f 100644 --- a/stdnum/ma/__init__.py +++ b/stdnum/ma/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Moroccan numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.ma import ice as vat # noqa: F401 diff --git a/stdnum/ma/ice.py b/stdnum/ma/ice.py index 10d4023e..fd222454 100644 --- a/stdnum/ma/ice.py +++ b/stdnum/ma/ice.py @@ -59,13 +59,14 @@ >>> format('00 21 36 09 30 00 040') '002136093000040' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_97_10 from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators, removes surrounding @@ -74,7 +75,7 @@ def compact(number): return clean(number, ' ') -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Morocco ICE number. This checks the length and formatting. @@ -89,7 +90,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Morocco ICE number.""" try: return bool(validate(number)) @@ -97,6 +98,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number).zfill(15) diff --git a/stdnum/mac.py b/stdnum/mac.py index 947e8936..fa2ea9e3 100644 --- a/stdnum/mac.py +++ b/stdnum/mac.py @@ -42,6 +42,7 @@ >>> get_iab('d0:50:99:84:a2:a0') '84A2A0' """ +from __future__ import annotations import re @@ -53,14 +54,14 @@ _mac_re = re.compile('^([0-9a-f]{2}:){5}[0-9a-f]{2}$') -def compact(number): +def compact(number: str) -> str: """Convert the MAC address to the minimal, consistent representation.""" number = clean(number, ' ').strip().lower().replace('-', ':') # zero-pad single-digit elements return ':'.join('0' + n if len(n) == 1 else n for n in number.split(':')) -def _lookup(number): +def _lookup(number: str) -> tuple[str, str]: """Look up the manufacturer in the IEEE OUI registry.""" number = compact(number).replace(':', '').upper() info = numdb.get('oui').info(number) @@ -72,23 +73,23 @@ def _lookup(number): raise InvalidComponent() -def get_manufacturer(number): +def get_manufacturer(number: str) -> str: """Look up the manufacturer in the IEEE OUI registry.""" return _lookup(number)[1] -def get_oui(number): +def get_oui(number: str) -> str: """Return the OUI (organization unique ID) part of the address.""" return _lookup(number)[0] -def get_iab(number): +def get_iab(number: str) -> str: """Return the IAB (individual address block) part of the address.""" number = compact(number).replace(':', '').upper() return number[len(get_oui(number)):] -def is_unicast(number): +def is_unicast(number: str) -> bool: """Check whether the number is a unicast address. Unicast addresses are received by one node in a network (LAN).""" @@ -96,7 +97,7 @@ def is_unicast(number): return int(number[:2], 16) & 1 == 0 -def is_multicast(number): +def is_multicast(number: str) -> bool: """Check whether the number is a multicast address. Multicast addresses are meant to be received by (potentially) multiple @@ -104,7 +105,7 @@ def is_multicast(number): return not is_unicast(number) -def is_broadcast(number): +def is_broadcast(number: str) -> bool: """Check whether the number is the broadcast address. Broadcast addresses are meant to be received by all nodes in a network.""" @@ -112,18 +113,18 @@ def is_broadcast(number): return number == 'ff:ff:ff:ff:ff:ff' -def is_universally_administered(number): +def is_universally_administered(number: str) -> bool: """Check if the address is supposed to be assigned by the manufacturer.""" number = compact(number) return int(number[:2], 16) & 2 == 0 -def is_locally_administered(number): +def is_locally_administered(number: str) -> bool: """Check if the address is meant to be configured by an administrator.""" return not is_universally_administered(number) -def validate(number, validate_manufacturer=None): +def validate(number: str, validate_manufacturer: bool | None = None) -> str: """Check if the number provided is a valid MAC address. The existence of the manufacturer is by default only checked for @@ -141,7 +142,7 @@ def validate(number, validate_manufacturer=None): return number -def is_valid(number, validate_manufacturer=None): +def is_valid(number: str, validate_manufacturer: bool | None = None) -> bool: """Check if the number provided is a valid IBAN.""" try: return bool(validate(number, validate_manufacturer=validate_manufacturer)) @@ -149,6 +150,6 @@ def is_valid(number, validate_manufacturer=None): return False -def to_eui48(number): +def to_eui48(number: str) -> str: """Convert the MAC address to EUI-48 format.""" return compact(number).upper().replace(':', '-') diff --git a/stdnum/mc/__init__.py b/stdnum/mc/__init__.py index 33e9a335..a0b6b01c 100644 --- a/stdnum/mc/__init__.py +++ b/stdnum/mc/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Monacan numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.mc import tva as vat # noqa: F401 diff --git a/stdnum/mc/tva.py b/stdnum/mc/tva.py index 0fe3bf5e..ebe6fd38 100644 --- a/stdnum/mc/tva.py +++ b/stdnum/mc/tva.py @@ -33,18 +33,19 @@ ... InvalidComponent: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.fr import tva -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return 'FR' + tva.compact(number) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = tva.validate(number) @@ -53,7 +54,7 @@ def validate(number): return 'FR' + number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/md/idno.py b/stdnum/md/idno.py index 5673d7f8..634c2e51 100644 --- a/stdnum/md/idno.py +++ b/stdnum/md/idno.py @@ -36,24 +36,25 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (7, 3, 1, 7, 3, 1, 7, 3, 1, 7, 3, 1) return str(sum(w * int(n) for w, n in zip(weights, number)) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is valid. This checks the length, formatting and check digit.""" number = compact(number) @@ -66,7 +67,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is valid. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/me/__init__.py b/stdnum/me/__init__.py index 5040786f..24ad62ee 100644 --- a/stdnum/me/__init__.py +++ b/stdnum/me/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Montenegro numbers.""" +from __future__ import annotations # provide aliases from stdnum.me import pib as vat # noqa: F401 diff --git a/stdnum/me/iban.py b/stdnum/me/iban.py index 248aa22c..bc4ad2e6 100644 --- a/stdnum/me/iban.py +++ b/stdnum/me/iban.py @@ -36,6 +36,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations from stdnum import iban from stdnum.exceptions import * @@ -48,12 +49,12 @@ format = iban.format -def _checksum(number): +def _checksum(number: str) -> int: """Calculate the check digits over the provided part of the number.""" return int(number) % 97 -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid Montenegro IBAN.""" number = iban.validate(number, check_country=False) if not number.startswith('ME'): @@ -63,7 +64,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid Montenegro IBAN.""" try: return bool(validate(number)) diff --git a/stdnum/me/pib.py b/stdnum/me/pib.py index 22216c3a..f8991ee6 100644 --- a/stdnum/me/pib.py +++ b/stdnum/me/pib.py @@ -37,12 +37,13 @@ >>> format('02655284') '02655284' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -51,13 +52,13 @@ def compact(number): return clean(number, ' ') -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number.""" weights = (8, 7, 6, 5, 4, 3, 2) return str((-sum(w * int(n) for w, n in zip(weights, number))) % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Montenegro PIB number.""" number = compact(number) if len(number) != 8: @@ -69,7 +70,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Montenegro PIB number.""" try: return bool(validate(number)) @@ -77,6 +78,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/meid.py b/stdnum/meid.py index d883eecb..2545d9eb 100644 --- a/stdnum/meid.py +++ b/stdnum/meid.py @@ -37,7 +37,9 @@ >>> format('af0123450abcDEC', format='dec', add_check_digit=True) '29360 87365 0070 3710 0' """ +from __future__ import annotations +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -45,20 +47,20 @@ _hex_alphabet = '0123456789ABCDEF' -def _cleanup(number): +def _cleanup(number: str) -> str: """Remove any grouping information from the number and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def _ishex(number): +def _ishex(number: str) -> bool: for x in number: if x not in _hex_alphabet: return False return True -def _parse(number): +def _parse(number: str) -> tuple[str, str]: number = _cleanup(number) if len(number) in (14, 15): # 14 or 15 digit hex representation @@ -74,7 +76,7 @@ def _parse(number): raise InvalidLength() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number. The number should not already have a check digit.""" # both the 18-digit decimal format and the 14-digit hex format @@ -86,7 +88,7 @@ def calc_check_digit(number): return luhn.calc_check_digit(number, alphabet=_hex_alphabet) -def compact(number, strip_check_digit=True): +def compact(number: str, strip_check_digit: bool = True) -> str: """Convert the MEID number to the minimal (hexadecimal) representation. This strips grouping information, removes surrounding whitespace and converts to hexadecimal if needed. If the check digit is to be preserved @@ -105,7 +107,7 @@ def compact(number, strip_check_digit=True): return number + cd -def validate(number, strip_check_digit=True): +def validate(number: str, strip_check_digit: bool = True) -> str: """Check if the number is a valid MEID number. This converts the representation format of the number (if it is decimal it is not converted to hexadecimal).""" @@ -136,7 +138,7 @@ def validate(number, strip_check_digit=True): return number + cd -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid MEID number.""" try: return bool(validate(number)) @@ -144,7 +146,12 @@ def is_valid(number): return False -def format(number, separator=' ', format=None, add_check_digit=False): +def format( + number: str, + separator: str = ' ', + format: t.Literal['dec', 'hex'] | None = None, + add_check_digit: bool = False, +) -> str: """Reformat the number to the standard presentation format. The separator used can be provided. If the format is specified (either 'hex' or 'dec') the number is reformatted in that format, otherwise the current @@ -167,22 +174,23 @@ def format(number, separator=' ', format=None, add_check_digit=False): if add_check_digit and not cd: cd = calc_check_digit(number) # split number according to format + parts: t.Iterable[str] if len(number) == 14: - number = [number[i * 2:i * 2 + 2] - for i in range(7)] + [cd] + parts = [number[i * 2:i * 2 + 2] + for i in range(7)] + [cd] else: - number = (number[:5], number[5:10], number[10:14], number[14:], cd) - return separator.join(x for x in number if x) + parts = (number[:5], number[5:10], number[10:14], number[14:], cd) + return separator.join(x for x in parts if x) -def to_binary(number): +def to_binary(number: str) -> bytes: """Convert the number to its binary representation (without the check digit).""" from binascii import a2b_hex return a2b_hex(compact(number, strip_check_digit=True)) -def to_pseudo_esn(number): +def to_pseudo_esn(number: str) -> str: """Convert the provided MEID to a pseudo ESN (pESN). The ESN is returned in compact hexadecimal representation.""" # return the last 6 digits of the SHA1 hash prefixed with the reserved diff --git a/stdnum/mk/__init__.py b/stdnum/mk/__init__.py index fac842e5..f709e2f4 100644 --- a/stdnum/mk/__init__.py +++ b/stdnum/mk/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of North Macedonia numbers.""" +from __future__ import annotations # provide aliases from stdnum.mk import edb as vat # noqa: F401 diff --git a/stdnum/mk/edb.py b/stdnum/mk/edb.py index c654dbde..47408183 100644 --- a/stdnum/mk/edb.py +++ b/stdnum/mk/edb.py @@ -42,12 +42,13 @@ >>> format('MK4057009501106') # ASCII letters '4057009501106' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -61,14 +62,14 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2) total = sum(int(n) * w for n, w in zip(number, weights)) return str((-total % 11) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid North Macedonia ЕДБ number. This checks the length, formatting and check digit. @@ -83,7 +84,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid North Macedonia ЕДБ number.""" try: return bool(validate(number)) @@ -91,6 +92,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/mt/vat.py b/stdnum/mt/vat.py index 6d7899ec..682fc7d7 100644 --- a/stdnum/mt/vat.py +++ b/stdnum/mt/vat.py @@ -29,12 +29,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -43,13 +44,13 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (3, 4, 6, 7, 8, 9, 10, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 37 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -62,7 +63,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/mu/nid.py b/stdnum/mu/nid.py index fef4193e..7fb2e8f9 100644 --- a/stdnum/mu/nid.py +++ b/stdnum/mu/nid.py @@ -37,6 +37,7 @@ * https://mnis.govmu.org/English/ID%20Card/Pages/default.aspx """ +from __future__ import annotations import datetime import re @@ -52,20 +53,20 @@ _alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separation dash.""" return clean(number, ' ').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number.""" check = sum((14 - i) * _alphabet.index(n) for i, n in enumerate(number[:13])) return _alphabet[(17 - check) % 17] -def _get_date(number): +def _get_date(number: str) -> datetime.date: """Convert the part of the number that represents a date into a datetime. Note that the century may be incorrect.""" day = int(number[1:3]) @@ -77,7 +78,7 @@ def _get_date(number): raise InvalidComponent() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ID number.""" number = compact(number) if len(number) != 14: @@ -90,7 +91,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid RFC.""" try: return bool(validate(number)) diff --git a/stdnum/mx/__init__.py b/stdnum/mx/__init__.py index 51c4fe49..19c3ad65 100644 --- a/stdnum/mx/__init__.py +++ b/stdnum/mx/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Mexican numbers.""" +from __future__ import annotations # provide aliases from stdnum.mx import curp as personalid # noqa: F401 diff --git a/stdnum/mx/curp.py b/stdnum/mx/curp.py index 33b91683..5ccb75a7 100644 --- a/stdnum/mx/curp.py +++ b/stdnum/mx/curp.py @@ -41,10 +41,12 @@ >>> get_gender('BOXW310820HNERXN09') 'M' """ +from __future__ import annotations import datetime import re +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean @@ -66,13 +68,13 @@ '''.split()) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separation dash.""" return clean(number, '-_ ').upper().strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) year = int(number[4:6]) @@ -88,7 +90,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the gender (M/F) from the person's CURP.""" number = compact(number) if number[10] == 'H': @@ -103,13 +105,13 @@ def get_gender(number): _alphabet = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ' -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" check = sum(_alphabet.index(c) * (18 - i) for i, c in enumerate(number[:17])) return str((10 - check % 10) % 10) -def validate(number, validate_check_digits=True): +def validate(number: str, validate_check_digits: bool = True) -> str: """Check if the number is a valid CURP.""" number = compact(number) if len(number) != 18: @@ -127,7 +129,7 @@ def validate(number, validate_check_digits=True): return number -def is_valid(number, validate_check_digits=True): +def is_valid(number: str, validate_check_digits: bool = True) -> bool: """Check if the number provided is a valid CURP.""" try: return bool(validate(number, validate_check_digits)) diff --git a/stdnum/mx/rfc.py b/stdnum/mx/rfc.py index 5bb6cf03..565ef1ba 100644 --- a/stdnum/mx/rfc.py +++ b/stdnum/mx/rfc.py @@ -59,6 +59,7 @@ >>> format('GODE561231GR8') 'GODE 561231 GR8' """ +from __future__ import annotations import datetime import re @@ -81,13 +82,13 @@ _alphabet = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ Ñ' -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separation dash.""" return clean(number, '-_ ').upper().strip() -def _get_date(number): +def _get_date(number: str) -> datetime.date: """Convert the part of the number that represents a date into a datetime. Note that the century may be incorrect.""" year = int(number[0:2]) @@ -99,7 +100,7 @@ def _get_date(number): raise InvalidComponent() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" number = (' ' + number)[-12:] @@ -107,7 +108,7 @@ def calc_check_digit(number): return _alphabet[(11 - check) % 11] -def validate(number, validate_check_digits=False): +def validate(number: str, validate_check_digits: bool = False) -> str: """Check if the number is a valid RFC.""" number = compact(number) if len(number) in (10, 13): @@ -132,7 +133,7 @@ def validate(number, validate_check_digits=False): return number -def is_valid(number, validate_check_digits=False): +def is_valid(number: str, validate_check_digits: bool = False) -> bool: """Check if the number provided is a valid RFC.""" try: return bool(validate(number, validate_check_digits)) @@ -140,7 +141,7 @@ def is_valid(number, validate_check_digits=False): return False -def format(number, separator=' '): +def format(number: str, separator: str = ' ') -> str: """Reformat the number to the standard presentation format.""" number = compact(number) if len(number) == 12: diff --git a/stdnum/my/nric.py b/stdnum/my/nric.py index c3283d7e..82e505ff 100644 --- a/stdnum/my/nric.py +++ b/stdnum/my/nric.py @@ -40,6 +40,7 @@ >>> format('770305021234') '770305-02-1234' """ +from __future__ import annotations import datetime @@ -47,13 +48,13 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -*').strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date. Note that in some cases it may return the registration date instead of the birth date and it may be a century off.""" @@ -72,7 +73,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_birth_place(number): +def get_birth_place(number: str) -> dict[str, str]: """Use the number to look up the place of birth of the person. This can either be a state or federal territory within Malaysia or a country outside of Malaysia.""" @@ -84,7 +85,7 @@ def get_birth_place(number): return results -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid NRIC number. This checks the length, formatting and birth date and place.""" number = compact(number) @@ -97,7 +98,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid NRIC number.""" try: return bool(validate(number)) @@ -105,7 +106,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:6] + '-' + number[6:8] + '-' + number[8:] diff --git a/stdnum/nl/__init__.py b/stdnum/nl/__init__.py index 408d065b..b6460755 100644 --- a/stdnum/nl/__init__.py +++ b/stdnum/nl/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Dutch numbers.""" +from __future__ import annotations # provide aliases from stdnum.nl import btw as vat # noqa: F401 diff --git a/stdnum/nl/brin.py b/stdnum/nl/brin.py index 656873a3..93e08235 100644 --- a/stdnum/nl/brin.py +++ b/stdnum/nl/brin.py @@ -44,6 +44,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations import re @@ -57,13 +58,13 @@ _brin_re = re.compile(r'^(?P[0-9]{2}[A-Z]{2})(?P[0-9]{2})?$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Brun number. This currently does not check whether the number points to a registered school.""" number = compact(number) @@ -75,7 +76,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Brun number.""" try: return bool(validate(number)) diff --git a/stdnum/nl/bsn.py b/stdnum/nl/bsn.py index 41047e4b..ff18488e 100644 --- a/stdnum/nl/bsn.py +++ b/stdnum/nl/bsn.py @@ -43,25 +43,26 @@ >>> format('111222333') '1112.22.333' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').strip().zfill(9) -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum over the number. A valid number should have a checksum of 0.""" return (sum((9 - i) * int(n) for i, n in enumerate(number[:-1])) - int(number[-1])) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid BSN. This checks the length and whether the check digit is correct.""" number = compact(number) @@ -74,7 +75,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid BSN.""" try: return bool(validate(number)) @@ -82,7 +83,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the passed number to the standard presentation format.""" number = compact(number) return number[:4] + '.' + number[4:6] + '.' + number[6:] diff --git a/stdnum/nl/btw.py b/stdnum/nl/btw.py index 6d123725..76e82be2 100644 --- a/stdnum/nl/btw.py +++ b/stdnum/nl/btw.py @@ -46,6 +46,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_97_10 @@ -53,7 +54,7 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').upper().strip() @@ -62,7 +63,7 @@ def compact(number): return bsn.compact(number[:-3]) + number[-3:] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid btw number. This checks the length, formatting and check digit.""" number = compact(number) @@ -79,7 +80,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid btw number.""" try: return bool(validate(number)) diff --git a/stdnum/nl/identiteitskaartnummer.py b/stdnum/nl/identiteitskaartnummer.py index 4bde2293..9fed5791 100644 --- a/stdnum/nl/identiteitskaartnummer.py +++ b/stdnum/nl/identiteitskaartnummer.py @@ -53,6 +53,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations import re @@ -60,13 +61,13 @@ from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding white space.""" return clean(number, ' ').strip().upper() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid passport number. This checks the length, formatting and check digit.""" number = compact(number) @@ -79,7 +80,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Dutch passport number.""" try: return bool(validate(number)) diff --git a/stdnum/nl/onderwijsnummer.py b/stdnum/nl/onderwijsnummer.py index ad3545c0..23babd23 100644 --- a/stdnum/nl/onderwijsnummer.py +++ b/stdnum/nl/onderwijsnummer.py @@ -42,6 +42,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.nl.bsn import checksum, compact @@ -51,7 +52,7 @@ __all__ = ['compact', 'validate', 'is_valid'] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid onderwijsnummer. This checks the length and whether the check digit is correct and whether it starts with the right sequence.""" @@ -67,7 +68,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid onderwijsnummer.""" try: return bool(validate(number)) diff --git a/stdnum/nl/postcode.py b/stdnum/nl/postcode.py index e91acc8a..db6c7bcf 100644 --- a/stdnum/nl/postcode.py +++ b/stdnum/nl/postcode.py @@ -39,6 +39,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations import re @@ -52,7 +53,7 @@ _postcode_blacklist = ('SA', 'SD', 'SS') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -61,7 +62,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is in the correct format. This currently does not check whether the code corresponds to a real address.""" number = compact(number) @@ -73,7 +74,7 @@ def validate(number): return '%s %s' % (match.group('pt1'), match.group('pt2')) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid postal code.""" try: return bool(validate(number)) diff --git a/stdnum/no/__init__.py b/stdnum/no/__init__.py index 2a9e88d7..c9c26ba8 100644 --- a/stdnum/no/__init__.py +++ b/stdnum/no/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Norwegian numbers.""" +from __future__ import annotations # provide aliases from stdnum.no import fodselsnummer as personalid # noqa: F401 diff --git a/stdnum/no/fodselsnummer.py b/stdnum/no/fodselsnummer.py index ccdf1adb..7d085af5 100644 --- a/stdnum/no/fodselsnummer.py +++ b/stdnum/no/fodselsnummer.py @@ -41,32 +41,34 @@ >>> format('15108695077') '151086 95077' """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -:') -def calc_check_digit1(number): +def calc_check_digit1(number: str) -> str: """Calculate the first check digit for the number.""" weights = (3, 7, 6, 1, 8, 9, 4, 5, 2) return str((11 - sum(w * int(n) for w, n in zip(weights, number))) % 11) -def calc_check_digit2(number): +def calc_check_digit2(number: str) -> str: """Calculate the second check digit for the number.""" weights = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2) return str((11 - sum(w * int(n) for w, n in zip(weights, number))) % 11) -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the person's birth gender ('M' or 'F').""" number = compact(number) if int(number[8]) % 2: @@ -75,7 +77,7 @@ def get_gender(number): return 'F' -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Determine and return the birth date.""" number = compact(number) day = int(number[0:2]) @@ -111,7 +113,7 @@ def get_birth_date(number): raise InvalidComponent('The number does not contain valid birth date information.') -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid birth number.""" number = compact(number) if len(number) != 11: @@ -128,7 +130,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid birth number.""" try: return bool(validate(number)) @@ -136,7 +138,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:6] + ' ' + number[6:] diff --git a/stdnum/no/iban.py b/stdnum/no/iban.py index 34a0ac79..3deb8edd 100644 --- a/stdnum/no/iban.py +++ b/stdnum/no/iban.py @@ -43,6 +43,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum import iban from stdnum.exceptions import * @@ -56,7 +57,7 @@ format = iban.format -def to_kontonr(number): +def to_kontonr(number: str) -> str: """Return the Norwegian bank account number part of the number.""" number = compact(number) if not number.startswith('NO'): @@ -64,14 +65,14 @@ def to_kontonr(number): return number[4:] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid Norwegian IBAN.""" number = iban.validate(number, check_country=False) kontonr.validate(to_kontonr(number)) return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid Norwegian IBAN.""" try: return bool(validate(number)) diff --git a/stdnum/no/kontonr.py b/stdnum/no/kontonr.py index addc767a..b1a0f42a 100644 --- a/stdnum/no/kontonr.py +++ b/stdnum/no/kontonr.py @@ -41,13 +41,14 @@ >>> to_iban('8601 11 17947') 'NO93 8601 11 17947' """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' .-').strip() @@ -56,13 +57,13 @@ def compact(number): return number -def _calc_check_digit(number): +def _calc_check_digit(number: str) -> str: """Calculate the check digit for the 11-digit number.""" weights = (6, 7, 8, 9, 4, 5, 6, 7, 8, 9) return str(sum(w * int(n) for w, n in zip(weights, number)) % 11) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid bank account number.""" number = compact(number) if not isdigits(number): @@ -77,7 +78,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid bank account number.""" try: return bool(validate(number)) @@ -85,7 +86,7 @@ def is_valid(number): return False -def to_iban(number): +def to_iban(number: str) -> str: """Convert the number to an IBAN.""" from stdnum import iban separator = ' ' if ' ' in number else '' @@ -94,7 +95,7 @@ def to_iban(number): number)) -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number).zfill(11) return '.'.join([ diff --git a/stdnum/no/mva.py b/stdnum/no/mva.py index 60a14214..3c52572c 100644 --- a/stdnum/no/mva.py +++ b/stdnum/no/mva.py @@ -33,13 +33,14 @@ >>> format('995525828MVA') 'NO 995 525 828 MVA' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.no import orgnr from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').upper().strip() @@ -48,7 +49,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid MVA number. This checks the length, formatting and check digit.""" number = compact(number) @@ -58,7 +59,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid MVA number.""" try: return bool(validate(number)) @@ -66,7 +67,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return 'NO ' + orgnr.format(number[:9]) + ' ' + number[9:] diff --git a/stdnum/no/orgnr.py b/stdnum/no/orgnr.py index d1bcf65f..497c094f 100644 --- a/stdnum/no/orgnr.py +++ b/stdnum/no/orgnr.py @@ -39,24 +39,25 @@ >>> format('988077917') '988 077 917' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (3, 2, 7, 6, 5, 4, 3, 2, 1) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid organisation number. This checks the length, formatting and check digit.""" number = compact(number) @@ -69,7 +70,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid organisation number.""" try: return bool(validate(number)) @@ -77,7 +78,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:3] + ' ' + number[3:6] + ' ' + number[6:] diff --git a/stdnum/numdb.py b/stdnum/numdb.py index c9c7283f..ab439673 100644 --- a/stdnum/numdb.py +++ b/stdnum/numdb.py @@ -58,9 +58,15 @@ [('1', {'prop1': 'foo'}), ('200', {'prop2': 'bar', 'prop3': 'baz'}), ('333', {'prop4': 'bax'})] """ +from __future__ import annotations import re +import sys +from stdnum import _typing as t + + +PrefixInfo: t.TypeAlias = 'tuple[int, str, str, dict[str, str], list[PrefixInfo]]' _line_re = re.compile( r'^(?P *)' @@ -84,19 +90,21 @@ class NumDB(): """Number database.""" - def __init__(self): + prefixes: list[PrefixInfo] + + def __init__(self) -> None: """Construct an empty database.""" self.prefixes = [] @staticmethod - def _find(number, prefixes): + def _find(number: str, prefixes: list[PrefixInfo]) -> list[tuple[str, dict[str, str]]]: """Lookup the specified number in the list of prefixes, this will return basically what info() should return but works recursively.""" if not number: return [] part = number - properties = {} - next_prefixes = [] + properties: dict[str, t.Any] = {} + next_prefixes: list[PrefixInfo] = [] # go over prefixes and find matches for length, low, high, props, children in prefixes: if len(part) >= length and low <= part[:length] <= high: @@ -110,20 +118,20 @@ def _find(number, prefixes): # return first part and recursively find next matches return [(part, properties)] + NumDB._find(number[len(part):], next_prefixes) - def info(self, number): + def info(self, number: str) -> list[tuple[str, dict[str, str]]]: """Split the provided number in components and associate properties with each component. This returns a tuple of tuples. Each tuple consists of a string (a part of the number) and a dict of properties. """ return NumDB._find(number, self.prefixes) - def split(self, number): + def split(self, number: str) -> list[str]: """Split the provided number in components. This returns a tuple with the number of components identified.""" return [part for part, props in self.info(number)] -def _parse(fp): +def _parse(fp: t.Iterable[str]) -> t.Generator[tuple[int, int, str, str, dict[str, str], list[PrefixInfo]]]: """Read lines of text from the file pointer and generate indent, length, low, high, properties tuples.""" for line in fp: @@ -132,10 +140,11 @@ def _parse(fp): continue # pragma: no cover (optimisation takes it out) # any other line should parse match = _line_re.search(line) + assert match is not None indent = len(match.group('indent')) ranges = match.group('ranges') props = dict(_prop_re.findall(match.group('props'))) - children = [] + children: list[PrefixInfo] = [] for rnge in ranges.split(','): if '-' in rnge: low, high = rnge.split('-') @@ -144,7 +153,7 @@ def _parse(fp): yield indent, len(low), low, high, props, children -def read(fp): +def read(fp: t.Iterable[str]) -> NumDB: """Return a new database with the data read from the specified file.""" last_indent = 0 db = NumDB() @@ -153,22 +162,21 @@ def read(fp): if indent > last_indent: # set our stack location to the last parent entry stack[indent] = stack[last_indent][-1][4] - stack[indent].append([length, low, high, props, children]) + stack[indent].append((length, low, high, props, children)) last_indent = indent return db -def _get_resource_stream(name): +def _get_resource_stream(name: str) -> t.IO[bytes]: """Return a readable file-like object for the resource.""" - try: # pragma: no cover (Python 3.9 and newer) - import importlib.resources - return importlib.resources.files(__package__).joinpath(name).open('rb') - except (ImportError, AttributeError): # pragma: no cover (older Python versions) - import pkg_resources - return pkg_resources.resource_stream(__name__, name) + if sys.version_info >= (3, 9): # pragma: no cover (Python 3.9 and newer) + import importlib.resources as importlib_resources + else: # pragma: no cover (older Python versions) + import importlib_resources + return importlib_resources.files(__package__).joinpath(name).open('rb') -def get(name): +def get(name: str) -> NumDB: """Open a database with the specified name to perform queries on.""" if name not in _open_databases: import codecs diff --git a/stdnum/nz/__init__.py b/stdnum/nz/__init__.py index 150057ee..74984747 100644 --- a/stdnum/nz/__init__.py +++ b/stdnum/nz/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of New Zealand numbers.""" +from __future__ import annotations # provide aliases from stdnum.nz import ird as vat # noqa: F401 diff --git a/stdnum/nz/bankaccount.py b/stdnum/nz/bankaccount.py index 3a561785..6aeaa46f 100644 --- a/stdnum/nz/bankaccount.py +++ b/stdnum/nz/bankaccount.py @@ -41,6 +41,7 @@ >>> format('0102420100194000') '01-0242-0100194-000' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -84,21 +85,21 @@ } -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" - number = clean(number).strip().replace(' ', '-').split('-') - if len(number) == 4: + parts = clean(number).strip().replace(' ', '-').split('-') + if len(parts) == 4: # zero pad the different sections if they are found lengths = (2, 4, 7, 3) - return ''.join(n.zfill(l) for n, l in zip(number, lengths)) + return ''.join(n.zfill(l) for n, l in zip(parts, lengths)) else: # otherwise zero pad the account type - number = ''.join(number) + number = ''.join(parts) return number[:13] + number[13:].zfill(3) -def info(number): +def info(number: str) -> dict[str, str]: """Return a dictionary of data about the supplied number. This typically returns the name of the bank and branch and a BIC if it is valid.""" number = compact(number) @@ -109,7 +110,7 @@ def info(number): return info -def _calc_checksum(number): +def _calc_checksum(number: str) -> int: # pick the algorithm and parameters algorithm = _algorithms.get(number[:2], 'X') if algorithm == 'A' and number[6:13] >= '0990000': @@ -122,7 +123,7 @@ def _calc_checksum(number): (w * int(n) for w, n in zip(weights, number))) % mod2 -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid bank account number.""" number = compact(number) if not isdigits(number): @@ -137,7 +138,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid bank account number.""" try: return bool(validate(number)) @@ -145,7 +146,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([ diff --git a/stdnum/nz/ird.py b/stdnum/nz/ird.py index 6bd313bb..5d4898ec 100644 --- a/stdnum/nz/ird.py +++ b/stdnum/nz/ird.py @@ -45,12 +45,13 @@ >>> format('49098576') '49-098-576' """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" number = clean(number, ' -').upper().strip() if number.startswith('NZ'): @@ -58,7 +59,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included. @@ -74,7 +75,7 @@ def calc_check_digit(number): return str(s) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid IRD number.""" number = compact(number) if len(number) not in (8, 9): @@ -88,7 +89,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid IRD number.""" try: return bool(validate(number)) @@ -96,7 +97,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:-6], number[-6:-3], number[-3:]]) diff --git a/stdnum/pe/__init__.py b/stdnum/pe/__init__.py index 80bbd529..6f5951d9 100644 --- a/stdnum/pe/__init__.py +++ b/stdnum/pe/__init__.py @@ -19,4 +19,6 @@ # 02110-1301 USA """Collection of Peruvian numbers.""" +from __future__ import annotations + from stdnum.pe import ruc as vat # noqa: F401 diff --git a/stdnum/pe/cui.py b/stdnum/pe/cui.py index d9c37eac..0850500b 100644 --- a/stdnum/pe/cui.py +++ b/stdnum/pe/cui.py @@ -41,18 +41,19 @@ >>> to_ruc('10117410-2') '10101174102' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip().upper() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the possible check digits for the CUI.""" number = compact(number) weights = (3, 2, 7, 6, 5, 4, 3, 2) @@ -60,14 +61,14 @@ def calc_check_digits(number): return '65432110987'[c] + 'KJIHGFEDCBA'[c] -def to_ruc(number): +def to_ruc(number: str) -> str: """Convert the number to a valid RUC.""" from stdnum.pe import ruc number = '10' + compact(number)[:8] return number + ruc.calc_check_digit(number) -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid CUI. This checks the length, formatting and check digit.""" number = compact(number) @@ -80,7 +81,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid CUI. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/pe/ruc.py b/stdnum/pe/ruc.py index 7d165249..31b554b6 100644 --- a/stdnum/pe/ruc.py +++ b/stdnum/pe/ruc.py @@ -39,24 +39,25 @@ >>> to_dni('10054148289') '05414828' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2) return str((11 - sum(w * int(n) for w, n in zip(weights, number)) % 11) % 10) -def to_dni(number): +def to_dni(number: str) -> str: """Return the DNI (CUI) part of the number for natural persons.""" number = validate(number) if not number.startswith('10'): @@ -64,7 +65,7 @@ def to_dni(number): return number[2:10] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid RUC. This checks the length, formatting and check digit.""" number = compact(number) @@ -79,7 +80,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid RUC. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/pk/cnic.py b/stdnum/pk/cnic.py index cd1381f7..5167cbb9 100644 --- a/stdnum/pk/cnic.py +++ b/stdnum/pk/cnic.py @@ -45,24 +45,27 @@ >>> format('3420108912318') '34201-0891231-8' """ +from __future__ import annotations +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F'] | None: """Get the person's birth gender ('M' or 'F').""" number = compact(number) if number[-1] in '13579': return 'M' elif number[-1] in '2468': return 'F' + return None # Valid Province IDs @@ -77,13 +80,13 @@ def get_gender(number): } -def get_province(number): - """Get the person's birth gender ('M' or 'F').""" +def get_province(number: str) -> str | None: + """Get the person's province.""" number = compact(number) return PROVINCES.get(number[0]) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid CNIC. This checks the length, formatting and some digits.""" number = compact(number) @@ -98,7 +101,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid CNIC.""" try: return bool(validate(number)) @@ -106,7 +109,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join((number[:5], number[5:12], number[12:])) diff --git a/stdnum/pl/__init__.py b/stdnum/pl/__init__.py index 1b5e5dbf..39989969 100644 --- a/stdnum/pl/__init__.py +++ b/stdnum/pl/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Polish numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.pl import nip as vat # noqa: F401 diff --git a/stdnum/pl/nip.py b/stdnum/pl/nip.py index 2ff1c0f8..3ef64097 100644 --- a/stdnum/pl/nip.py +++ b/stdnum/pl/nip.py @@ -31,12 +31,13 @@ >>> format('PL 8567346215') '856-734-62-15' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -45,13 +46,13 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" weights = (6, 5, 7, 2, 3, 4, 5, 6, 7, -1) return sum(w * int(n) for w, n in zip(weights, number)) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -64,7 +65,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) @@ -72,7 +73,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join((number[0:3], number[3:6], number[6:8], number[8:])) diff --git a/stdnum/pl/pesel.py b/stdnum/pl/pesel.py index 370082e3..6f6bad40 100644 --- a/stdnum/pl/pesel.py +++ b/stdnum/pl/pesel.py @@ -46,20 +46,22 @@ >>> get_gender('02211307589') 'F' """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) year = int(number[0:2]) @@ -79,7 +81,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the person's birth gender ('M' or 'F').""" number = compact(number) if number[9] in '02468': # even @@ -88,7 +90,7 @@ def get_gender(number): return 'M' -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for organisations. The number passed should not have the check digit included.""" weights = (1, 3, 7, 9, 1, 3, 7, 9, 1, 3) @@ -96,7 +98,7 @@ def calc_check_digit(number): return str((10 - check) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid national identification number. This checks the length, formatting and check digit.""" number = compact(number) @@ -110,7 +112,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid national identification number.""" try: return bool(validate(number)) diff --git a/stdnum/pl/regon.py b/stdnum/pl/regon.py index e738ca7e..1a575ec3 100644 --- a/stdnum/pl/regon.py +++ b/stdnum/pl/regon.py @@ -49,29 +49,30 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for organisations. The number passed should not have the check digit included.""" if len(number) == 8: - weights = (8, 9, 2, 3, 4, 5, 6, 7) + weights: tuple[int, ...] = (8, 9, 2, 3, 4, 5, 6, 7) else: weights = (2, 4, 8, 5, 0, 9, 7, 3, 6, 1, 2, 4, 8) check = sum(w * int(n) for w, n in zip(weights, number)) return str(check % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid REGON number. This checks the length, formatting and check digit.""" number = compact(number) @@ -86,7 +87,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid REGON number.""" try: return bool(validate(number)) diff --git a/stdnum/pt/__init__.py b/stdnum/pt/__init__.py index 70566544..862d29fe 100644 --- a/stdnum/pt/__init__.py +++ b/stdnum/pt/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Portuguese numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.pt import nif as vat # noqa: F401 diff --git a/stdnum/pt/cc.py b/stdnum/pt/cc.py index baac0dff..6033b312 100644 --- a/stdnum/pt/cc.py +++ b/stdnum/pt/cc.py @@ -41,6 +41,7 @@ >>> format('000000000ZZ4') '00000000 0 ZZ4' """ +from __future__ import annotations import re @@ -51,24 +52,27 @@ _cc_re = re.compile(r'^\d*[A-Z0-9]{2}\d$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' ').upper().strip() return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the number.""" alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - cutoff = lambda x: x - 9 if x > 9 else x + + def cutoff(x: int) -> int: + return x - 9 if x > 9 else x + s = sum( cutoff(alphabet.index(n) * 2) if i % 2 == 0 else alphabet.index(n) for i, n in enumerate(number[::-1])) return str((10 - s) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid cartao de cidadao number.""" number = compact(number) if not _cc_re.match(number): @@ -78,7 +82,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid cartao de cidadao number.""" try: return bool(validate(number)) @@ -86,7 +90,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join([number[:-4], number[-4], number[-3:]]) diff --git a/stdnum/pt/nif.py b/stdnum/pt/nif.py index 77a2808e..7e9fb94d 100644 --- a/stdnum/pt/nif.py +++ b/stdnum/pt/nif.py @@ -31,12 +31,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').upper().strip() @@ -45,14 +46,14 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" s = sum((9 - i) * int(n) for i, n in enumerate(number)) return str((11 - s) % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -65,7 +66,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/py.typed b/stdnum/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/stdnum/py/__init__.py b/stdnum/py/__init__.py index b7916f73..6bbf3fd9 100644 --- a/stdnum/py/__init__.py +++ b/stdnum/py/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Paraguayan numbers.""" +from __future__ import annotations # provide aliases from stdnum.py import ruc as vat # noqa: F401 diff --git a/stdnum/py/ruc.py b/stdnum/py/ruc.py index 0630d96d..47f92bd3 100644 --- a/stdnum/py/ruc.py +++ b/stdnum/py/ruc.py @@ -49,12 +49,13 @@ >>> format('800000358') '80000035-8' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -63,7 +64,7 @@ def compact(number): return clean(number, ' -').upper().strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included. @@ -72,7 +73,7 @@ def calc_check_digit(number): return str((-s % 11) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Paraguay RUC number. This checks the length, formatting and check digit. @@ -87,7 +88,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Paraguay RUC number.""" try: return bool(validate(number)) @@ -95,7 +96,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:-1], number[-1]]) diff --git a/stdnum/ro/__init__.py b/stdnum/ro/__init__.py index b0ae7a29..d543d308 100644 --- a/stdnum/ro/__init__.py +++ b/stdnum/ro/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Romanian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.ro import cf as vat # noqa: F401 diff --git a/stdnum/ro/cf.py b/stdnum/ro/cf.py index e6b7511e..0e91b1b1 100644 --- a/stdnum/ro/cf.py +++ b/stdnum/ro/cf.py @@ -29,13 +29,14 @@ >>> validate('1630615123457') # CNP '1630615123457' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.ro import cnp, cui from stdnum.util import clean -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() @@ -45,7 +46,7 @@ def compact(number): calc_check_digit = cui.calc_check_digit -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -62,7 +63,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/ro/cnp.py b/stdnum/ro/cnp.py index 44e02e8c..2fb24708 100644 --- a/stdnum/ro/cnp.py +++ b/stdnum/ro/cnp.py @@ -49,6 +49,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations import datetime @@ -111,13 +112,13 @@ } -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for personal codes.""" # note that this algorithm has not been confirmed by an independent source weights = (2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9) @@ -125,7 +126,7 @@ def calc_check_digit(number): return '1' if check == 10 else str(check) -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the birth date.""" number = compact(number) centuries = { @@ -140,7 +141,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_county(number): +def get_county(number: str) -> str: """Get the county name from the number""" try: return _COUNTIES[compact(number)[7:9]] @@ -148,7 +149,7 @@ def get_county(number): raise InvalidComponent() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -169,7 +170,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/ro/cui.py b/stdnum/ro/cui.py index fbfad983..0de44b58 100644 --- a/stdnum/ro/cui.py +++ b/stdnum/ro/cui.py @@ -42,12 +42,13 @@ >>> validate('RO 185 472 90') # the RO prefix is ignored '18547290' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -56,7 +57,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (7, 5, 3, 2, 1, 7, 5, 3, 2) number = number.zfill(9) @@ -64,7 +65,7 @@ def calc_check_digit(number): return str(check % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid CUI or CIF number. This checks the length, formatting and check digit.""" number = compact(number) @@ -77,7 +78,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid CUI or CIF number.""" try: return bool(validate(number)) diff --git a/stdnum/ro/onrc.py b/stdnum/ro/onrc.py index 21860f3b..cd81ed6b 100644 --- a/stdnum/ro/onrc.py +++ b/stdnum/ro/onrc.py @@ -34,6 +34,7 @@ ... InvalidComponent: ... """ +from __future__ import annotations import datetime import re @@ -56,7 +57,7 @@ _counties = set(list(range(1, 41)) + [51, 52]) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = _cleanup_re.sub('/', clean(number).upper().strip()) @@ -73,7 +74,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ONRC.""" number = compact(number) if not _onrc_re.match(number): @@ -92,7 +93,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ONRC.""" try: return bool(validate(number)) diff --git a/stdnum/rs/__init__.py b/stdnum/rs/__init__.py index 05916563..7d939ba7 100644 --- a/stdnum/rs/__init__.py +++ b/stdnum/rs/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Serbian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.rs import pib as vat # noqa: F401 diff --git a/stdnum/rs/pib.py b/stdnum/rs/pib.py index d53e2114..da93e976 100644 --- a/stdnum/rs/pib.py +++ b/stdnum/rs/pib.py @@ -30,19 +30,20 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.iso7064 import mod_11_10 from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -54,7 +55,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/ru/__init__.py b/stdnum/ru/__init__.py index 470f21a9..3fa8524d 100644 --- a/stdnum/ru/__init__.py +++ b/stdnum/ru/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Russian numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.ru import inn as vat # noqa: F401 diff --git a/stdnum/ru/inn.py b/stdnum/ru/inn.py index 58cc2167..606b4428 100644 --- a/stdnum/ru/inn.py +++ b/stdnum/ru/inn.py @@ -37,33 +37,34 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def calc_company_check_digit(number): +def calc_company_check_digit(number: str) -> str: """Calculate the check digit for the 10-digit ИНН for organisations.""" weights = (2, 4, 10, 3, 5, 9, 4, 6, 8) return str(sum(w * int(n) for w, n in zip(weights, number)) % 11 % 10) -def calc_personal_check_digits(number): +def calc_personal_check_digits(number: str) -> str: """Calculate the check digits for the 12-digit personal ИНН.""" - weights = (7, 2, 4, 10, 3, 5, 9, 4, 6, 8) + weights: tuple[int, ...] = (7, 2, 4, 10, 3, 5, 9, 4, 6, 8) d1 = str(sum(w * int(n) for w, n in zip(weights, number)) % 11 % 10) weights = (3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8) d2 = str(sum(w * int(n) for w, n in zip(weights, number[:10] + d1)) % 11 % 10) return d1 + d2 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ИНН. This checks the length, formatting and check digit.""" number = compact(number) @@ -81,7 +82,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ИНН.""" try: return bool(validate(number)) diff --git a/stdnum/ru/ogrn.py b/stdnum/ru/ogrn.py index e3d65586..d434da70 100644 --- a/stdnum/ru/ogrn.py +++ b/stdnum/ru/ogrn.py @@ -43,18 +43,19 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ') -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the control digit of the OGRN based on its length.""" if len(number) == 13: return str(int(number[:-1]) % 11 % 10) @@ -62,7 +63,7 @@ def calc_check_digit(number): return str(int(number[:-1]) % 13) -def validate(number): +def validate(number: str) -> str: """Determine if the given number is a valid OGRN.""" number = compact(number) if not isdigits(number): @@ -80,7 +81,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid OGRN.""" try: return bool(validate(number)) diff --git a/stdnum/se/__init__.py b/stdnum/se/__init__.py index 23b98c5f..95f46c2c 100644 --- a/stdnum/se/__init__.py +++ b/stdnum/se/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Swedish numbers.""" +from __future__ import annotations # provide aliases from stdnum.se import personnummer as personalid # noqa: F401 diff --git a/stdnum/se/orgnr.py b/stdnum/se/orgnr.py index 2aea9eb0..2922ba30 100644 --- a/stdnum/se/orgnr.py +++ b/stdnum/se/orgnr.py @@ -35,19 +35,20 @@ >>> format('123456-7897') '123456-7897' """ +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -.').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid organisation number. This checks the length, formatting and check digit.""" number = compact(number) @@ -58,7 +59,7 @@ def validate(number): return luhn.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid organisation number""" try: return bool(validate(number)) @@ -66,7 +67,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number[:6] + '-' + number[6:] diff --git a/stdnum/se/personnummer.py b/stdnum/se/personnummer.py index 8829ef78..2e53941c 100644 --- a/stdnum/se/personnummer.py +++ b/stdnum/se/personnummer.py @@ -44,15 +44,17 @@ >>> format('8803200016') '880320-0016' """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' :') @@ -61,7 +63,7 @@ def compact(number): return number[:-5].replace('-', '').replace('+', '') + number[-5:] -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Determine the birth date from the number. For people aged 100 and up, the minus/dash in the personnummer is changed to a plus @@ -90,7 +92,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the person's birth gender ('M' or 'F').""" number = compact(number) if int(number[-2]) % 2: @@ -99,7 +101,7 @@ def get_gender(number): return 'F' -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid identity number.""" number = compact(number) if len(number) not in (11, 13): @@ -112,7 +114,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid identity number.""" try: return bool(validate(number)) @@ -120,6 +122,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/se/postnummer.py b/stdnum/se/postnummer.py index b2246db8..4ceec897 100644 --- a/stdnum/se/postnummer.py +++ b/stdnum/se/postnummer.py @@ -38,12 +38,13 @@ >>> format('11418') '114 18' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -52,7 +53,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is in the correct format. This currently does not check whether the code corresponds to a real address.""" number = compact(number) @@ -63,7 +64,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid postal code.""" try: return bool(validate(number)) @@ -71,7 +72,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '%s %s' % (number[:3], number[3:]) diff --git a/stdnum/se/vat.py b/stdnum/se/vat.py index 9dcd3565..9428e5a4 100644 --- a/stdnum/se/vat.py +++ b/stdnum/se/vat.py @@ -31,13 +31,14 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.se import orgnr from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -.').upper().strip() @@ -46,7 +47,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -56,7 +57,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/sg/__init__.py b/stdnum/sg/__init__.py index 1d3e79d3..984c605a 100644 --- a/stdnum/sg/__init__.py +++ b/stdnum/sg/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Singapore numbers.""" +from __future__ import annotations # provide aliases from stdnum.sg import uen as vat # noqa: F401 diff --git a/stdnum/sg/uen.py b/stdnum/sg/uen.py index ea0a1b25..7c562784 100644 --- a/stdnum/sg/uen.py +++ b/stdnum/sg/uen.py @@ -59,6 +59,7 @@ # There are some references to special 10-digit (or 7-digit) numbers that # start with an F for foreign companies but it is unclear whether this is # still current and not even examples of these numbers could be found. +from __future__ import annotations from datetime import datetime @@ -74,7 +75,7 @@ ) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This converts to uppercase and removes surrounding whitespace. It @@ -84,14 +85,14 @@ def compact(number): return clean(number).upper().strip() -def calc_business_check_digit(number): +def calc_business_check_digit(number: str) -> str: """Calculate the check digit for the Business (ROB) number.""" number = compact(number) weights = (10, 4, 9, 3, 8, 2, 7, 1) return 'XMKECAWLJDB'[sum(int(n) * w for n, w in zip(number, weights)) % 11] -def _validate_business(number): +def _validate_business(number: str) -> str: """Perform validation on UEN - Business (ROB) numbers.""" if not isdigits(number[:-1]): raise InvalidFormat() @@ -102,14 +103,14 @@ def _validate_business(number): return number -def calc_local_company_check_digit(number): +def calc_local_company_check_digit(number: str) -> str: """Calculate the check digit for the Local Company (ROC) number.""" number = compact(number) weights = (10, 8, 6, 4, 9, 7, 5, 3, 1) return 'ZKCMDNERGWH'[sum(int(n) * w for n, w in zip(number, weights)) % 11] -def _validate_local_company(number): +def _validate_local_company(number: str) -> str: """Perform validation on UEN - Local Company (ROC) numbers.""" if not isdigits(number[:-1]): raise InvalidFormat() @@ -121,7 +122,7 @@ def _validate_local_company(number): return number -def calc_other_check_digit(number): +def calc_other_check_digit(number: str) -> str: """Calculate the check digit for the other entities number.""" number = compact(number) alphabet = 'ABCDEFGHJKLMNPQRSTUVWX0123456789' @@ -129,7 +130,7 @@ def calc_other_check_digit(number): return alphabet[(sum(alphabet.index(n) * w for n, w in zip(number, weights)) - 5) % 11] -def _validate_other(number): +def _validate_other(number: str) -> str: """Perform validation on other UEN numbers.""" if number[0] not in ('R', 'S', 'T'): raise InvalidComponent() @@ -147,7 +148,7 @@ def _validate_other(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Singapore UEN number.""" number = compact(number) if len(number) not in (9, 10): @@ -159,7 +160,7 @@ def validate(number): return _validate_other(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Singapore UEN number.""" try: return bool(validate(number)) @@ -167,6 +168,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/si/__init__.py b/stdnum/si/__init__.py index 98349b8c..b292f90f 100644 --- a/stdnum/si/__init__.py +++ b/stdnum/si/__init__.py @@ -20,6 +20,7 @@ # 02110-1301 USA """Collection of Slovenian numbers.""" +from __future__ import annotations # provide aliases from stdnum.si import ddv as vat # noqa: F401 diff --git a/stdnum/si/ddv.py b/stdnum/si/ddv.py index bb6d9865..dc6cbfaf 100644 --- a/stdnum/si/ddv.py +++ b/stdnum/si/ddv.py @@ -31,12 +31,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -45,7 +46,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" check = (11 - sum((8 - i) * int(n) for i, n in enumerate(number)) % 11) @@ -53,7 +54,7 @@ def calc_check_digit(number): return '0' if check == 10 else str(check) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -66,7 +67,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/si/emso.py b/stdnum/si/emso.py index 2a129562..4fda7228 100644 --- a/stdnum/si/emso.py +++ b/stdnum/si/emso.py @@ -40,27 +40,29 @@ ... InvalidChecksum: ... """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2) total = sum(int(n) * w for n, w in zip(number, weights)) return str(-total % 11 % 10) -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Return date of birth from valid EMŠO.""" number = compact(number) day = int(number[:2]) @@ -76,7 +78,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the person's birth gender ('M' or 'F').""" number = compact(number) if int(number[9:12]) < 500: @@ -85,12 +87,12 @@ def get_gender(number): return 'F' -def get_region(number): +def get_region(number: str) -> str: """Return (political) region from valid EMŠO.""" return number[7:9] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid EMŠO number. This checks the length, formatting and check digit.""" number = compact(number) @@ -104,7 +106,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid ID. This checks the length, formatting and check digit.""" try: @@ -113,6 +115,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/si/maticna.py b/stdnum/si/maticna.py index 6aa91359..092c00db 100644 --- a/stdnum/si/maticna.py +++ b/stdnum/si/maticna.py @@ -42,6 +42,7 @@ ... InvalidChecksum: ... """ +from __future__ import annotations import re @@ -49,7 +50,7 @@ from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, '. ').strip().upper() @@ -58,7 +59,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (7, 6, 5, 4, 3, 2) total = sum(int(n) * w for n, w in zip(number, weights)) @@ -68,7 +69,7 @@ def calc_check_digit(number): return str(remainder % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Corporate Registration number. This checks the length and check digit.""" number = compact(number) @@ -83,7 +84,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if provided is valid ID. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/sk/__init__.py b/stdnum/sk/__init__.py index c2e257ee..b0140d8c 100644 --- a/stdnum/sk/__init__.py +++ b/stdnum/sk/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Slovak numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.sk import dph as vat # noqa: F401 diff --git a/stdnum/sk/dph.py b/stdnum/sk/dph.py index c76f5265..f2ab68df 100644 --- a/stdnum/sk/dph.py +++ b/stdnum/sk/dph.py @@ -30,13 +30,14 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.sk import rc from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" number = clean(number, ' -').upper().strip() @@ -45,12 +46,12 @@ def compact(number): return number -def checksum(number): +def checksum(number: str) -> int: """Calculate the checksum.""" return int(number) % 11 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This checks the length, formatting and check digit.""" number = compact(number) @@ -68,7 +69,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/sk/rc.py b/stdnum/sk/rc.py index f43dc798..8442d471 100644 --- a/stdnum/sk/rc.py +++ b/stdnum/sk/rc.py @@ -49,6 +49,8 @@ # since this number is essentially the same as the Czech counterpart # (until 1993 the Czech Republic and Slovakia were one country) +from __future__ import annotations + from stdnum.cz.rc import compact, format, is_valid, validate diff --git a/stdnum/sm/__init__.py b/stdnum/sm/__init__.py index 28c385d9..99a7f0c5 100644 --- a/stdnum/sm/__init__.py +++ b/stdnum/sm/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of San Marino numbers.""" +from __future__ import annotations # provide vat as an alias from stdnum.sm import coe as vat # noqa: F401 diff --git a/stdnum/sm/coe.py b/stdnum/sm/coe.py index 721bda52..955f4b9e 100644 --- a/stdnum/sm/coe.py +++ b/stdnum/sm/coe.py @@ -38,6 +38,7 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -51,13 +52,13 @@ 87, 88, 91, 92, 94, 95, 96, 97, 99)) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips surrounding whitespace and separation dash.""" return clean(number, '.').strip().lstrip('0') -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid COE. This checks the length and formatting.""" number = compact(number) @@ -70,7 +71,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid COE.""" try: return bool(validate(number)) diff --git a/stdnum/sv/__init__.py b/stdnum/sv/__init__.py index dd63b082..bdf57120 100644 --- a/stdnum/sv/__init__.py +++ b/stdnum/sv/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of El Salvador numbers.""" +from __future__ import annotations # provide aliases from stdnum.sv import nit as vat # noqa: F401 diff --git a/stdnum/sv/nit.py b/stdnum/sv/nit.py index 24124e9e..8108748f 100644 --- a/stdnum/sv/nit.py +++ b/stdnum/sv/nit.py @@ -60,12 +60,13 @@ >>> format('06140507071048') '0614-050707-104-8' """ # noqa: E501 +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -77,7 +78,7 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" # Old NIT if number[10:13] <= '100': @@ -90,7 +91,7 @@ def calc_check_digit(number): return str((-total % 11) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid El Salvador NIT number. This checks the length, formatting and check digit. @@ -107,7 +108,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid El Salvador NIT number.""" try: return bool(validate(number)) @@ -115,7 +116,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:4], number[4:-4], number[-4:-1], number[-1]]) diff --git a/stdnum/th/moa.py b/stdnum/th/moa.py index 95380218..b963b474 100644 --- a/stdnum/th/moa.py +++ b/stdnum/th/moa.py @@ -44,6 +44,7 @@ >>> format('0993000133978') '0-99-3-000-13397-8' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.th import pin @@ -57,13 +58,13 @@ calc_check_digit = pin.calc_check_digit -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid MOA Number. This checks the length, formatting, component and check digit.""" number = compact(number) @@ -78,7 +79,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check whether the number is valid.""" try: return bool(validate(number)) @@ -86,7 +87,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join(( diff --git a/stdnum/th/pin.py b/stdnum/th/pin.py index 77afe402..db02c936 100644 --- a/stdnum/th/pin.py +++ b/stdnum/th/pin.py @@ -43,24 +43,25 @@ >>> format('7100600445635') '7-1006-00445-63-5' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" s = sum((2 - i) * int(n) for i, n in enumerate(number[:12])) % 11 return str((1 - s) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid PIN. This checks the length, formatting and check digit.""" number = compact(number) @@ -75,7 +76,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check whether the number is valid.""" try: return bool(validate(number)) @@ -83,7 +84,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join(( diff --git a/stdnum/th/tin.py b/stdnum/th/tin.py index cc513874..971ce225 100644 --- a/stdnum/th/tin.py +++ b/stdnum/th/tin.py @@ -42,6 +42,7 @@ >>> format('0993000133978') '0-99-3-000-13397-8' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.th import moa, pin @@ -51,13 +52,13 @@ _tin_modules = (moa, pin) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').strip() -def tin_type(number): +def tin_type(number: str) -> str | None: """Return a TIN type which this number is valid.""" number = compact(number) for mod in _tin_modules: @@ -67,19 +68,19 @@ def tin_type(number): return None -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid TIN. This searches for the proper sub-type and validates using that.""" for mod in _tin_modules: try: - return mod.validate(number) + return mod.validate(number) # type: ignore[no-any-return] except ValidationError: pass # try next module # fallback raise InvalidFormat() -def is_valid(number): +def is_valid(number: str) -> bool: """Check whether the number is valid.""" try: return bool(validate(number)) @@ -87,9 +88,9 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" for mod in _tin_modules: if mod.is_valid(number): - return mod.format(number) + return mod.format(number) # type: ignore[no-any-return] return number diff --git a/stdnum/tn/__init__.py b/stdnum/tn/__init__.py index edbf777d..9496b654 100644 --- a/stdnum/tn/__init__.py +++ b/stdnum/tn/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Tunisian numbers.""" +from __future__ import annotations # provide aliases from stdnum.tn import mf as vat # noqa: F401 diff --git a/stdnum/tn/mf.py b/stdnum/tn/mf.py index 90cb3cf7..601c52be 100644 --- a/stdnum/tn/mf.py +++ b/stdnum/tn/mf.py @@ -62,6 +62,7 @@ >>> format('1496298 T P N 000') '1496298/T/P/N/000' """ +from __future__ import annotations import re @@ -76,7 +77,7 @@ _VALID_CATEGORY_CODES = ('M', 'P', 'C', 'N', 'E') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators, removes surrounding @@ -90,7 +91,7 @@ def compact(number): return number -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Tunisia MF number. This checks the length and formatting. @@ -115,7 +116,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Tunisia MF number.""" try: return bool(validate(number)) @@ -123,7 +124,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" result = compact(number) if len(result) == 8: diff --git a/stdnum/tr/__init__.py b/stdnum/tr/__init__.py index ebb92d35..045f5e48 100644 --- a/stdnum/tr/__init__.py +++ b/stdnum/tr/__init__.py @@ -19,4 +19,6 @@ # 02110-1301 USA """Collection of Turkish numbers.""" +from __future__ import annotations + from stdnum.tr import vkn as vat # noqa: F401 diff --git a/stdnum/tr/tckimlik.py b/stdnum/tr/tckimlik.py index 9a5df41b..8074d989 100644 --- a/stdnum/tr/tckimlik.py +++ b/stdnum/tr/tckimlik.py @@ -44,6 +44,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, get_soap_client, isdigits @@ -53,13 +54,13 @@ """The WSDL URL of the T.C. Kimlik validation service.""" -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number).strip() -def calc_check_digits(number): +def calc_check_digits(number: str) -> str: """Calculate the check digits for the specified number. The number passed should not have the check digit included.""" check1 = (10 - sum((3, 1)[i % 2] * int(n) @@ -68,7 +69,7 @@ def calc_check_digits(number): return '%d%d' % (check1, check2) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid T.C. Kimlik number. This checks the length and check digits.""" number = compact(number) @@ -81,7 +82,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid T.C. Kimlik number.""" try: return bool(validate(number)) @@ -89,7 +90,7 @@ def is_valid(number): return False -def check_kps(number, name, surname, birth_year, timeout=30, verify=True): # pragma: no cover +def check_kps(number, name, surname, birth_year, timeout=30, verify=True): # type: ignore # pragma: no cover """Use the T.C. Kimlik validation service to check the provided number. Query the online T.C. Kimlik validation service run by the Directorate diff --git a/stdnum/tr/vkn.py b/stdnum/tr/vkn.py index e23cb918..40601b65 100644 --- a/stdnum/tr/vkn.py +++ b/stdnum/tr/vkn.py @@ -39,18 +39,19 @@ ... InvalidLength: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number).strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the specified number.""" s = 0 for i, n in enumerate(reversed(number[:9]), 1): @@ -61,7 +62,7 @@ def calc_check_digit(number): return str((10 - s) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Vergi Kimlik Numarası. This checks the length and check digits.""" number = compact(number) @@ -74,7 +75,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Vergi Kimlik Numarası. This checks the length and check digits.""" try: diff --git a/stdnum/tw/__init__.py b/stdnum/tw/__init__.py index d79cc3db..e0439dcc 100644 --- a/stdnum/tw/__init__.py +++ b/stdnum/tw/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Taiwanese numbers.""" +from __future__ import annotations # provide aliases from stdnum.tw import ubn as vat # noqa: F401 diff --git a/stdnum/tw/ubn.py b/stdnum/tw/ubn.py index efae7406..bc332087 100644 --- a/stdnum/tw/ubn.py +++ b/stdnum/tw/ubn.py @@ -42,12 +42,13 @@ >>> format(' 0050150 3 ') '00501503' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -56,7 +57,7 @@ def compact(number): return clean(number, ' -').strip() -def calc_checksum(number): +def calc_checksum(number: str) -> int: """Calculate the checksum over the number.""" # convert to numeric first, then sum individual digits weights = (1, 2, 1, 2, 1, 2, 4, 1) @@ -64,7 +65,7 @@ def calc_checksum(number): return sum(int(n) for n in number) % 10 -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Taiwan UBN number. This checks the length, formatting and check digit. @@ -80,7 +81,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Taiwan UBN number.""" try: return bool(validate(number)) @@ -88,6 +89,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/ua/edrpou.py b/stdnum/ua/edrpou.py index a286f53a..0ec19be1 100644 --- a/stdnum/ua/edrpou.py +++ b/stdnum/ua/edrpou.py @@ -43,19 +43,20 @@ >>> format(' 32855961 ') '32855961' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" return clean(number, ' ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for number.""" - weights = (1, 2, 3, 4, 5, 6, 7) + weights: tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7) if number[0] in '345': weights = (7, 1, 2, 3, 4, 5, 6) total = sum(w * int(n) for w, n in zip(weights, number)) @@ -67,7 +68,7 @@ def calc_check_digit(number): return str(total % 11 % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Ukraine EDRPOU (ЄДРПОУ) number. This checks the length, formatting and check digit. @@ -82,7 +83,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Ukraine EDRPOU (ЄДРПОУ) number.""" try: return bool(validate(number)) @@ -90,6 +91,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/ua/rntrc.py b/stdnum/ua/rntrc.py index 07b64de2..525bd07f 100644 --- a/stdnum/ua/rntrc.py +++ b/stdnum/ua/rntrc.py @@ -42,24 +42,25 @@ >>> format(' 25 30 41 40 71 ') '2530414071' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" return clean(number, ' ').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for number.""" weights = (-1, 5, 7, 9, 4, 6, 10, 5, 7) total = sum(w * int(n) for w, n in zip(weights, number)) return str((total % 11) % 10) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Ukraine RNTRC (РНОКПП) number. This checks the length, formatting and check digit. @@ -74,7 +75,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Ukraine RNTRC (РНОКПП) number.""" try: return bool(validate(number)) @@ -82,6 +83,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/stdnum/us/atin.py b/stdnum/us/atin.py index 4ff59535..71c2e963 100644 --- a/stdnum/us/atin.py +++ b/stdnum/us/atin.py @@ -34,6 +34,7 @@ >>> format('123') # unknown formatting is left alone '123' """ +from __future__ import annotations import re @@ -45,13 +46,13 @@ _atin_re = re.compile(r'^[0-9]{3}-?[0-9]{2}-?[0-9]{4}$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ATIN. This checks the length and formatting if it is present.""" match = _atin_re.search(clean(number, '').strip()) @@ -61,7 +62,7 @@ def validate(number): return compact(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ATIN.""" try: return bool(validate(number)) @@ -69,7 +70,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" if len(number) == 9: number = number[:3] + '-' + number[3:5] + '-' + number[5:] diff --git a/stdnum/us/ein.py b/stdnum/us/ein.py index 75d178c2..60e76648 100644 --- a/stdnum/us/ein.py +++ b/stdnum/us/ein.py @@ -41,6 +41,7 @@ >>> format('123') # unknown formatting is left alone '123' """ +from __future__ import annotations import re @@ -52,13 +53,13 @@ _ein_re = re.compile(r'^(?P[0-9]{2})-?(?P[0-9]{7})$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def get_campus(number): +def get_campus(number: str) -> str: """Determine the Campus or other location that issued the EIN.""" from stdnum import numdb number = compact(number) @@ -68,7 +69,7 @@ def get_campus(number): return results['campus'] -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid EIN. This checks the length, groups and formatting if it is present.""" match = _ein_re.search(clean(number, '').strip()) @@ -78,7 +79,7 @@ def validate(number): return compact(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid EIN.""" try: return bool(validate(number)) @@ -86,7 +87,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" if len(number) == 9: number = number[:2] + '-' + number[2:] diff --git a/stdnum/us/itin.py b/stdnum/us/itin.py index 0d1aa41d..63090cca 100644 --- a/stdnum/us/itin.py +++ b/stdnum/us/itin.py @@ -48,6 +48,7 @@ >>> format('123') # unknown formatting is left alone '123' """ +from __future__ import annotations import re @@ -63,13 +64,13 @@ _allowed_groups = set((str(x) for x in range(70, 100) if x not in (89, 93))) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid ITIN. This checks the length, groups and formatting if it is present.""" match = _itin_re.search(clean(number, '').strip()) @@ -82,7 +83,7 @@ def validate(number): return compact(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ITIN.""" try: return bool(validate(number)) @@ -90,7 +91,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" if len(number) == 9: number = number[:3] + '-' + number[3:5] + '-' + number[5:] diff --git a/stdnum/us/ptin.py b/stdnum/us/ptin.py index 7d60f6a3..932cb4b5 100644 --- a/stdnum/us/ptin.py +++ b/stdnum/us/ptin.py @@ -32,6 +32,7 @@ ... InvalidFormat: ... """ +from __future__ import annotations import re @@ -43,13 +44,13 @@ _ptin_re = re.compile(r'^P[0-9]{8}$') -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid PTIN. This checks the length, groups and formatting if it is present.""" number = compact(number).upper() @@ -59,7 +60,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid ATIN.""" try: return bool(validate(number)) diff --git a/stdnum/us/rtn.py b/stdnum/us/rtn.py index eb889266..e1e3af62 100644 --- a/stdnum/us/rtn.py +++ b/stdnum/us/rtn.py @@ -41,19 +41,20 @@ ... InvalidChecksum: .. """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any surrounding whitespace.""" number = clean(number).strip() return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit. The number passed should not have the check digit included.""" digits = [int(c) for c in number] @@ -65,7 +66,7 @@ def calc_check_digit(number): return str(checksum) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid routing number. This checks the length and check digit.""" number = compact(number) @@ -78,7 +79,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid RTN.""" try: return bool(validate(number)) diff --git a/stdnum/us/ssn.py b/stdnum/us/ssn.py index 78d538e6..f3350754 100644 --- a/stdnum/us/ssn.py +++ b/stdnum/us/ssn.py @@ -59,6 +59,7 @@ >>> format('111223333') '111-22-3333' """ +from __future__ import annotations import re @@ -74,13 +75,13 @@ _ssn_blacklist = set(('078-05-1120', '457-55-5462', '219-09-9999')) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid SSN. This checks the length, groups and formatting if it is present.""" match = _ssn_re.search(clean(number, '').strip()) @@ -100,7 +101,7 @@ def validate(number): return compact(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid SSN.""" try: return bool(validate(number)) @@ -108,7 +109,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" if len(number) == 9: number = number[:3] + '-' + number[3:5] + '-' + number[5:] diff --git a/stdnum/us/tin.py b/stdnum/us/tin.py index 72ccddcb..ee21af97 100644 --- a/stdnum/us/tin.py +++ b/stdnum/us/tin.py @@ -47,6 +47,7 @@ >>> format('123-456') # invalid numbers are not reformatted '123-456' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.us import atin, ein, itin, ptin, ssn @@ -56,25 +57,25 @@ _tin_modules = (ssn, itin, ein, ptin, atin) -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, '-').strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid TIN. This searches for the proper sub-type and validates using that.""" for mod in _tin_modules: try: - return mod.validate(number) + return mod.validate(number) # type: ignore[no-any-return] except ValidationError: pass # try next module # fallback raise InvalidFormat() -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid TIN.""" try: return bool(validate(number)) @@ -82,7 +83,7 @@ def is_valid(number): return False -def guess_type(number): +def guess_type(number: str) -> list[str]: """Return a list of possible TIN types for which this number is valid..""" return [mod.__name__.rsplit('.', 1)[-1] @@ -90,9 +91,9 @@ def guess_type(number): if mod.is_valid(number)] -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" for mod in _tin_modules: if mod.is_valid(number) and hasattr(mod, 'format'): - return mod.format(number) + return mod.format(number) # type: ignore[no-any-return] return number diff --git a/stdnum/util.py b/stdnum/util.py index 081c3bad..e2592b65 100644 --- a/stdnum/util.py +++ b/stdnum/util.py @@ -24,6 +24,7 @@ guaranteed to remain stable and as such not part of the public API of stdnum. """ +from __future__ import annotations import pkgutil import pydoc @@ -33,6 +34,7 @@ import unicodedata import warnings +from stdnum import _typing as t from stdnum.exceptions import * @@ -44,7 +46,7 @@ _digits_re = re.compile(r'^[0-9]+$') -def _mk_char_map(mapping): +def _mk_char_map(mapping: t.Mapping[str, str]) -> t.Generator[tuple[str, str]]: """Transform a dictionary with comma separated uniode character names to tuples with unicode characters as key.""" for key, value in mapping.items(): @@ -151,12 +153,12 @@ def _mk_char_map(mapping): })) -def _clean_chars(number): +def _clean_chars(number: str) -> str: """Replace various Unicode characters with their ASCII counterpart.""" return ''.join(_char_map.get(x, x) for x in number) -def clean(number, deletechars=''): +def clean(number: str, deletechars: str = '') -> str: """Remove the specified characters from the supplied number. >>> clean('123-456:78 9', ' -:') @@ -172,18 +174,16 @@ def clean(number, deletechars=''): return ''.join(x for x in number if x not in deletechars) -def isdigits(number): +def isdigits(number: str) -> bool: """Check whether the provided string only consists of digits.""" # This function is meant to replace str.isdigit() which will also return # True for all kind of unicode digits which is generally not what we want return bool(_digits_re.match(number)) -def to_unicode(text): +@t.deprecated('to_unicode() will be removed in an upcoming release') +def to_unicode(text: str | bytes) -> str: """DEPRECATED: Will be removed in an upcoming release.""" # noqa: D40 - warnings.warn( - 'to_unicode() will be removed in an upcoming release', - DeprecationWarning, stacklevel=2) if not isinstance(text, str): try: return text.decode('utf-8') @@ -192,7 +192,7 @@ def to_unicode(text): return text -def get_number_modules(base='stdnum'): +def get_number_modules(base: str = 'stdnum') -> t.Generator[t.NumberValidationModule]: """Yield all the number validation modules under the specified module.""" __import__(base) module = sys.modules[base] @@ -201,25 +201,31 @@ def get_number_modules(base='stdnum'): warnings.filterwarnings('ignore', category=DeprecationWarning, module=r'stdnum\..*') for _loader, name, _is_pkg in pkgutil.walk_packages( module.__path__, module.__name__ + '.'): + + # we don't want the runtime overhead of loading this module + # additionally it would put a hard requirement on typing_extensions + if name == 'stdnum._types': + continue + __import__(name) module = sys.modules[name] if hasattr(module, 'validate') and module.__name__ == name: yield module -def get_module_name(module): +def get_module_name(module: object) -> str: """Return the short description of the number.""" return pydoc.splitdoc(pydoc.getdoc(module))[0].strip('.') -def get_module_description(module): +def get_module_description(module: object) -> str: """Return a description of the number.""" doc = pydoc.splitdoc(pydoc.getdoc(module))[1] # remove the doctests return _strip_doctest_re.sub('', doc).strip() -def get_cc_module(cc, name): +def get_cc_module(cc: str, name: str) -> t.NumberValidationModule | None: """Find the country-specific named module.""" cc = cc.lower() # add suffix for python reserved words @@ -229,32 +235,41 @@ def get_cc_module(cc, name): mod = __import__('stdnum.%s' % cc, globals(), locals(), [name]) return getattr(mod, name, None) except ImportError: - return + return None # this is a cache of SOAP clients _soap_clients = {} -def _get_zeep_soap_client(wsdlurl, timeout, verify): # pragma: no cover (not part of normal test suite) +def _get_zeep_soap_client( + wsdlurl: str, + timeout: float, + verify: bool | str, +) -> t.Any: # pragma: no cover (not part of normal test suite) from requests import Session from zeep import CachingClient from zeep.transports import Transport session = Session() session.verify = verify - transport = Transport(operation_timeout=timeout, timeout=timeout, session=session) - return CachingClient(wsdlurl, transport=transport).service + transport = Transport(operation_timeout=timeout, timeout=timeout, session=session) # type: ignore[no-untyped-call] + return CachingClient(wsdlurl, transport=transport).service # type: ignore[no-untyped-call] -def _get_suds_soap_client(wsdlurl, timeout, verify): # pragma: no cover (not part of normal test suite) +def _get_suds_soap_client( + wsdlurl: str, + timeout: float, + verify: bool | str, +) -> t.Any: # pragma: no cover (not part of normal test suite) + import os.path from urllib.request import HTTPSHandler, getproxies - from suds.client import Client - from suds.transport.http import HttpTransport + from suds.client import Client # type: ignore + from suds.transport.http import HttpTransport # type: ignore - class CustomSudsTransport(HttpTransport): + class CustomSudsTransport(HttpTransport): # type: ignore[misc] - def u2handlers(self): + def u2handlers(self): # type: ignore[no-untyped-def] handlers = super(CustomSudsTransport, self).u2handlers() if isinstance(verify, str): if not os.path.isdir(verify): @@ -274,8 +289,14 @@ def u2handlers(self): return Client(wsdlurl, proxy=getproxies(), timeout=timeout, transport=CustomSudsTransport()).service -def _get_pysimplesoap_soap_client(wsdlurl, timeout, verify): # pragma: no cover (not part of normal test suite) - from pysimplesoap.client import SoapClient +def _get_pysimplesoap_soap_client( + wsdlurl: str, + timeout: float, + verify: bool | str, +) -> t.Any: # pragma: no cover (not part of normal test suite) + from urllib.request import getproxies + + from pysimplesoap.client import SoapClient # type: ignore if verify is False: raise ValueError('PySimpleSOAP does not support verify=False') kwargs = {} @@ -287,7 +308,11 @@ def _get_pysimplesoap_soap_client(wsdlurl, timeout, verify): # pragma: no cover return SoapClient(wsdl=wsdlurl, proxy=getproxies(), timeout=timeout, **kwargs) -def get_soap_client(wsdlurl, timeout=30, verify=True): # pragma: no cover (not part of normal test suite) +def get_soap_client( + wsdlurl: str, + timeout: float = 30, + verify: bool = True, +) -> t.Any: # pragma: no cover (not part of normal test suite) """Get a SOAP client for performing requests. The client is cached. The timeout is in seconds. The verify parameter is either True (the default), False (to disabled certificate validation) or string value pointing to a CA certificate diff --git a/stdnum/uy/__init__.py b/stdnum/uy/__init__.py index 3cfea915..0b70bb58 100644 --- a/stdnum/uy/__init__.py +++ b/stdnum/uy/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Uruguayan numbers.""" +from __future__ import annotations # provide aliases from stdnum.uy import rut as vat # noqa: F401 diff --git a/stdnum/uy/rut.py b/stdnum/uy/rut.py index 769578da..87dbfe21 100644 --- a/stdnum/uy/rut.py +++ b/stdnum/uy/rut.py @@ -47,6 +47,7 @@ >>> format('211003420017') '21-100342-001-7' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits @@ -58,7 +59,7 @@ # https://servicios.dgi.gub.uy/ServiciosEnLinea/ampliar/servicios-automatizados -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -70,14 +71,14 @@ def compact(number): return number -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2) total = sum(int(n) * w for w, n in zip(weights, number)) return str(-total % 11) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Uruguay RUT number. This checks the length, formatting and check digit. @@ -98,7 +99,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Uruguay RUT number.""" try: return bool(validate(number)) @@ -106,7 +107,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return '-'.join([number[:2], number[2:-4], number[-4:-1], number[-1]]) diff --git a/stdnum/vatin.py b/stdnum/vatin.py index f7ee12b5..3c25d424 100644 --- a/stdnum/vatin.py +++ b/stdnum/vatin.py @@ -23,7 +23,7 @@ The number VAT identification number (VATIN) is an identifier used in many countries. It starts with an ISO 3166-1 alpha-2 (2 letters) country code (except for Greece, which uses EL, instead of GR) and is followed by the -country-specific the identifier. +country-specific identifier. This module supports all VAT numbers that are supported in python-stdnum. @@ -46,9 +46,11 @@ ... InvalidComponent: ... """ +from __future__ import annotations import re +from stdnum import _typing as t from stdnum.exceptions import * from stdnum.util import clean, get_cc_module @@ -57,7 +59,7 @@ _country_modules = dict() -def _get_cc_module(cc): +def _get_cc_module(cc: str) -> t.NumberValidationModule: """Get the VAT number module based on the country code.""" # Greece uses a "wrong" country code, special case for Northern Ireland cc = cc.lower().replace('el', 'gr').replace('xi', 'gb') @@ -65,12 +67,14 @@ def _get_cc_module(cc): raise InvalidFormat() if cc not in _country_modules: _country_modules[cc] = get_cc_module(cc, 'vat') - if not _country_modules[cc]: + + module = _country_modules[cc] + if module is None: raise InvalidComponent() # unknown/unsupported country code - return _country_modules[cc] + return module -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation.""" number = clean(number).strip() module = _get_cc_module(number[:2]) @@ -80,7 +84,7 @@ def compact(number): return module.compact(number) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid VAT number. This performs the country-specific check for the number. @@ -93,7 +97,7 @@ def validate(number): return module.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid VAT number.""" try: return bool(validate(number)) diff --git a/stdnum/ve/__init__.py b/stdnum/ve/__init__.py index 83223b3a..8d02ba2b 100644 --- a/stdnum/ve/__init__.py +++ b/stdnum/ve/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Venezuelan numbers.""" +from __future__ import annotations # provide aliases from stdnum.ve import rif as vat # noqa: F401 diff --git a/stdnum/ve/rif.py b/stdnum/ve/rif.py index 2ed3c9f3..cda61774 100644 --- a/stdnum/ve/rif.py +++ b/stdnum/ve/rif.py @@ -32,12 +32,13 @@ ... InvalidChecksum: ... """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding whitespace.""" return clean(number, ' -').upper().strip() @@ -54,7 +55,7 @@ def compact(number): } -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit for the RIF.""" number = compact(number) weights = (3, 2, 7, 6, 5, 4, 3, 2) @@ -63,7 +64,7 @@ def calc_check_digit(number): return '00987654321'[c % 11] -def validate(number): +def validate(number: str) -> str: """Check if the number provided is a valid RIF. This checks the length, formatting and check digit.""" number = compact(number) @@ -78,7 +79,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided is a valid RIF. This checks the length, formatting and check digit.""" try: diff --git a/stdnum/verhoeff.py b/stdnum/verhoeff.py index b6b0f202..8a75a13f 100644 --- a/stdnum/verhoeff.py +++ b/stdnum/verhoeff.py @@ -44,6 +44,7 @@ >>> validate('12340') '12340' """ +from __future__ import annotations from stdnum.exceptions import * @@ -74,19 +75,16 @@ (7, 0, 4, 6, 9, 1, 3, 2, 5, 8)) -def checksum(number): +def checksum(number: str) -> int: """Calculate the Verhoeff checksum over the provided number. The checksum is returned as an int. Valid numbers should have a checksum of 0.""" - # transform number list - number = tuple(int(n) for n in reversed(str(number))) - # calculate checksum check = 0 - for i, n in enumerate(number): + for i, n in enumerate(int(n) for n in reversed(str(number))): check = _multiplication_table[check][_permutation_table[i % 8][n]] return check -def validate(number): +def validate(number: str) -> str: """Check if the number provided passes the Verhoeff checksum.""" if not bool(number): raise InvalidFormat() @@ -99,7 +97,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number provided passes the Verhoeff checksum.""" try: return bool(validate(number)) @@ -107,7 +105,7 @@ def is_valid(number): return False -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the extra digit that should be appended to the number to make it a valid number.""" return str(_multiplication_table[checksum(str(number) + '0')].index(0)) diff --git a/stdnum/vn/__init__.py b/stdnum/vn/__init__.py index 62552bce..4ff5bb1a 100644 --- a/stdnum/vn/__init__.py +++ b/stdnum/vn/__init__.py @@ -19,6 +19,7 @@ # 02110-1301 USA """Collection of Vietnam numbers.""" +from __future__ import annotations # provide aliases from stdnum.vn import mst as vat # noqa: F401 diff --git a/stdnum/vn/mst.py b/stdnum/vn/mst.py index 7e372cfe..33d73f92 100644 --- a/stdnum/vn/mst.py +++ b/stdnum/vn/mst.py @@ -60,12 +60,13 @@ >>> format('0312 68 78 78 - 001') '0312687878-001' """ +from __future__ import annotations from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -74,14 +75,14 @@ def compact(number): return clean(number, ' -.').strip() -def calc_check_digit(number): +def calc_check_digit(number: str) -> str: """Calculate the check digit.""" weights = (31, 29, 23, 19, 17, 13, 7, 5, 3) total = sum(w * int(n) for w, n in zip(weights, number)) return str(10 - (total % 11)) -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid Vietnam MST number. This checks the length, formatting and check digit. @@ -100,7 +101,7 @@ def validate(number): return number -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid Vietnam MST number.""" try: return bool(validate(number)) @@ -108,7 +109,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return number if len(number) == 10 else '-'.join([number[:10], number[10:]]) diff --git a/stdnum/za/idnr.py b/stdnum/za/idnr.py index 7a8ced16..f51233ef 100644 --- a/stdnum/za/idnr.py +++ b/stdnum/za/idnr.py @@ -48,15 +48,17 @@ >>> format('750330 5044089') '750330 5044 08 9' """ +from __future__ import annotations import datetime +from stdnum import _typing as t from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -65,7 +67,7 @@ def compact(number): return clean(number, ' ') -def get_birth_date(number): +def get_birth_date(number: str) -> datetime.date: """Split the date parts from the number and return the date of birth. Since the number only uses two digits for the year, the century may be @@ -84,7 +86,7 @@ def get_birth_date(number): raise InvalidComponent() -def get_gender(number): +def get_gender(number: str) -> t.Literal['M', 'F']: """Get the gender (M/F) from the person's ID number.""" number = compact(number) if number[6] in '01234': @@ -93,7 +95,7 @@ def get_gender(number): return 'M' -def get_citizenship(number): +def get_citizenship(number: str) -> t.Literal['citizen', 'resident']: """Get the citizenship status (citizen/resident) from the ID number.""" number = compact(number) if number[10] == '0': @@ -104,7 +106,7 @@ def get_citizenship(number): raise InvalidComponent() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid South African ID number. This checks the length, formatting and check digit. @@ -119,7 +121,7 @@ def validate(number): return luhn.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid South African ID number.""" try: return bool(validate(number)) @@ -127,7 +129,7 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" number = compact(number) return ' '.join((number[:6], number[6:10], number[10:12], number[12:])) diff --git a/stdnum/za/tin.py b/stdnum/za/tin.py index 9ec400c7..eeb97dec 100644 --- a/stdnum/za/tin.py +++ b/stdnum/za/tin.py @@ -42,13 +42,14 @@ >>> format('084308984-8') '0843089848' """ # noqa: E501 +from __future__ import annotations from stdnum import luhn from stdnum.exceptions import * from stdnum.util import clean, isdigits -def compact(number): +def compact(number: str) -> str: """Convert the number to the minimal representation. This strips the number of any valid separators and removes surrounding @@ -57,7 +58,7 @@ def compact(number): return clean(number, ' -/').upper().strip() -def validate(number): +def validate(number: str) -> str: """Check if the number is a valid South Africa Tax Reference Number. This checks the length, formatting and check digit. @@ -72,7 +73,7 @@ def validate(number): return luhn.validate(number) -def is_valid(number): +def is_valid(number: str) -> bool: """Check if the number is a valid South Africa Tax Reference Number.""" try: return bool(validate(number)) @@ -80,6 +81,6 @@ def is_valid(number): return False -def format(number): +def format(number: str) -> str: """Reformat the number to the standard presentation format.""" return compact(number) diff --git a/tox.ini b/tox.ini index 4c59f43f..8841171a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{36,37,38,39,310,311,312,313,py3},flake8,docs,headers +envlist = py{37,38,39,310,311,312,313,py3},flake8,docs,headers skip_missing_interpreters = true [testenv] @@ -30,6 +30,18 @@ commands = flake8 . setenv= PYTHONWARNINGS=ignore +[testenv:mypy] +skip_install = true +deps = mypy + types-requests + zeep +commands = + mypy -p stdnum --python-version 3.9 + mypy -p stdnum --python-version 3.10 + mypy -p stdnum --python-version 3.11 + mypy -p stdnum --python-version 3.12 + mypy -p stdnum --python-version 3.13 + [testenv:docs] use_develop = true deps = Sphinx