def parse(cls: Type[Sscc], value: str) -> Sscc: """Parse the given value into a :class:`Sscc` object. Args: value: The value to parse. Returns: SSCC data structure with the successfully extracted data. The checksum is guaranteed to be valid if an SSCC object is returned. Raises: ParseError: If the parsing fails. """ value = value.strip() if len(value) != 18: raise ParseError(f"Failed to parse {value!r} as SSCC: " f"Expected 18 digits, got {len(value)}.") if not value.isnumeric(): raise ParseError( f"Failed to parse {value!r} as SSCC: Expected a numerical value." ) value_without_extension_digit = value[1:] prefix = GS1Prefix.extract(value_without_extension_digit) extension_digit = int(value[0]) payload = value[:-1] check_digit = int(value[-1]) calculated_check_digit = numeric_check_digit(payload) if check_digit != calculated_check_digit: raise ParseError( f"Invalid SSCC check digit for {value!r}: " f"Expected {calculated_check_digit!r}, got {check_digit!r}.") return cls( value=value, prefix=prefix, extension_digit=extension_digit, payload=payload, check_digit=check_digit, )
def parse( cls: Type[Gtin], value: str, *, rcn_region: Optional[RcnRegion] = None ) -> Gtin: """Parse the given value into a :class:`Gtin` object. Both GTIN-8, GTIN-12, GTIN-13, and GTIN-14 are supported. Args: value: The value to parse. rcn_region: The geographical region whose rules should be used to interpret Restricted Circulation Numbers (RCN). Needed to extract e.g. variable weight/price from GTIN. Returns: GTIN data structure with the successfully extracted data. The checksum is guaranteed to be valid if a GTIN object is returned. Raises: ParseError: If the parsing fails. """ from biip.gtin import Rcn value = value.strip() if len(value) not in (8, 12, 13, 14): raise ParseError( f"Failed to parse {value!r} as GTIN: " f"Expected 8, 12, 13, or 14 digits, got {len(value)}." ) if not value.isnumeric(): raise ParseError( f"Failed to parse {value!r} as GTIN: Expected a numerical value." ) stripped_value = _strip_leading_zeros(value) assert len(stripped_value) in (8, 12, 13, 14) num_significant_digits = len(stripped_value) gtin_format = GtinFormat(num_significant_digits) payload = stripped_value[:-1] check_digit = int(stripped_value[-1]) packaging_level: Optional[int] = None if gtin_format == GtinFormat.GTIN_14: packaging_level = int(stripped_value[0]) value_without_packaging_level = stripped_value[1:] prefix = GS1Prefix.extract(value_without_packaging_level) elif gtin_format == GtinFormat.GTIN_12: # Add a zero to convert U.P.C. Company Prefix to GS1 Company Prefix prefix = GS1Prefix.extract(stripped_value.zfill(13)) elif gtin_format == GtinFormat.GTIN_8: prefix = GS1Prefix.extract(stripped_value.zfill(12)) else: prefix = GS1Prefix.extract(stripped_value) calculated_check_digit = numeric_check_digit(payload) if check_digit != calculated_check_digit: raise ParseError( f"Invalid GTIN check digit for {value!r}: " f"Expected {calculated_check_digit!r}, got {check_digit!r}." ) gtin_type: Type[Union[Gtin, Rcn]] if "Restricted Circulation Number" in prefix.usage: gtin_type = Rcn else: gtin_type = Gtin gtin = gtin_type( value=value, format=gtin_format, prefix=prefix, payload=payload, check_digit=check_digit, packaging_level=packaging_level, ) if isinstance(gtin, Rcn) and rcn_region is not None: gtin._parse_with_regional_rules(rcn_region) return gtin
def test_is_hashable() -> None: prefix = GS1Prefix.extract("978") assert hash(prefix) is not None
def test_invalid_gs1_prefix(bad_value: str) -> None: with pytest.raises(ParseError) as exc_info: GS1Prefix.extract(bad_value) assert str(exc_info.value) == f"Failed to get GS1 Prefix from {bad_value!r}."
def test_gs1_prefix(value: str, expected: GS1Prefix) -> None: assert GS1Prefix.extract(value) == expected