Skip to content

biip.gs1_web_uris

Support for GS1 Web URIs.

GS1 Web URIs are HTTP(S) URIs pointing to any hostname, optionally with a path prefix, where GS1 element strings are encoded in the path and query parameters.

Examples of GS1 Web URIs:

  • https://id.gs1.org/gtin/614141123452/lot/ABC1/ser/12345?exp=180426
  • https://id.gs1.org/gtin/614141123452?3103=000195
  • https://id.gs1.org/sscc/106141412345678908?02=00614141123452&37=25&10=ABC123

This makes it possible to use GS1 Web URIs encoded in 2D barcodes both in supply chain and logistics applications, as well as for consumers to look up product information.

References

https://www.gs1.org/standards/Digital-Link/1-0

Example

If you only want to parse GS1 Web URIs, you can import the GS1 Web URI parser directly instead of using biip.parse().

>>> from biip.gs1_web_uris import GS1WebURI

If the parsing succeeds, it returns a GS1WebURI object.

>>> web_uri = GS1WebURI.parse("https://id.gs1.org/sscc/106141412345678908?02=00614141123452&37=25&10=ABC123")

In this case, the URI is parsed into the SSCC of a shipping container, the GTIN of the product within the shipping container, the number of item within, and the batch number.

>>> pprint(web_uri.element_strings)
[
    GS1ElementString(
        ai=GS1ApplicationIdentifier(
            ai='00',
            description='Serial Shipping Container Code (SSCC)',
            data_title='SSCC',
            separator_required=False,
            format='N2+N18'
        ),
        value='106141412345678908',
        pattern_groups=[
            '106141412345678908'
        ],
        sscc=Sscc(
            value='106141412345678908',
            prefix=GS1Prefix(
                value='061',
                usage='GS1 US'
            ),
            company_prefix=GS1CompanyPrefix(
                value='0614141'
            ),
            extension_digit=1,
            payload='10614141234567890',
            check_digit=8
        )
    ),
    GS1ElementString(
        ai=GS1ApplicationIdentifier(
            ai='02',
            description='Global Trade Item Number (GTIN) of contained trade items',
            data_title='CONTENT',
            separator_required=False,
            format='N2+N14'
        ),
        value='00614141123452',
        pattern_groups=[
            '00614141123452'
        ],
        gtin=Gtin(
            value='00614141123452',
            format=GtinFormat.GTIN_12,
            prefix=GS1Prefix(
                value='061',
                usage='GS1 US'
            ),
            company_prefix=GS1CompanyPrefix(
                value='0614141'
            ),
            payload='61414112345',
            check_digit=2
        )
    ),
    GS1ElementString(
        ai=GS1ApplicationIdentifier(
            ai='37',
            description='Count of trade items or trade item pieces contained in a logistic unit',
            data_title='COUNT',
            separator_required=True,
            format='N2+N..8'
        ),
        value='25',
        pattern_groups=[
            '25'
        ]
    ),
    GS1ElementString(
        ai=GS1ApplicationIdentifier(
            ai='10',
            description='Batch or lot number',
            data_title='BATCH/LOT',
            separator_required=True,
            format='N2+X..20'
        ),
        value='ABC123',
        pattern_groups=[
            'ABC123'
        ]
    )
]

GS1WebURI dataclass

A GS1 Web URI is a URI that contains GS1 element strings.

