Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gs1: Add methods to find parsed Element Strings #39

Merged
merged 1 commit into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ The library can interpret the following formats:
parse amounts with currency into `Money` values
- [x] Encode as Human Readable Interpretation (HRI),
e.g. with parenthesis around the AI numbers
- [x] Easy lookup of parsed Element Strings by:
- [x] AI prefix
- [x] Part of AI's data title
- GTIN (Global Trade Item Number)
- [x] Parse GTIN-8, e.g. from EAN-8 barcodes
- [x] Parse GTIN-12, e.g. from UPC-A and UPC-E barcodes
Expand Down
6 changes: 6 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ v0.3.0 (UNRELEASED)

:mod:`biip.gs1`

- Add :meth:`~biip.gs1.GS1Message.filter` to find all parsed Element Strings
that matches the criteria.

- Add :meth:`~biip.gs1.GS1Message.get` to find first parsed Element String
that matches the criteria.

- Add :attr:`~biip.gs1.GS1ElementString.decimal` field which is set for
AIs with weight, volume, dimensions, dicount percentages, and amounts
payable.
Expand Down
4 changes: 2 additions & 2 deletions src/biip/gs1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
format=GtinFormat.GTIN_13, prefix=GS1Prefix(value='703', usage='GS1
Norway'), payload='703206980498', check_digit=8, packaging_level=None),
date=None, decimal=None, money=None)
>>> msg.element_strings[1]
>>> msg.get(data_title='BEST BY')
GS1ElementString(ai=GS1ApplicationIdentifier(ai='15', description='Best
before date (YYMMDD)', data_title='BEST BEFORE or BEST BY',
fnc1_required=False, format='N2+N6'), value='210526',
pattern_groups=['210526'], gtin=None, date=datetime.date(2021, 5, 26),
decimal=None, money=None)
>>> msg.element_strings[2]
>>> msg.get(ai="10")
GS1ElementString(ai=GS1ApplicationIdentifier(ai='10', description='Batch
or lot number', data_title='BATCH/LOT', fnc1_required=True,
format='N2+X..20'), value='0329', pattern_groups=['0329'], gtin=None,
Expand Down
61 changes: 59 additions & 2 deletions src/biip/gs1/_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import List, Type
from typing import List, Optional, Type, Union

from biip import ParseError
from biip.gs1 import DEFAULT_SEPARATOR_CHAR, GS1ElementString
from biip.gs1 import (
DEFAULT_SEPARATOR_CHAR,
GS1ApplicationIdentifier,
GS1ElementString,
)


@dataclass
Expand Down Expand Up @@ -80,3 +84,56 @@ def as_hri(self: GS1Message) -> str:
A human-readable string where the AIs are wrapped in parenthesis.
"""
return "".join(es.as_hri() for es in self.element_strings)

def filter(
self: GS1Message,
*,
ai: Optional[Union[str, GS1ApplicationIdentifier]] = None,
data_title: Optional[str] = None,
) -> List[GS1ElementString]:
"""Filter Element Strings by AI or data title.

Args:
ai: AI instance or string to match against the start of the
Element String's AI.
data_title: String to find anywhere in the Element String's AI
data title.

Returns:
All matching Element Strings in the message.
"""
if isinstance(ai, GS1ApplicationIdentifier):
ai = ai.ai

result = []

for element_string in self.element_strings:
if ai is not None and element_string.ai.ai.startswith(ai):
result.append(element_string)
elif (
data_title is not None
and data_title in element_string.ai.data_title
):
result.append(element_string)

return result

def get(
self: GS1Message,
*,
ai: Optional[Union[str, GS1ApplicationIdentifier]] = None,
data_title: Optional[str] = None,
) -> Optional[GS1ElementString]:
"""Get Element String by AI or data title.

Args:
ai: AI instance or string to match against the start of the
Element String's AI.
data_title: String to find anywhere in the Element String's AI
data title..

Returns:
The first matching Element String in the message.
"""
matches = self.filter(ai=ai, data_title=data_title)
return matches[0] if matches else None
98 changes: 98 additions & 0 deletions tests/gs1/test_messages.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import date
from typing import List

import pytest

Expand Down Expand Up @@ -193,3 +194,100 @@ def test_parse_fails_if_fixed_length_field_ends_with_separator_char() -> None:
)
def test_as_hri(value: str, expected: str) -> None:
assert GS1Message.parse(value).as_hri() == expected


@pytest.mark.parametrize(
"value, ai, expected",
[
("010703206980498815210526100329", "01", ["07032069804988"]),
("010703206980498815210526100329", "15", ["210526"]),
("010703206980498815210526100329", "37", []),
("7230EM123\x1d7231EM456\x1d7232EM789", "7231", ["EM456"]),
(
"7230EM123\x1d7231EM456\x1d7232EM789",
"723",
["EM123", "EM456", "EM789"],
),
],
)
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


@pytest.mark.parametrize(
"value, data_title, expected",
[
("010703206980498815210526100329", "GTIN", ["07032069804988"]),
("010703206980498815210526100329", "BEST BY", ["210526"]),
("010703206980498815210526100329", "COUNT", []),
(
"7230EM123\x1d7231EM456\x1d7232EM789",
"CERT",
["EM123", "EM456", "EM789"],
),
],
)
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


@pytest.mark.parametrize(
"value, ai, expected",
[
("010703206980498815210526100329", "01", "07032069804988"),
("010703206980498815210526100329", "15", "210526"),
("010703206980498815210526100329", "10", "0329"),
("010703206980498815210526100329", "37", None),
("7230EM123\x1d7231EM456\x1d7232EM789", "7231", "EM456"),
("7230EM123\x1d7231EM456\x1d7232EM789", "723", "EM123"),
],
)
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


@pytest.mark.parametrize(
"value, data_title, expected",
[
("010703206980498815210526100329", "GTIN", "07032069804988"),
("010703206980498815210526100329", "BEST BY", "210526"),
("010703206980498815210526100329", "BATCH", "0329"),
("010703206980498815210526100329", "COUNT", None),
("7230EM123\x1d7231EM456\x1d7232EM789", "CERT #2", "EM456"),
("7230EM123\x1d7231EM456\x1d7232EM789", "CERT", "EM123"),
],
)
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_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"