Esempio n. 1
0
    def _parse_upc_e(cls, value: str) -> Upc:
        length = len(value)
        assert length in (6, 7, 8)

        if length == 6:
            # Implicit number system 0, no check digit.
            number_system_digit = 0
            payload = f"{number_system_digit}{value}"
            upc_a_payload = _upc_e_to_upc_a_expansion(f"{payload}0")[:-1]
            check_digit = numeric_check_digit(upc_a_payload)
        elif length == 7:
            # Explicit number system, no check digit.
            number_system_digit = int(value[0])
            payload = value
            upc_a_payload = _upc_e_to_upc_a_expansion(f"{payload}0")[:-1]
            check_digit = numeric_check_digit(upc_a_payload)
        elif length == 8:
            # Explicit number system and check digit.
            number_system_digit = int(value[0])
            payload = value[:-1]
            check_digit = int(value[-1])
        else:
            raise Exception(  # pragma: no cover
                "Unhandled UPC-E length. This is a bug."
            )

        # Control that the number system digit is correct.
        if number_system_digit not in (0, 1):
            raise ParseError(
                f"Invalid UPC-E number system for {value!r}: "
                f"Expected 0 or 1, got {number_system_digit!r}."
            )

        # Control that check digit is correct.
        upc_a_payload = _upc_e_to_upc_a_expansion(f"{payload}{check_digit}")[:-1]
        calculated_check_digit = numeric_check_digit(upc_a_payload)
        if check_digit != calculated_check_digit:
            raise ParseError(
                f"Invalid UPC-E check digit for {value!r}: "
                f"Expected {calculated_check_digit!r}, got {check_digit!r}."
            )

        return cls(
            value=value,
            format=UpcFormat.UPC_E,
            payload=payload,
            number_system_digit=number_system_digit,
            check_digit=check_digit,
        )
Esempio n. 2
0
File: _rcn.py Progetto: ilayshp/biip
    def without_variable_measure(self: Rcn) -> Gtin:
        """Create a new RCN where the variable measure is zeroed out.

        This provides us with a number which still includes the item
        reference, but does not vary with weight/price, and can thus be used
        to lookup the relevant trade item in a database or similar.

        This has no effect on RCNs intended for use within a company, as
        the semantics of those numbers vary from company to company.

        Returns:
            A new RCN instance with zeros in the variable measure places.

        Raises:
            EncodeError: If the rules for variable measures in the region are unknown.
        """
        if self.usage == RcnUsage.COMPANY:
            return self

        if self.region in (
                RcnRegion.BALTICS,
                RcnRegion.NORWAY,
                RcnRegion.SWEDEN,
        ):
            measure = "0000"
            payload = f"{self.value[:-5]}{measure}"
            check_digit = checksums.numeric_check_digit(payload)
            value = f"{payload}{check_digit}"
            return Gtin.parse(value, rcn_region=self.region)
        elif self.region in (RcnRegion.GREAT_BRITAIN, ):
            measure = "0000"
            price_check_digit = checksums.price_check_digit(measure)
            payload = f"{self.value[:-6]}{price_check_digit}{measure}"
            check_digit = checksums.numeric_check_digit(payload)
            value = f"{payload}{check_digit}"
            return Gtin.parse(value, rcn_region=self.region)
        else:
            raise EncodeError(
                f"Cannot zero out the variable measure part of {self.value!r} as the "
                f"RCN rules for the geographical region {self.region!r} are unknown."
            )
Esempio n. 3
0
    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,
        )
Esempio n. 4
0
File: _rcn.py Progetto: jodal/biip
    def without_variable_measure(self, rcn: Rcn) -> Gtin:
        # Zero out the variable measure part of the payload, and recalculate both
        # the GTIN check digit and the variable measure's check digit digit, if any.

        rcn_13 = rcn.as_gtin_13()
        zeroed_value = "0" * len(rcn_13[self.value_slice])

        digits = list(self.pattern)
        digits[self.prefix_slice] = list(rcn_13[self.prefix_slice])
        digits[self.value_slice] = list(zeroed_value)
        if self.check_digit_slice is not None:
            digits[self.check_digit_slice] = [
                str(checksums.price_check_digit(zeroed_value))
            ]

        gtin_payload = "".join(digits)
        gtin_check_digit = checksums.numeric_check_digit(gtin_payload)
        gtin = f"{gtin_payload}{gtin_check_digit}"
        return Gtin.parse(gtin, rcn_region=rcn.region)
Esempio n. 5
0
    def _parse_upc_a(cls, value: str) -> Upc:
        assert len(value) == 12

        payload = value[:-1]
        number_system_digit = int(value[0])
        check_digit = int(value[-1])

        calculated_check_digit = numeric_check_digit(payload)
        if check_digit != calculated_check_digit:
            raise ParseError(
                f"Invalid UPC-A check digit for {value!r}: "
                f"Expected {calculated_check_digit!r}, got {check_digit!r}."
            )

        return cls(
            value=value,
            format=UpcFormat.UPC_A,
            payload=payload,
            number_system_digit=number_system_digit,
            check_digit=check_digit,
        )
Esempio n. 6
0
def test_numeric_check_digit_with_nonnumeric_value(value: str) -> None:
    with pytest.raises(ValueError) as exc_info:
        numeric_check_digit(value)

    assert str(exc_info.value) == f"Expected numeric value, got {value!r}."
Esempio n. 7
0
def test_numeric_check_digit(value: str, expected: int) -> None:
    assert numeric_check_digit(value) == expected
Esempio n. 8
0
    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