Source code in src/biip/gs1_web_uris.py
@dataclass(frozen=True)
class GS1WebURI:
    """A GS1 Web URI is a URI that contains GS1 element strings."""

    value: str
    """Raw unprocessed value."""

    element_strings: GS1ElementStrings
    """List of element strings found in the message.

    See [`GS1ElementStrings`][biip.gs1_element_strings.GS1ElementStrings] for
    methods to extract interesting element strings from the list.
    """

    @classmethod
    def parse(  # noqa: C901
        cls,
        value: str,
        *,
        config: ParseConfig | None = None,
    ) -> GS1WebURI:
        """Parse a string as a GS1 Web URI.

        Args:
            value: The string to parse.
            config: The parse configuration.

        Returns:
            The parsed GS1 Web URI.

        Raises:
            ParseError: If the parsing fails.
        """
        if config is None:
            config = ParseConfig()

        parsed = urlparse(value)

        match parsed.scheme:
            case "https" | "http":
                pass
            case "":
                msg = f"Expected URI, got {value!r}."
                raise ParseError(msg)
            case scheme:
                msg = f"Expected URI scheme to be 'http' or 'https', got {scheme!r}."
                raise ParseError(msg)

        # Consume path prefix
        path_parts = parsed.path.split("/")
        while path_parts:
            if path_parts[0] in _PRIMARY_IDENTIFIER_MAP:
                break
            path_parts.pop(0)

        if not path_parts:
            msg = f"Expected a primary identifier in the path, got '{parsed.path}'."
            raise ParseError(msg)

        if len(path_parts) % 2 != 0:
            path = f"/{'/'.join(path_parts)}"
            msg = f"Expected even number of path segments, got {path!r}."
            raise ParseError(msg)

        element_strings = GS1ElementStrings()
        path_pairs = _get_pairs(path_parts)

        # Extract exactly one primary identifier from path
        (pi_key, pi_value) = next(path_pairs)
        pi = _PRIMARY_IDENTIFIER_MAP[pi_key]
        if pi.zfill_to_width:
            pi_value = pi_value.zfill(pi.zfill_to_width)
        element_strings.append(
            GS1ElementString.extract(
                f"{pi.ai.ai}{pi_value}",
                config=config,
            )
        )

        # Extract qualifiers from path
        expected_qualifiers = list(pi.qualifiers)
        for qualifier_key, qualifier_value in path_pairs:
            qualifier = _get_qualifier(qualifier_key, expected_qualifiers)
            element_strings.append(
                GS1ElementString.extract(
                    f"{qualifier.ai.ai}{qualifier_value}",
                    config=config,
                )
            )

        # Extract query string components
        query_pairs = [
            (key, vals[-1])  # If an AI is repeated, the last value is used.
            for key, vals in parse_qs(parsed.query).items()
        ]
        for component_key, component_value in query_pairs:
            ai = _GS1_APPLICATION_IDENTIFIERS.get(component_key)
            if ai is None:
                # Extra query parameters are ignored.
                continue
            element_strings.append(
                GS1ElementString.extract(
                    f"{ai.ai}{component_value}",
                    config=config,
                )
            )

        return cls(value=value, element_strings=element_strings)

    @classmethod
    def from_element_strings(cls, element_strings: GS1ElementStrings) -> GS1WebURI:
        """Create a GS1 Web URI from a list of GS1 element strings.

        Args:
            element_strings: A list of GS1 element strings.

        Returns:
            GS1WebURI: The created GS1 Web URI.
        """
        return GS1WebURI(
            value=_build_url(element_strings),
            element_strings=element_strings,
        )

    def as_uri(
        self,
        *,
        domain: str | None = None,
        prefix: str | None = None,
        short_names: bool = False,
    ) -> str:
        """Render as a GS1 Web URI.

        Args:
            domain: The domain name to use in the URI. Defaults to `id.gs1.org`.
            prefix: The path prefix to use in the URI. Defaults to no prefix.
            short_names: Whether to use short names for AI values in the URI
                path. Defaults to False.

        Returns:
            str: The GS1 Web URI.
        """
        return _build_url(
            self.element_strings,
            domain=domain or "id.gs1.org",
            prefix=prefix,
            short_names=short_names,
        )

    def as_canonical_uri(self) -> str:
        """Render as a canonical GS1 Web URI.

        Canonical URIs:

        - Use the domain name `id.gs1.org`.
        - Uses numeric AI values in the URI path.
        - Excludes all query parameters that are not valid application identifiers.

        Returns:
            str: The canonical GS1 Web URI.

        References:
            GS1 Web URI Structure Standard, section 5.2
        """
        return _build_url(self.element_strings)

    def as_gs1_message(self) -> GS1Message:
        """Converts the GS1 Web URI to a GS1 Message."""
        from biip.gs1_messages import GS1Message

        return GS1Message.from_element_strings(self.element_strings)

element_strings instance-attribute

element_strings: GS1ElementStrings

List of element strings found in the message.

See GS1ElementStrings for methods to extract interesting element strings from the list.

value instance-attribute

value: str

Raw unprocessed value.

as_canonical_uri

as_canonical_uri() -> str

Render as a canonical GS1 Web URI.

Canonical URIs:

  • Use the domain name id.gs1.org.
  • Uses numeric AI values in the URI path.
  • Excludes all query parameters that are not valid application identifiers.

Returns:

  • str ( str ) –

    The canonical GS1 Web URI.

References

GS1 Web URI Structure Standard, section 5.2

Source code in src/biip/gs1_web_uris.py
def as_canonical_uri(self) -> str:
    """Render as a canonical GS1 Web URI.

    Canonical URIs:

    - Use the domain name `id.gs1.org`.
    - Uses numeric AI values in the URI path.
    - Excludes all query parameters that are not valid application identifiers.

    Returns:
        str: The canonical GS1 Web URI.

    References:
        GS1 Web URI Structure Standard, section 5.2
    """
    return _build_url(self.element_strings)

as_gs1_message

as_gs1_message() -> GS1Message

Converts the GS1 Web URI to a GS1 Message.

Source code in src/biip/gs1_web_uris.py
def as_gs1_message(self) -> GS1Message:
    """Converts the GS1 Web URI to a GS1 Message."""
    from biip.gs1_messages import GS1Message

    return GS1Message.from_element_strings(self.element_strings)

as_uri

as_uri(
    *,
    domain: str | None = None,
    prefix: str | None = None,
    short_names: bool = False,
) -> str

