def test_parse_with_too_long_separator_char_fails() -> None: with pytest.raises(ValueError) as exc_info: GS1Message.parse("10222--15210526", separator_chars=["--"]) assert ( str(exc_info.value) == "All separator characters must be exactly 1 character long, got ['--']." )
def test_parse_fails_if_unparsed_data_left() -> None: # 10 = AI for BATCH/LOT # 222... = Max length BATCH/LOT # aaa = Superflous data value = "1022222222222222222222aaa" with pytest.raises(ParseError) as exc_info: GS1Message.parse(value) assert str(exc_info.value ) == "Failed to get GS1 Application Identifier from 'aaa'."
def test_parse_fails_if_fixed_length_field_ends_with_separator_char() -> None: # 15... is a fixed length date. # \1xd is the default separator character in an illegal position. # 10... is any other field. value = "15210526\x1d100329" with pytest.raises(ParseError) as exc_info: GS1Message.parse(value) assert str(exc_info.value) == ( r"Element String '(15)210526' has fixed length and " r"should not end with a separator character. " r"Separator character '\x1d' found in '15210526\x1d100329'.")
def _parse_gs1_message( value: str, *, config: ParseConfig, queue: ParseQueue, result: ParseResult, ) -> None: if result.gs1_message is not None: return # pragma: no cover try: result.gs1_message = GS1Message.parse( value, rcn_region=config.rcn_region, separator_chars=config.separator_chars, ) result.gs1_message_error = None except ParseError as exc: result.gs1_message = None result.gs1_message_error = str(exc) else: # If the GS1 Message contains an SSCC, set SSCC on the top-level result. ai_00 = result.gs1_message.get(ai="00") if ai_00 is not None and ai_00.sscc is not None: queue.append((_parse_sscc, ai_00.sscc.value)) # If the GS1 Message contains an GTIN, set GTIN on the top-level result. ai_01 = result.gs1_message.get(ai="01") if ai_01 is not None and ai_01.gtin is not None: queue.append((_parse_gtin, ai_01.gtin.value))
def test_parse_with_separator_char( value: str, separator_chars: Iterable[str], expected_hri: str ) -> None: assert ( GS1Message.parse(value, separator_chars=separator_chars).as_hri() == expected_hri )
def test_filter_element_strings_by_ai_instance() -> None: ai = GS1ApplicationIdentifier.extract("01") msg = GS1Message.parse("010703206980498815210526100329") element_string = msg.get(ai=ai) assert element_string is not None assert element_string.value == "07032069804988"
def test_get_element_string_by_ai(value: str, ai: str, expected: str) -> None: element_string = GS1Message.parse(value).get(ai=ai) if expected is None: assert element_string is None else: assert element_string is not None assert element_string.value == expected
def test_get_element_string_by_data_title(value: str, data_title: str, expected: str) -> None: element_string = GS1Message.parse(value).get(data_title=data_title) if expected is None: assert element_string is None else: assert element_string is not None assert element_string.value == expected
def test_parse(value: str, expected: GS1Message) -> None: assert GS1Message.parse(value) == expected
def test_filter_element_strings_by_data_title(value: str, data_title: str, expected: List[str]) -> None: matches = GS1Message.parse(value).filter(data_title=data_title) assert [element_string.value for element_string in matches] == expected
def test_filter_element_strings_by_ai(value: str, ai: str, expected: List[str]) -> None: matches = GS1Message.parse(value).filter(ai=ai) assert [element_string.value for element_string in matches] == expected
def test_as_hri(value: str, expected: str) -> None: assert GS1Message.parse(value).as_hri() == expected
def test_parse_strips_surrounding_whitespace() -> None: message = GS1Message.parse(" \t 800370713240010220085952 \n ") assert message.value == "800370713240010220085952"
GS1Message( value="010703206980498815210526100329", element_strings=[ GS1ElementString( ai=GS1ApplicationIdentifier( ai="01", description="Global Trade Item Number (GTIN)", data_title="GTIN", fnc1_required=False, format="N2+N14", pattern="^01(\\d{14})$", ), value="07032069804988", pattern_groups=["07032069804988"], gtin=Gtin( value="07032069804988", format=GtinFormat.GTIN_13, prefix=GS1Prefix(value="703", usage="GS1 Norway"), payload="703206980498", check_digit=8, ), ), GS1ElementString( ai=GS1ApplicationIdentifier( ai="15", description="Best before date (YYMMDD)", data_title="BEST BEFORE or BEST BY", fnc1_required=False, format="N2+N6", pattern="^15(\\d{6})$", ), value="210526", pattern_groups=["210526"], date=date(2021, 5, 26), ), GS1ElementString( ai=GS1ApplicationIdentifier( ai="10", description="Batch or lot number", data_title="BATCH/LOT", fnc1_required=True, format="N2+X..20", pattern=( r"^10([\x21-\x22\x25-\x2F\x30-\x39\x3A-\x3F" r"\x41-\x5A\x5F\x61-\x7A]{0,20})$"), ), value="0329", pattern_groups=["0329"], ), ], ),
def parse( value: str, *, rcn_region: Optional[RcnRegion] = None, separator_chars: Iterable[str] = DEFAULT_SEPARATOR_CHARS, ) -> ParseResult: """Identify data format and parse data. The current strategy is: 1. If Symbology Identifier prefix indicates a GTIN or GS1 Message, attempt to parse and validate as that. 2. Else, if not Symbology Identifier, attempt to parse with all parsers. Args: value: The data to classify and 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. separator_chars: Characters used in place of the FNC1 symbol. Defaults to `<GS>` (ASCII value 29). If variable-length fields in the middle of the message are not terminated with a separator character, the parser might greedily consume the rest of the message. Returns: A data class depending upon what type of data is parsed. Raises: ParseError: If parsing of the data fails. """ value = value.strip() result = ParseResult(value=value) # Extract Symbology Identifier if value.startswith("]"): result.symbology_identifier = SymbologyIdentifier.extract(value) value = value[len(result.symbology_identifier):] # Select parsers parsers: List[ParserType] = [] if result.symbology_identifier is not None: if (result.symbology_identifier.gs1_symbology in GS1Symbology.with_gtin()): parsers.append(Gtin) if (result.symbology_identifier.gs1_symbology in GS1Symbology.with_ai_element_strings()): parsers.append(GS1Message) if not parsers: # If we're not able to select a subset based on Symbology Identifiers, # run all parsers in the default order. parsers = [Gtin, Sscc, GS1Message] # Run all parsers in order for parser in parsers: if parser == Gtin: try: result.gtin = Gtin.parse(value, rcn_region=rcn_region) except ParseError as exc: result.gtin_error = str(exc) if parser == Sscc: try: result.sscc = Sscc.parse(value) except ParseError as exc: result.sscc_error = str(exc) if parser == GS1Message: try: result.gs1_message = GS1Message.parse( value, rcn_region=rcn_region, separator_chars=separator_chars, ) except ParseError as exc: result.gs1_message_error = str(exc) else: ai_00 = result.gs1_message.get(ai="00") if ai_00 is not None: # GS1 Message contains an SSCC result.sscc = ai_00.sscc # Clear error from parsing full value a SSCC. result.sscc_error = None ai_01 = result.gs1_message.get(ai="01") if ai_01 is not None: # GS1 Message contains a GTIN. result.gtin = ai_01.gtin # Clear error from parsing full value as GTIN. result.gtin_error = None if result._has_result(): return result else: raise ParseError( f"Failed to parse {value!r}:\n{result._get_errors_list()}")
# GTIN-8 "96385074", ParseResult( value="96385074", gtin=Gtin( value="96385074", format=GtinFormat.GTIN_8, prefix=GS1Prefix(value="00009", usage="GS1 US"), payload="9638507", check_digit=4, ), gs1_message=GS1Message( value="96385074", element_strings=[ GS1ElementString( ai=GS1ApplicationIdentifier.extract("96"), value="385074", pattern_groups=["385074"], ) ], ), sscc_error= ("Failed to parse '96385074' as SSCC: Expected 18 digits, got 8." ), ), ), ( # GTIN-12 "123601057072", ParseResult( value="123601057072", gtin=Gtin(