"""Spanish tax-identifier validation (NIF / NIE / CIF) returning canonical strings.The Agencia Tributaria's identifier algorithm is shared infrastructureacross multiple subpackages — invoice counterparty checks, encryptedmaster-key NIF canaries, sanitiser fixture validation, and CLI preflightgates. Co-locating the algorithm in :mod:`core.identity` givesevery caller a public, layer-respecting import path.This module differs from :mod:`core.identity._documents` only in its returnshape: :func:`validate_spanish_tax_id` yields the normalised identifier string,while :func:`~core.identity.validate_identity` returns the matching:class:`~core.identity.IdentityDocument` enum member. Both surfaces raise:class:`~core.identity.IdentityError`; this module also exports:func:`nif_check_letter` for callers that need the shared NIF/NIE checksumtable directly."""from__future__importannotationsfrom._documentsimport(_CIF_KIND_LETTERS,_CIF_LETTER_TABLE,IdentityError,_cif_check_value,nif_check_letter,)_NIE_LEADERS={"X":"0","Y":"1","Z":"2"}_PREFIXED_NIF_LEADERS={"K","L","M"}_CIF_LEADERS=_CIF_KIND_LETTERS_CIF_LETTER_CONTROL_LEADERS=set("PQRSNW")
[docs]defvalidate_spanish_tax_id(value:str)->str:"""Validate a Spanish NIF, NIE, or CIF and return its canonical form. Implements the Agencia Tributaria algorithm: * **NIF** — 8 digits, or current ``K``/``L``/``M`` plus 7 digits for natural persons without DNI/NIE, followed by a checksum letter drawn from ``TRWAGMYFPDXBNJZSQVHLCKE`` indexed by ``number % 23``. * **NIE** — a leading ``X``/``Y``/``Z`` substituted with ``0``/``1``/``2`` before applying the NIF rule. * **CIF** — a leading letter from ``ABCDEFGHJNPQRSUVW``, 7 digits, and a 1-character control. Leading letters in ``PQRSNW`` require a **letter** control drawn from ``JABCDEFGHI``; leading letters in ``ABEH`` require a **digit** control; all other leaders accept either form (both historically in circulation). Args: value: Raw tax identifier to validate. Returns: The uppercased, whitespace-trimmed identifier. Raises: IdentityError: If the identifier is malformed or the checksum fails. """normalized=value.strip().upper().replace(" ","").replace("-","").replace(".","")ifnotnormalized:raiseIdentityError("tax identifier must not be blank")iflen(normalized)==11andnormalized.startswith("ES"):normalized=normalized[2:]iflen(normalized)!=9:raiseIdentityError("tax identifier must be 9 characters long")leader=normalized[0]ifleader.isdigit():return_validate_nif(normalized)ifleaderin_PREFIXED_NIF_LEADERS:return_validate_prefixed_nif(normalized)ifleaderin_NIE_LEADERS:return_validate_nie(normalized)ifleaderin_CIF_LEADERS:return_validate_cif(normalized)raiseIdentityError("tax identifier has an unrecognised leading character")
def_validate_nif(value:str)->str:"""Validate a normalised NIF, returning the input or raising :exc:`ValueError`."""digits=value[:8]control=value[8]ifnotdigits.isdigit()ornotcontrol.isalpha():raiseIdentityError("NIF must be 8 digits followed by a checksum letter")expected=nif_check_letter(int(digits))ifcontrol!=expected:raiseIdentityError("NIF checksum letter is invalid")returnvaluedef_validate_prefixed_nif(value:str)->str:"""Validate a K/L/M-prefixed natural-person NIF."""body=value[1:8]control=value[8]ifnotbody.isdigit()ornotcontrol.isalpha():raiseIdentityError("prefixed NIF must be a leading K/L/M plus 7 digits and a checksum letter")expected=nif_check_letter(int(body))ifcontrol!=expected:raiseIdentityError("prefixed NIF checksum letter is invalid")returnvaluedef_validate_nie(value:str)->str:"""Validate a normalised NIE, returning the input or raising :exc:`ValueError`."""leader=value[0]body=value[1:8]control=value[8]ifnotbody.isdigit()ornotcontrol.isalpha():raiseIdentityError("NIE must be a leading X/Y/Z plus 7 digits and a checksum letter")substituted=_NIE_LEADERS[leader]+bodyexpected=nif_check_letter(int(substituted))ifcontrol!=expected:raiseIdentityError("NIE checksum letter is invalid")returnvaluedef_validate_cif(value:str)->str:"""Validate a normalised CIF, returning the input or raising :exc:`ValueError`."""leader=value[0]digits=value[1:8]control=value[8]ifnotdigits.isdigit():raiseIdentityError("CIF body must be 7 digits")digit_control=_cif_check_value(digits)letter_control=_CIF_LETTER_TABLE[digit_control]ifleaderin_CIF_LETTER_CONTROL_LEADERS:ifnotcontrol.isalpha()orcontrol!=letter_control:raiseIdentityError("CIF letter-control checksum is invalid")else:ifcontrol.isdigit():ifint(control)!=digit_control:raiseIdentityError("CIF digit-control checksum is invalid")elifcontrol.isalpha():ifcontrol!=letter_control:raiseIdentityError("CIF letter-control checksum is invalid")else:raiseIdentityError("CIF control character must be a digit or uppercase letter")returnvalue__all__=["nif_check_letter","validate_spanish_tax_id"]