Skip to content

Commit

Permalink
Enable type hinting, update dependencies (#33)
Browse files Browse the repository at this point in the history
* Restructure EPC_SCHEME interface, use epc_uri in init

* Fix mypy errors for schemes and common utils

* Fix mypy issues in parsers

* Create py.typed file for allowing type checking

* Update dependencies

* Fix typo in README

* Deprecate Python 3.7
  • Loading branch information
SanderMeinderts authored May 31, 2023
1 parent f5a454f commit 16b4312
Show file tree
Hide file tree
Showing 28 changed files with 209 additions and 308 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ A Python module for creation, validation, and transformation of EPC representati
- [Notebook](#notebook)

## Requirements
- Python >= 3.7
- Python >= 3.8

## Installation
```
Expand Down Expand Up @@ -214,7 +214,7 @@ Poetry must be installed and available in `$PATH`.
After cloning run `poetry install` to install (development) dependencies.

### Testing
This module uses the Python unittest library. Run `poetry run tests` for running the tests.
This module uses the Python unittest library. Run `poetry run test` for running the tests.

### Coverage
Run `poetry run coverage run -m unittest discover` to execute all tests with coverage. The resulting coverage can be reported using `poetry run coverage report --omit="*/test*"` for a textual view the terminal and with `poetry run coverage html --omit="*/test*"` for a webpage.
Expand Down
13 changes: 6 additions & 7 deletions epcpy/epc_schemes/adi.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from __future__ import annotations

import math
import re
from enum import Enum

from epcpy.epc_schemes.base_scheme import EPCScheme, TagEncodable
from epcpy.epc_schemes.base_scheme import TagEncodable
from epcpy.utils.common import (
ConvertException,
binary_to_int,
Expand Down Expand Up @@ -87,7 +86,7 @@ class ADIFilterValue(Enum):
RESERVED_63 = "63"


class ADI(EPCScheme, TagEncodable):
class ADI(TagEncodable):
"""ADI EPC scheme implementation.
ADI pure identities are of the form:
Expand All @@ -114,7 +113,7 @@ class BinaryHeader(Enum):
ADI_VAR = "00111011"

def __init__(self, epc_uri) -> None:
super().__init__()
super().__init__(epc_uri)

if not ADI_URI_REGEX.fullmatch(epc_uri):
raise ConvertException(message=f"Invalid ADI URI {epc_uri}")
Expand Down Expand Up @@ -209,13 +208,13 @@ def from_binary(cls, binary_string: str) -> ADI:
part_number_binary, _, *serial_binary = re.split(
"([0]{6})", truncated_binary[50:]
)
serial_binary = "".join(serial_binary)[:-6]
serial_binary_trimmed = "".join(serial_binary)[:-6]

filter_string = binary_to_int(filter_binary)
cage_code_string = decode_cage_code_six_bits(cage_code_binary)
part_number_string = decode_string_six_bits(part_number_binary, math.inf)
part_number_string = decode_string_six_bits(part_number_binary, 32)

serial_string = decode_string_six_bits(serial_binary, math.inf)
serial_string = decode_string_six_bits(serial_binary_trimmed, 30)

return cls.from_tag_uri(
f"{cls.TAG_URI_PREFIX}{binary_coding_scheme.value}:{filter_string}.{cage_code_string}.{part_number_string}.{serial_string}"
Expand Down
79 changes: 51 additions & 28 deletions epcpy/epc_schemes/base_scheme.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from __future__ import annotations

import re
from typing import Any, Dict
from enum import Enum
from typing import Dict, Optional, Type, TypeVar

from epcpy.utils.common import ConvertException, hex_to_base64, hex_to_binary
from epcpy.utils.regex import TAG_URI

T_EPCScheme = TypeVar("T_EPCScheme", bound="EPCScheme")
T_GS1Element = TypeVar("T_GS1Element", bound="GS1Element")
T_TagEncodable = TypeVar("T_TagEncodable", bound="TagEncodable")


class EPCScheme:
"""Base class for EPC schemes
Expand All @@ -14,9 +19,9 @@ class EPCScheme:
epc_uri (str): The EPC pure identity URI
"""

def __init__(self) -> None:
def __init__(self, epc_uri: str) -> None:
super().__init__()
self.epc_uri = None
self.epc_uri = epc_uri

def __eq__(self, other: object) -> bool:
"""Verify equality of two classes by validing if its an EPCScheme and whether the EPC URIs are equal.
Expand All @@ -33,7 +38,7 @@ def __eq__(self, other: object) -> bool:
return self.epc_uri == other.epc_uri

@classmethod
def from_epc_uri(cls, epc_uri: str) -> EPCScheme:
def from_epc_uri(cls: Type[T_EPCScheme], epc_uri: str) -> EPCScheme:
"""Instantiate an EPCScheme class from an EPC pure identity URI.
Args:
Expand All @@ -45,7 +50,7 @@ def from_epc_uri(cls, epc_uri: str) -> EPCScheme:
return cls(epc_uri)


class TagEncodable:
class TagEncodable(EPCScheme):
"""Base class for tag encodable EPCSchemes
Attributes:
Expand All @@ -55,17 +60,28 @@ class TagEncodable:
base64 (str): Base64 representation of the EPC tag URI
"""

class BinaryCodingScheme(Enum):
"""Binary coding schemes for tag encodable EPC schemes"""

pass

class BinaryHeader(Enum):
"""Binary headers for tag encodable EPC schemes"""

pass

TAG_URI_REGEX = re.compile(TAG_URI)
TAG_URI_PREFIX = "urn:epc:tag:"

def __init__(self) -> None:
super().__init__()
self._base64 = None
self._binary = None
self._hex = None
self._tag_uri = None
def __init__(self, epc_uri: str) -> None:
super().__init__(epc_uri)

def tag_uri(self, **kwargs) -> str:
self._base64: Optional[str] = None
self._binary: Optional[str] = None
self._hex: Optional[str] = None
self._tag_uri: Optional[str] = None

def tag_uri(self, *args, **kwargs) -> str:
"""Return the tag URI of the tag encodable
Raises:
Expand All @@ -76,7 +92,7 @@ def tag_uri(self, **kwargs) -> str:
"""
raise NotImplementedError

def binary(self, **kwargs) -> str:
def binary(self, *args, **kwargs) -> str:
"""Return the binary representation of the tag encodable
Raises:
Expand All @@ -87,7 +103,7 @@ def binary(self, **kwargs) -> str:
"""
raise NotImplementedError

def hex(self, **kwargs) -> str:
def hex(self, *args, **kwargs) -> str:
"""Return the hexadecimal representation of the tag encodable
Returns:
Expand All @@ -100,7 +116,7 @@ def hex(self, **kwargs) -> str:

return f"{int(padded_binary, 2):X}"

def base64(self, **kwargs) -> str:
def base64(self, *args, **kwargs) -> str:
"""Return the base64 representation of the tag encodable
Returns:
Expand All @@ -110,7 +126,10 @@ def base64(self, **kwargs) -> str:

return hex_to_base64(hex_string)

def from_binary(tag_binary_string: str) -> TagEncodable:
@classmethod
def from_binary(
cls: Type[T_TagEncodable], tag_binary_string: str
) -> T_TagEncodable:
"""Instantiate a TagEncodable class from a binary string.
Args:
Expand All @@ -122,7 +141,7 @@ def from_binary(tag_binary_string: str) -> TagEncodable:
raise NotImplementedError

@classmethod
def from_hex(cls, tag_hex_string: str) -> TagEncodable:
def from_hex(cls: Type[T_TagEncodable], tag_hex_string: str) -> T_TagEncodable:
"""Instantiate a TagEncodable class from a hexidecimal string.
Args:
Expand All @@ -134,7 +153,9 @@ def from_hex(cls, tag_hex_string: str) -> TagEncodable:
return cls.from_binary(hex_to_binary(tag_hex_string))

@classmethod
def from_base64(cls, tag_base64_string: str) -> TagEncodable:
def from_base64(
cls: Type[T_TagEncodable], tag_base64_string: str
) -> T_TagEncodable:
"""Instantiate a TagEncodable class from a base64 string.
Args:
Expand All @@ -147,8 +168,8 @@ def from_base64(cls, tag_base64_string: str) -> TagEncodable:

@classmethod
def from_tag_uri(
cls, epc_tag_uri: str, includes_filter: bool = True
) -> TagEncodable:
cls: Type[T_TagEncodable], epc_tag_uri: str, includes_filter: bool = True
) -> T_TagEncodable:
"""Instantiate a TagEncodable class from a tag URI.
Args:
Expand All @@ -175,7 +196,7 @@ def from_tag_uri(
return cls(f"urn:epc:id:{epc_scheme.split('-')[0]}:{value}")

@classmethod
def header_to_schemes(cls) -> Dict[str, Any]:
def header_to_schemes(cls: Type[T_TagEncodable]) -> Dict[str, Enum]:
"""Create dictionary of binary header -> binary coding scheme
Returns:
Expand All @@ -187,9 +208,11 @@ def header_to_schemes(cls) -> Dict[str, Any]:
}


class GS1Element:
def __init__(self) -> None:
super().__init__()
class GS1Element(EPCScheme):
gs1_element_string_regex: re.Pattern

def __init__(self, epc_uri: str) -> None:
super().__init__(epc_uri)

def gs1_element_string(self, *args, **kwargs) -> str:
"""GS1 element string of the given EPC scheme
Expand All @@ -204,8 +227,8 @@ def gs1_element_string(self, *args, **kwargs) -> str:

@classmethod
def from_gs1_element_string(
cls, gs1_element_string: str, company_prefix_length: int
) -> GS1Element:
cls: Type[T_GS1Element], gs1_element_string: str, company_prefix_length: int
) -> T_GS1Element:
"""Create a GS1Element instance from a GS1 element string and company prefix length.
Args:
Expand All @@ -222,8 +245,8 @@ def from_gs1_element_string(


class GS1Keyed(GS1Element):
def __init__(self) -> None:
super().__init__()
def __init__(self, epc_uri: str) -> None:
super().__init__(epc_uri)

def gs1_key(self, *args, **kwargs) -> str:
"""GS1 key of the given EPC scheme
Expand Down
2 changes: 1 addition & 1 deletion epcpy/epc_schemes/bic.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class BIC(EPCScheme):
"""

def __init__(self, epc_uri) -> None:
super().__init__()
super().__init__(epc_uri)

if not BIC_URI_REGEX.fullmatch(epc_uri):
raise ConvertException(message=f"Invalid BIC URI {epc_uri}")
Expand Down
6 changes: 3 additions & 3 deletions epcpy/epc_schemes/cpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from enum import Enum

from epcpy.epc_schemes.base_scheme import EPCScheme, GS1Element, TagEncodable
from epcpy.epc_schemes.base_scheme import GS1Element, TagEncodable
from epcpy.utils.common import (
ConvertException,
binary_to_int,
Expand Down Expand Up @@ -168,7 +168,7 @@ def revert_cpi_escapes(cpi: str) -> str:
return cpi.replace("#", "%23").replace("/", "%2F")


class CPI(EPCScheme, GS1Element, TagEncodable):
class CPI(GS1Element, TagEncodable):
"""CPI EPC scheme implementation.
CPI pure identities are of the form:
Expand Down Expand Up @@ -201,7 +201,7 @@ class BinaryHeader(Enum):
gs1_element_string_regex = re.compile(CPI_GS1_ELEMENT_STRING)

def __init__(self, epc_uri) -> None:
super().__init__()
super().__init__(epc_uri)

if not CPI_URI_REGEX.fullmatch(epc_uri):
raise ConvertException(message=f"Invalid CPI URI {epc_uri}")
Expand Down
6 changes: 3 additions & 3 deletions epcpy/epc_schemes/gdti.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from enum import Enum

from epcpy.epc_schemes.base_scheme import EPCScheme, GS1Keyed, TagEncodable
from epcpy.epc_schemes.base_scheme import GS1Keyed, TagEncodable
from epcpy.utils.common import (
ConvertException,
binary_to_int,
Expand Down Expand Up @@ -96,7 +96,7 @@ class GDTIFilterValue(Enum):
RESERVED_7 = "7"


class GDTI(EPCScheme, TagEncodable, GS1Keyed):
class GDTI(TagEncodable, GS1Keyed):
"""GDTI EPC scheme implementation.
GDTI pure identities are of the form:
Expand Down Expand Up @@ -130,7 +130,7 @@ class BinaryHeader(Enum):
gs1_element_string_regex = re.compile(GDTI_GS1_ELEMENT_STRING)

def __init__(self, epc_uri) -> None:
super().__init__()
super().__init__(epc_uri)

if not GDTI_URI_REGEX.fullmatch(epc_uri):
raise ConvertException(message=f"Invalid GDTI URI {epc_uri}")
Expand Down
6 changes: 3 additions & 3 deletions epcpy/epc_schemes/giai.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from enum import Enum

from epcpy.epc_schemes.base_scheme import EPCScheme, GS1Keyed, TagEncodable
from epcpy.epc_schemes.base_scheme import GS1Keyed, TagEncodable
from epcpy.utils.common import (
ConvertException,
binary_to_int,
Expand Down Expand Up @@ -155,7 +155,7 @@ class GIAIFilterValue(Enum):
RESERVED_7 = "7"


class GIAI(EPCScheme, TagEncodable, GS1Keyed):
class GIAI(TagEncodable, GS1Keyed):
"""GIAI EPC scheme implementation.
GIAI pure identities are of the form:
Expand Down Expand Up @@ -189,7 +189,7 @@ class BinaryHeader(Enum):
gs1_element_string_regex = re.compile(GIAI_GS1_ELEMENT_STRING)

def __init__(self, epc_uri) -> None:
super().__init__()
super().__init__(epc_uri)

if not GIAI_URI_REGEX.fullmatch(epc_uri):
raise ConvertException(message=f"Invalid GIAI URI {epc_uri}")
Expand Down
10 changes: 5 additions & 5 deletions epcpy/epc_schemes/gid.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from enum import Enum

from epcpy.epc_schemes.base_scheme import EPCScheme, TagEncodable
from epcpy.epc_schemes.base_scheme import TagEncodable
from epcpy.utils.common import (
ConvertException,
binary_to_int,
Expand All @@ -15,7 +15,7 @@
GID_URI_REGEX = re.compile(GID_URI)


class GID(EPCScheme, TagEncodable):
class GID(TagEncodable):
"""GID EPC scheme implementation.
GID pure identities are of the form:
Expand All @@ -42,7 +42,7 @@ class BinaryHeader(Enum):
GID_96 = "00110101"

def __init__(self, epc_uri) -> None:
super().__init__()
super().__init__(epc_uri)

if not GID_URI_REGEX.fullmatch(epc_uri):
raise ConvertException(message=f"Invalid GID URI {epc_uri}")
Expand All @@ -64,7 +64,7 @@ def __init__(self, epc_uri) -> None:

def tag_uri(
self,
binary_coding_scheme: GID.BinaryCodingScheme.GID_96 = BinaryCodingScheme.GID_96,
binary_coding_scheme: BinaryCodingScheme = BinaryCodingScheme.GID_96,
) -> str:
"""Return the tag URI belonging to this GID with the provided binary coding scheme and filter value.
Expand All @@ -79,7 +79,7 @@ def tag_uri(

def binary(
self,
binary_coding_scheme: GID.BinaryCodingScheme.GID_96 = BinaryCodingScheme.GID_96,
binary_coding_scheme: BinaryCodingScheme = BinaryCodingScheme.GID_96,
) -> str:
"""Return the binary representation belonging to this GID with the provided binary coding scheme and filter value.
Expand Down
Loading

0 comments on commit 16b4312

Please sign in to comment.