validation.uk-company-number@2.0.1
impl/python.py
2,639 bytes · the Python implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .validation_uk_company_number_data import COMPANY_PREFIXES ← this capability’s own data, compiled from data/company-prefixes.json into the same file by fune build
from .validation_uk_company_number_types import UkCompanyNumber
#: Every company number is eight characters: eight digits, or a two-character
#: prefix and six digits.
_LENGTH = 8
def _invalid(reason: str) -> UkCompanyNumber:
return UkCompanyNumber(valid=False, normalised=None, prefix=None, jurisdiction=None, company_type=None, reason=reason)
def _is_letter(ch: str) -> bool:
return "A" <= ch <= "Z"
def _is_digit(ch: str) -> bool:
return "0" <= ch <= "9"
def validate_uk_company_number(value: str) -> UkCompanyNumber:
"""Check a Companies House company number's shape and prefix.
There is no check digit, so a pass means the number is shaped like one
Companies House issues under a prefix it uses, not that the company exists:
that is a lookup on the register. Checks run in a fixed order (characters,
shape, length, prefix) so every language gives the same first reason.
"""
if not isinstance(value, str):
return _invalid("empty")
compact = ""
for ch in value:
if ch == " ":
continue
# ASCII-only upper-casing, so every language folds identically.
if "a" <= ch <= "z":
ch = chr(ord(ch) - 32)
if not _is_letter(ch) and not _is_digit(ch):
return _invalid("bad-character")
compact += ch
if compact == "":
return _invalid("empty")
# R0 (pre-partition Northern Ireland) is the one prefix containing a digit.
if compact.startswith("R0") or (len(compact) >= 2 and _is_letter(compact[0]) and _is_letter(compact[1])):
prefix = compact[0:2]
elif _is_letter(compact[0]):
return _invalid("bad-format")
else:
prefix = ""
digits = compact[len(prefix):]
if not all(_is_digit(ch) for ch in digits):
return _invalid("bad-format")
width = _LENGTH - len(prefix)
if len(digits) == 0 or len(digits) > width:
return _invalid("bad-length")
# Leading zeros are part of the number and are often dropped when typed,
# so they are put back; a number of nothing but zeros is no company.
if all(ch == "0" for ch in digits):
return _invalid("bad-format")
for row in COMPANY_PREFIXES:
if row.prefix == prefix:
return UkCompanyNumber(
valid=True,
normalised=prefix + digits.rjust(width, "0"),
prefix=prefix,
jurisdiction=row.jurisdiction,
company_type=row.company_type,
reason=None,
)
return _invalid("unknown-prefix")