Render as a GS1 Web URI.

Parameters:

  • domain (str | None, default: None ) –

    The domain name to use in the URI. Defaults to id.gs1.org.

  • prefix (str | None, default: None ) –

    The path prefix to use in the URI. Defaults to no prefix.

  • short_names (bool, default: False ) –

    Whether to use short names for AI values in the URI path. Defaults to False.

Returns:

  • str ( str ) –

    The GS1 Web URI.

Source code in src/biip/gs1_web_uris.py
def as_uri(
    self,
    *,
    domain: str | None = None,
    prefix: str | None = None,
    short_names: bool = False,
) -> str:
    """Render as a GS1 Web URI.

    Args:
        domain: The domain name to use in the URI. Defaults to `id.gs1.org`.
        prefix: The path prefix to use in the URI. Defaults to no prefix.
        short_names: Whether to use short names for AI values in the URI
            path. Defaults to False.

    Returns:
        str: The GS1 Web URI.
    """
    return _build_url(
        self.element_strings,
        domain=domain or "id.gs1.org",
        prefix=prefix,
        short_names=short_names,
    )

from_element_strings classmethod

from_element_strings(
    element_strings: GS1ElementStrings,
) -> GS1WebURI

Create a GS1 Web URI from a list of GS1 element strings.

Parameters:

Returns:

  • GS1WebURI ( GS1WebURI ) –

    The created GS1 Web URI.

Source code in src/biip/gs1_web_uris.py
@classmethod
def from_element_strings(cls, element_strings: GS1ElementStrings) -> GS1WebURI:
    """Create a GS1 Web URI from a list of GS1 element strings.

    Args:
        element_strings: A list of GS1 element strings.

    Returns:
        GS1WebURI: The created GS1 Web URI.
    """
    return GS1WebURI(
        value=_build_url(element_strings),
        element_strings=element_strings,
    )

parse classmethod

parse(
    value: str, *, config: ParseConfig | None = None
) -> GS1WebURI

Parse a string as a GS1 Web URI.

Parameters:

  • value (str) –

    The string to parse.

  • config (ParseConfig | None, default: None ) –

    The parse configuration.

Returns:

Raises:

Source code in src/biip/gs1_web_uris.py
@classmethod
def parse(  # noqa: C901
    cls,
    value: str,
    *,
    config: ParseConfig | None = None,
) -> GS1WebURI:
    """Parse a string as a GS1 Web URI.

    Args:
        value: The string to parse.
        config: The parse configuration.

    Returns:
        The parsed GS1 Web URI.

    Raises:
        ParseError: If the parsing fails.
    """
    if config is None:
        config = ParseConfig()

    parsed = urlparse(value)

    match parsed.scheme:
        case "https" | "http":
            pass
        case "":
            msg = f"Expected URI, got {value!r}."
            raise ParseError(msg)
        case scheme:
            msg = f"Expected URI scheme to be 'http' or 'https', got {scheme!r}."
            raise ParseError(msg)

    # Consume path prefix
    path_parts = parsed.path.split("/")
    while path_parts:
        if path_parts[0] in _PRIMARY_IDENTIFIER_MAP:
            break
        path_parts.pop(0)

    if not path_parts:
        msg = f"Expected a primary identifier in the path, got '{parsed.path}'."
        raise ParseError(msg)

    if len(path_parts) % 2 != 0:
        path = f"/{'/'.join(path_parts)}"
        msg = f"Expected even number of path segments, got {path!r}."
        raise ParseError(msg)

    element_strings = GS1ElementStrings()
    path_pairs = _get_pairs(path_parts)

    # Extract exactly one primary identifier from path
    (pi_key, pi_value) = next(path_pairs)
    pi = _PRIMARY_IDENTIFIER_MAP[pi_key]
    if pi.zfill_to_width:
        pi_value = pi_value.zfill(pi.zfill_to_width)
    element_strings.append(
        GS1ElementString.extract(
            f"{pi.ai.ai}{pi_value}",
            config=config,
        )
    )

    # Extract qualifiers from path
    expected_qualifiers = list(pi.qualifiers)
    for qualifier_key, qualifier_value in path_pairs:
        qualifier = _get_qualifier(qualifier_key, expected_qualifiers)
        element_strings.append(
            GS1ElementString.extract(
                f"{qualifier.ai.ai}{qualifier_value}",
                config=config,
            )
        )

    # Extract query string components
    query_pairs = [
        (key, vals[-1])  # If an AI is repeated, the last value is used.
        for key, vals in parse_qs(parsed.query).items()
    ]
    for component_key, component_value in query_pairs:
        ai = _GS1_APPLICATION_IDENTIFIERS.get(component_key)
        if ai is None:
            # Extra query parameters are ignored.
            continue
        element_strings.append(
            GS1ElementString.extract(
                f"{ai.ai}{component_value}",
                config=config,
            )
        )

    return cls(value=value, element_strings=element_strings)