Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import List
from .validation_gs1_check_digit import is_gs1_check_digit_valid ← from validation.gs1-check-digit ^1.0.0 · built alongside by fune
from .validation_gtin_types import GtinCheck
#: The four GTIN lengths, and what each is called.
_KINDS = {8: "ean-8", 12: "upc-a", 13: "ean-13", 14: "gtin-14"}
def _invalid(reason: str) -> GtinCheck:
return GtinCheck(valid=False, normalised=None, kind=None, gtin14=None, reason=reason)
def validate_gtin(value: str) -> GtinCheck:
"""Check an EAN-8, UPC-A, EAN-13 or GTIN-14 against the GS1 mod-10 check digit.
A pass means the number was typed correctly, not that GS1 issued it.
Validators answer rather than raise: a malformed value is the answer "no".
"""
if not isinstance(value, str):
return _invalid("empty")
# Barcodes are printed in groups, so spaces and hyphens are ignored anywhere.
chars: List[str] = [ch for ch in value if ch != " " and ch != "-"]
if not chars:
return _invalid("empty")
# Compare against ASCII explicitly: str.isdigit() accepts Arabic-Indic and
# superscript digits, which the other two languages would reject.
if any(ch < "0" or ch > "9" for ch in chars):
return _invalid("bad-character")
digits = "".join(chars)
kind = _KINDS.get(len(digits))
if kind is None:
return _invalid("bad-length")
# Zero everywhere satisfies the arithmetic and has never been allocated.
if all(ch == "0" for ch in digits):
return _invalid("all-zero")
# The GS1 mod-10 check, weighted from the right, which is what makes one
# rule serve all four lengths (EAN-13's first digit gets 1, EAN-8's and
# UPC-A's get 3).
if not is_gs1_check_digit_valid(digits):
return _invalid("bad-check-digit")
return GtinCheck(valid=True, normalised=digits, kind=kind, gtin14=digits.rjust(14, "0"), reason=None)