From 7bf5e54a784f69810e2c4bb3ce85f85de9eb63c5 Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Thu, 14 Aug 2025 19:55:06 -0700 Subject: [PATCH 01/10] Updated the README with instructions on running the codegen. --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 8aad764..c4c462d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - [Usage](#usage) - [`get_tracking_number(number)`](#get_tracking_numbernumber) - [`get_definition(product_name)`](#get_definitionproduct_name) + - [Updating the definitions](#updating-the-definitions) - [Testing](#testing) @@ -84,6 +85,26 @@ tracking_number = ups_definition.test('some_valid_fedex_number') # => None ``` +## Updating the definitions + +The source [`tracking_number_data`](https://github.com/jkeen/tracking_number_data/) is +occasionally updated. To re-generate this library please run the following: + +```sh +# Fetch the latest tracking_number_data +git submodule init +git submodule update --remote --merge + +# Regenerate the tracking number definitions +python codegen.py + +# Format and Lint the code +pre-commit run --all-files + +# Finally test +pytest +``` + ## Testing We use the test cases defined in the courier data to generate pytest test cases. From 72505ddb8a701a81f8c2abb7b4b8275eebef453a Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Thu, 14 Aug 2025 20:17:33 -0700 Subject: [PATCH 02/10] Updated the pre-commit config, so it works with modern python. --- .pre-commit-config.yaml | 22 +++++++++------------- setup.cfg | 3 +++ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f7d0c8..1593bd4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.1.0 + rev: v6.0.0 hooks: - id: check-docstring-first - id: check-executables-have-shebangs @@ -13,37 +13,33 @@ repos: - id: trailing-whitespace exclude: '^tests/fixtures/.*' - repo: https://github.com/pycqa/flake8 - rev: '4.0.1' + rev: '7.3.0' hooks: - id: flake8 additional_dependencies: [ - flake8-tidy-imports==4.5.0, - flake8-logging-format==0.6.0, + flake8-tidy-imports==4.11.0, ] - repo: https://github.com/asottile/pyupgrade - rev: v1.14.0 + rev: v3.20.0 hooks: - id: pyupgrade - args: [--py36-plus] - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/asottile/add-trailing-comma - rev: v1.0.0 + rev: v3.2.0 hooks: - id: add-trailing-comma - args: [--py36-plus] - repo: https://github.com/asottile/reorder_python_imports - rev: v1.4.0 + rev: v3.15.0 hooks: - id: reorder-python-imports - args: [--py3-plus] - repo: https://github.com/thlorenz/doctoc - rev: v1.4.0 + rev: v2.2.0 hooks: - id: doctoc - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.782 + rev: v1.17.1 hooks: - id: mypy diff --git a/setup.cfg b/setup.cfg index 876d2ba..fc30243 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,3 +4,6 @@ max-line-length = 120 per-file-ignores = tracking_numbers/__init__.py:F401 tracking_numbers/_generated.py:E501 + +exclude = + tracking_number_data From abff2a81f207ba044ced3b6d9e1a811f3368f5db Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Fri, 15 Aug 2025 13:54:49 -0700 Subject: [PATCH 03/10] Bumped the version of tracking_number_data in use. --- .gitmodules | 2 +- tracking_number_data | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index f343630..1d0a8ec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "tracking_number_data"] path = tracking_number_data - url = git@github.com:jcomo/tracking_number_data + url = git@github.com:jkeen/tracking_number_data diff --git a/tracking_number_data b/tracking_number_data index e02ceb5..be61d7f 160000 --- a/tracking_number_data +++ b/tracking_number_data @@ -1 +1 @@ -Subproject commit e02ceb59991264f98d7db4108ea172f55953a6ec +Subproject commit be61d7f2f076e35382b680e4e49b62e51a4e8219 From 2bd4ff9c644954af23f2b373b318125c0d2f25d0 Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Fri, 15 Aug 2025 13:55:50 -0700 Subject: [PATCH 04/10] Additional information is now extracted, and the API has been updated to make this available. For example, S10 provides a table of information for each courier. This is now available via the courier_info property on TrackingNumber. If a additional validator is provided, that is now correctly used, instead of the old behaviour that would fail if the additional information was missing. This matches the behaviour of the upstream ruby library, and makes the library more generous at parsing valid tracking numbers, that we don't have additional information for. --- codegen.py | 3 +- tests/fixtures/example_s10.json | 39 ++++++++ tests/test_additional_information.py | 64 ++++++++++++ tracking_numbers/_generated.py | 3 +- tracking_numbers/definition.py | 144 ++++++++++++++++++++------- tracking_numbers/types.py | 43 ++++++++ 6 files changed, 259 insertions(+), 37 deletions(-) create mode 100644 tests/fixtures/example_s10.json create mode 100644 tests/test_additional_information.py diff --git a/codegen.py b/codegen.py index 24d36a0..997dfcb 100644 --- a/codegen.py +++ b/codegen.py @@ -13,7 +13,8 @@ "from tracking_numbers.checksum_validator import Mod7", "from tracking_numbers.checksum_validator import S10", "from tracking_numbers.checksum_validator import SumProductWithWeightsAndModulo", - "from tracking_numbers.definition import AdditionalValidation", + "from tracking_numbers.definition import Additional", + "from tracking_numbers.definition import AdditionalValidator", "from tracking_numbers.definition import TrackingNumberDefinition", "from tracking_numbers.serial_number import DefaultSerialNumberParser", "from tracking_numbers.serial_number import PrependIf", diff --git a/tests/fixtures/example_s10.json b/tests/fixtures/example_s10.json new file mode 100644 index 0000000..0e8626e --- /dev/null +++ b/tests/fixtures/example_s10.json @@ -0,0 +1,39 @@ +{ + "id": "s10", + "name": "S10", + "validation": { + "checksum": { + "name": "s10" + }, + "additional": { + "exists": ["Courier"] + } + }, + "regex": "\\s*(?([A-Z]\\s*){2})(?([0-9]\\s*){8})(?([0-9]\\s*))(?([A-Z]\\s*){2})", + "additional": [ + { + "name": "Service Type", + "regex_group_name": "ServiceType", + "lookup": [ + { + "name": "Letter Post Registered", + "matches_regex": "R[A-Z]", + "description": "Prepaid first-class mail." + } + ] + }, + { + "name": "Courier", + "regex_group_name": "CountryCode", + "lookup": [ + { + "country": "Great Britain", + "matches": "GB", + "courier": "Royal Mail Group plc", + "courier_url": "http://www.royalmail.com/postcode-finder?gear=postcode&campaignid=postcodefinder_redirect", + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/great-britain.html" + } + ] + } + ] +} diff --git a/tests/test_additional_information.py b/tests/test_additional_information.py new file mode 100644 index 0000000..9c85603 --- /dev/null +++ b/tests/test_additional_information.py @@ -0,0 +1,64 @@ +import json +import os + +from tracking_numbers.definition import TrackingNumberDefinition +from tracking_numbers.types import Courier + +# Load the S10 tracking number spec from the JSON file +spec_path = os.path.join(os.path.dirname(__file__), "fixtures/example_s10.json") +with open(spec_path) as f: + tn_spec = json.load(f) + +courier = Courier( + name="S10 International Standard", + code="s10", +) + +definition = TrackingNumberDefinition.from_spec(courier, tn_spec) + + +def test_s10_additional_info(): + # Test case for extracting additional information from the tracking number + tracking = definition.test("RB123456785GB") + + assert tracking is not None + assert tracking.courier_info == { + "code": "s10", + "name": "Royal Mail Group plc", + "url": "http://www.royalmail.com/postcode-finder?gear=postcode&campaignid=postcodefinder_redirect", + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/great-britain.html", + "country": "Great Britain", + } + + assert tracking.service_type == { + "code": "RB", + "name": "Letter Post Registered", + "description": "Prepaid first-class mail.", + } + + assert tracking.valid + + +def test_s10_missing_additional_info(): + print(definition) + + tracking = definition.test("AB123456785NP") + assert tracking is not None + + # AB is a unknown courier code, so we default back to s10 + assert tracking.courier_info == { + "code": "s10", + "name": "S10 International Standard", + } + + assert tracking.service_type == { + "code": "AB", + } + + assert not tracking.valid + assert tracking.validation_errors == [ + ( + "Courier", + "Courier not found in additional information", + ), + ] diff --git a/tracking_numbers/_generated.py b/tracking_numbers/_generated.py index 6bb1718..64ebc18 100644 --- a/tracking_numbers/_generated.py +++ b/tracking_numbers/_generated.py @@ -5,7 +5,8 @@ from tracking_numbers.checksum_validator import Mod7 from tracking_numbers.checksum_validator import S10 from tracking_numbers.checksum_validator import SumProductWithWeightsAndModulo -from tracking_numbers.definition import AdditionalValidation +from tracking_numbers.definition import Additional +from tracking_numbers.definition import AdditionalValidator from tracking_numbers.definition import TrackingNumberDefinition from tracking_numbers.serial_number import DefaultSerialNumberParser from tracking_numbers.serial_number import PrependIf diff --git a/tracking_numbers/definition.py b/tracking_numbers/definition.py index 82b31c2..2609d0d 100644 --- a/tracking_numbers/definition.py +++ b/tracking_numbers/definition.py @@ -3,6 +3,7 @@ from typing import List from typing import Optional from typing import Pattern +from typing import Tuple from tracking_numbers.checksum_validator import ChecksumValidator from tracking_numbers.compat import parse_regex @@ -11,6 +12,7 @@ from tracking_numbers.serial_number import SerialNumberParser from tracking_numbers.serial_number import UPSSerialNumberParser from tracking_numbers.types import Courier +from tracking_numbers.types import Info from tracking_numbers.types import Product from tracking_numbers.types import SerialNumber from tracking_numbers.types import Spec @@ -22,32 +24,72 @@ @dataclass -class AdditionalValidation: +class Additional: + """Spec for extracting additional information from the tracking number.""" + name: str regex_group_name: str - value_matchers: List[ValueMatcher] + value_matchers: List[Tuple[ValueMatcher, Info]] @classmethod - def from_spec(cls, spec: Spec) -> "AdditionalValidation": - value_matchers: List[ValueMatcher] = [] + def from_spec(cls, spec: Spec) -> "Additional": + value_matchers: List[Tuple[ValueMatcher, Info]] = [] for value_matcher_spec in spec["lookup"]: - value_matchers.append(ValueMatcher.from_spec(value_matcher_spec)) + # Create a copy of the value_matcher_spec without the 'match' and 'matches_regex' keys + info = { + k: value_matcher_spec[k] + for k in set(list(value_matcher_spec.keys())) + - {"matches", "matches_regex"} + } + + value_matchers.append((ValueMatcher.from_spec(value_matcher_spec), info)) - return AdditionalValidation( + return Additional( name=spec["name"], regex_group_name=spec["regex_group_name"], value_matchers=value_matchers, ) +@dataclass +class AdditionalValidator: + """Spec for validating additional information extracted from the tracking number.""" + + exists: List[str] + + @classmethod + def from_spec(cls, spec: Spec) -> Optional["AdditionalValidator"]: + if spec is None or "exists" not in spec: + return None + + return AdditionalValidator( + exists=[key for key in spec["exists"]], + ) + + class TrackingNumberDefinition: + """ + Represents a tracking number definition. + + Attributes: + courier: The courier associated with this tracking number definition. + product: The product or service type for the tracking number definition. + number_regex: Regex pattern to match and parse the tracking number. + tracking_url_template: Template for generating tracking URLs. + serial_number_parser: Parser for extracting serial number from the tracking number. + additional: List of additional information extractors. + additional_validator: Validator for additional extracted information. + checksum_validator: Validator for the tracking number's checksum digit. + """ + courier: Courier product: Product number_regex: Pattern tracking_url_template: Optional[str] serial_number_parser: SerialNumberParser + additional: List[Additional] + additional_validator: Optional[AdditionalValidator] checksum_validator: Optional[ChecksumValidator] - additional_validations: List[AdditionalValidation] def __init__( self, @@ -56,16 +98,18 @@ def __init__( number_regex: Pattern, tracking_url_template: Optional[str], serial_number_parser: SerialNumberParser, + additional: List[Additional], + additional_validator: Optional[AdditionalValidator], checksum_validator: Optional[ChecksumValidator], - additional_validations: List[AdditionalValidation], ): self.courier = courier self.product = product self.number_regex = number_regex self.tracking_url_template = tracking_url_template self.serial_number_parser = serial_number_parser + self.additional = additional + self.additional_validator = additional_validator self.checksum_validator = checksum_validator - self.additional_validations = additional_validations def __repr__(self): return repr_with_args( @@ -75,8 +119,9 @@ def __repr__(self): number_regex=self.number_regex, tracking_url_template=self.tracking_url_template, serial_number_parser=self.serial_number_parser, + additional=self.additional, + additional_validator=self.additional_validator, checksum_validator=self.checksum_validator, - additional_validations=self.additional_validations, ) @classmethod @@ -85,6 +130,13 @@ def from_spec(cls, courier: Courier, tn_spec: Spec) -> "TrackingNumberDefinition tracking_url_template = tn_spec.get("tracking_url") number_regex = parse_regex(tn_spec["regex"]) + # Optional additional validation, that provides mappings to countries, mail classes, etc. + additional_spec = tn_spec.get("additional") + additional: List[Additional] = [] + if isinstance(additional_spec, list): + for spec in additional_spec: + additional.append(Additional.from_spec(spec)) + validation_spec = tn_spec["validation"] serial_number_parser = ( UPSSerialNumberParser() @@ -92,12 +144,9 @@ def from_spec(cls, courier: Courier, tn_spec: Spec) -> "TrackingNumberDefinition else DefaultSerialNumberParser.from_spec(validation_spec) ) - additional_spec = tn_spec.get("additional") - additional_validations: List[AdditionalValidation] = [] - if isinstance(additional_spec, list): - # Handles None and 1 that is a dict (seems like old format / mistake) - for spec in additional_spec: - additional_validations.append(AdditionalValidation.from_spec(spec)) + # Additional validation that is required + additional_validation_spec = validation_spec.get("additional") + additional_validator = AdditionalValidator.from_spec(additional_validation_spec) return TrackingNumberDefinition( courier=courier, @@ -105,8 +154,9 @@ def from_spec(cls, courier: Courier, tn_spec: Spec) -> "TrackingNumberDefinition number_regex=number_regex, tracking_url_template=tracking_url_template, serial_number_parser=serial_number_parser, + additional=additional, + additional_validator=additional_validator, checksum_validator=ChecksumValidator.from_spec(validation_spec), - additional_validations=additional_validations, ) def test(self, tracking_number: str) -> Optional[TrackingNumber]: @@ -116,14 +166,28 @@ def test(self, tracking_number: str) -> Optional[TrackingNumber]: match_data = match.groupdict() if match else {} serial_number = self._get_serial_number(match_data) - validation_errors = self._get_validation_errors(serial_number, match_data) + tracking_url = self.tracking_url(tracking_number) + + additional: Dict[str, Info] = {} + for addition in self.additional: + info = self._get_additional(addition, match_data) + if info: + additional[addition.name] = info + + validation_errors = self._get_validation_errors( + serial_number, + additional, + match_data, + ) return TrackingNumber( number=tracking_number, courier=self.courier, product=self.product, + match_data=match_data, serial_number=serial_number, - tracking_url=self.tracking_url(tracking_number), + tracking_url=tracking_url, + additional=additional, validation_errors=validation_errors, ) @@ -139,6 +203,7 @@ def _get_serial_number(self, match_data: MatchData) -> Optional[SerialNumber]: def _get_validation_errors( self, serial_number: Optional[SerialNumber], + additional: Dict[str, Info], match_data: MatchData, ) -> List[ValidationError]: errors: List[ValidationError] = [] @@ -146,10 +211,7 @@ def _get_validation_errors( if checksum_error: errors.append(checksum_error) - for validation in self.additional_validations: - additional_error = self._get_additional_error(validation, match_data) - if additional_error: - errors.append(additional_error) + errors += self._get_additional_error(additional) return errors @@ -178,26 +240,38 @@ def _get_checksum_errors( return None - @staticmethod - def _get_additional_error( - validation: AdditionalValidation, + def _get_additional( + self, + additional: Additional, match_data: MatchData, - ) -> Optional[ValidationError]: - group_key = validation.regex_group_name + ) -> Optional[Info]: + group_key = additional.regex_group_name raw_value = match_data.get(group_key) if not raw_value: - return validation.name, f"{group_key} not found" + # The group_key is not present in the match data + return None value = _remove_whitespace(raw_value) - matches_any_value = any( - value_matcher.matches(value) for value_matcher in validation.value_matchers - ) - - if not matches_any_value: - return validation.name, f"Match not found for {group_key}: {value}" + for value_matcher, info in additional.value_matchers: + if value_matcher.matches(value): + return info + # The group_key is present, but the value is not valid return None + def _get_additional_error( + self, + additional: Dict[str, Info], + ) -> List[ValidationError]: + if not self.additional_validator: + return [] + + return [ + (key, f"{key} not found in additional information") + for key in self.additional_validator.exists + if key not in additional + ] + def tracking_url(self, tracking_number: str) -> Optional[str]: if not self.tracking_url_template: return None diff --git a/tracking_numbers/types.py b/tracking_numbers/types.py index 6e35d61..a6ed5a8 100644 --- a/tracking_numbers/types.py +++ b/tracking_numbers/types.py @@ -5,6 +5,8 @@ from typing import Optional from typing import Tuple + +Info = Dict[str, Any] Spec = Dict[str, Any] SerialNumber = List[int] ValidationError = Tuple[str, str] @@ -28,12 +30,53 @@ class TrackingNumber: product: Product serial_number: Optional[SerialNumber] tracking_url: Optional[str] + match_data: Info + additional: Dict[str, Info] validation_errors: List[ValidationError] @property def valid(self) -> bool: return not self.validation_errors + @property + def courier_info(self) -> Info: + """Provides information about the courier. + + Returns: + Info: A dictionary containing courier information. Typical fields + include "country", "courier", and "courier_url". + """ + # Start with dataclass fields + info = { + "code": self.courier.code, + "name": self.courier.name, + } + # Merge in additional fields if present + additional = self.additional.get("Courier", {}) + for k, v in additional.items(): + if v is None: + continue + if k == "courier": + info["name"] = v + elif k == "courier_url": + info["url"] = v + else: + info[k] = v + return info + + @property + def service_type(self) -> Info: + """Provides additional information about the service type. + + Returns: + Info: A dictionary containing service type information. Typical fields + include "name", and "description". + """ + return {"code": self.match_data.get("ServiceType")} | self.additional.get( + "Service Type", + {}, + ) + def to_int(serial_number: SerialNumber) -> int: return int("".join(map(str, serial_number))) From 98f7565b492adc2b3ed3ea851b061c136430a08e Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Fri, 15 Aug 2025 13:57:55 -0700 Subject: [PATCH 05/10] Change the type of the SerialNumber/CheckDigit to be a char, not a int. Additionally added two new checksum algorithsm, Luhn, and Mod_37_36. Newer formats, such as DPD, require alphanumeric serial numbers / check digits. The upstream library now supports this, and moves the casting to ints into checksum algorithsm, which I've copied. --- codegen.py | 2 + tests/test_tracking_numbers.py | 2 +- tracking_numbers/_generated.py | 3 + tracking_numbers/checksum_validator.py | 92 ++++++++++++++++++++++---- tracking_numbers/definition.py | 2 +- tracking_numbers/serial_number.py | 6 +- tracking_numbers/types.py | 2 +- tracking_numbers/value_matcher.py | 6 ++ 8 files changed, 95 insertions(+), 20 deletions(-) diff --git a/codegen.py b/codegen.py index 997dfcb..102c6fc 100644 --- a/codegen.py +++ b/codegen.py @@ -9,8 +9,10 @@ import_statements = [ "import re", "", + "from tracking_numbers.checksum_validator import Luhn", "from tracking_numbers.checksum_validator import Mod10", "from tracking_numbers.checksum_validator import Mod7", + "from tracking_numbers.checksum_validator import Mod_37_36", "from tracking_numbers.checksum_validator import S10", "from tracking_numbers.checksum_validator import SumProductWithWeightsAndModulo", "from tracking_numbers.definition import Additional", diff --git a/tests/test_tracking_numbers.py b/tests/test_tracking_numbers.py index 58a7052..3e7c4fe 100644 --- a/tests/test_tracking_numbers.py +++ b/tests/test_tracking_numbers.py @@ -25,7 +25,7 @@ def pytest_generate_tests(metafunc): ) -def test_tracking_numbers(definition, number, expected_valid): +def test_tracking_numbers(definition: TrackingNumberDefinition, number: str, expected_valid: bool): tracking_number = definition.test(number) if not tracking_number: assert not expected_valid, "Expected valid tracking number, but wasn't detected" diff --git a/tracking_numbers/_generated.py b/tracking_numbers/_generated.py index 64ebc18..c2d286e 100644 --- a/tracking_numbers/_generated.py +++ b/tracking_numbers/_generated.py @@ -1,8 +1,11 @@ # DO NOT EDIT - Generated by codegen.py + import re +from tracking_numbers.checksum_validator import Luhn from tracking_numbers.checksum_validator import Mod10 from tracking_numbers.checksum_validator import Mod7 +from tracking_numbers.checksum_validator import Mod_37_36 from tracking_numbers.checksum_validator import S10 from tracking_numbers.checksum_validator import SumProductWithWeightsAndModulo from tracking_numbers.definition import Additional diff --git a/tracking_numbers/checksum_validator.py b/tracking_numbers/checksum_validator.py index abb5f0a..fe1b89b 100644 --- a/tracking_numbers/checksum_validator.py +++ b/tracking_numbers/checksum_validator.py @@ -14,7 +14,7 @@ def __repr__(self): return repr_with_args(self) @abstractmethod - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: raise NotImplementedError @classmethod @@ -26,13 +26,19 @@ def from_spec(cls, validation_spec: Spec) -> Optional["ChecksumValidator"]: strategy = checksum_spec.get("name") if strategy == "s10": return S10() + elif strategy == "mod7": return Mod7() + elif strategy == "mod10": return Mod10( odds_multiplier=checksum_spec.get("odds_multiplier"), evens_multiplier=checksum_spec.get("evens_multiplier"), ) + + elif strategy == "mod_37_36": + return Mod_37_36() + elif strategy == "sum_product_with_weightings_and_modulo": return SumProductWithWeightsAndModulo( weights=checksum_spec["weightings"], @@ -40,16 +46,19 @@ def from_spec(cls, validation_spec: Spec) -> Optional["ChecksumValidator"]: second_modulo=checksum_spec["modulo2"], ) + elif strategy == "luhn": + return Luhn() + raise ValueError(f"Unknown checksum: {strategy}") class S10(ChecksumValidator): WEIGHTS = [8, 6, 4, 2, 3, 5, 9, 7] - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: total = 0 for digit, weight in zip(serial_number, self.WEIGHTS): - total += digit * weight + total += int(digit) * weight remainder = total % 11 if remainder == 1: @@ -59,7 +68,7 @@ def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: else: check = 11 - remainder - return check == check_digit + return check == int(check_digit) class Mod10(ChecksumValidator): @@ -78,29 +87,62 @@ def __repr__(self): evens_multiplier=self.evens_multiplier, ) - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: total = 0 for index, digit in enumerate(serial_number): is_even_index = index % 2 == 0 is_odd_index = not is_even_index if is_odd_index and self.odds_multiplier: - total += digit * self.odds_multiplier + total += int(digit) * self.odds_multiplier elif is_even_index and self.evens_multiplier: - total += digit * self.evens_multiplier + total += int(digit) * self.evens_multiplier else: - total += digit + total += int(digit) check = total % 10 if check != 0: check = 10 - check - return check == check_digit + return check == int(check_digit) class Mod7(ChecksumValidator): - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: - return check_digit == (to_int(serial_number) % 7) + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + return int(check_digit) == (to_int(serial_number) % 7) + + +class Mod_37_36(ChecksumValidator): + WEIGHTS = {chr(i): i + 10 for i in range(26)} # A=10, B=11, ..., Z=35 + + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + # From https://esolutions.dpd.com/dokumente/DPD_Parcel_Label_Specification_2.4.1_EN.pdf + mod = 36 + cd = mod + for char in serial_number: + if char.isalpha(): + val = self.WEIGHTS[char.upper()] + else: + val = int(char) + cd = val + cd + if cd > mod: + cd = cd - mod + cd = cd * 2 + if cd > mod: + cd = cd - (mod + 1) + cd = (mod + 1) - cd + if cd == mod: + cd = 0 + if cd >= 10: + # Find the letter corresponding to the value + computed = next((a for a, val in self.WEIGHTS.items() if val == cd), None) + if computed is not None: + computed = computed + else: + computed = str(cd) + else: + computed = str(cd) + return str(computed) == check_digit class SumProductWithWeightsAndModulo(ChecksumValidator): @@ -117,10 +159,32 @@ def __repr__(self): second_modulo=self.second_modulo, ) - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: total = 0 for digit, weight in zip(serial_number, self.weights): - total += digit * weight + total += int(digit) * weight check = total % self.first_modulo % self.second_modulo - return check == check_digit + return check == int(check_digit) + + +class Luhn(ChecksumValidator): + """Luhn algorithm for validating a checksum digit. + + See https://en.wikipedia.org/wiki/Luhn_algorithm + """ + + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + total = 0 + digits = list(serial_number)[::-1] + for i, c in enumerate(digits): + x = int(c) + if i % 2 == 0: + x *= 2 + if x > 9: + x -= 9 + total += x + check = total % 10 + if check != 0: + check = 10 - check + return check == int(check_digit) diff --git a/tracking_numbers/definition.py b/tracking_numbers/definition.py index 2609d0d..29517f0 100644 --- a/tracking_numbers/definition.py +++ b/tracking_numbers/definition.py @@ -232,7 +232,7 @@ def _get_checksum_errors( passes_checksum = self.checksum_validator.passes( serial_number=serial_number, - check_digit=int(check_digit), + check_digit=check_digit, ) if not passes_checksum: diff --git a/tracking_numbers/serial_number.py b/tracking_numbers/serial_number.py index 0159736..07451ed 100644 --- a/tracking_numbers/serial_number.py +++ b/tracking_numbers/serial_number.py @@ -43,7 +43,7 @@ def parse(self, number: str) -> SerialNumber: if self.prepend_if: number = self.prepend_if.apply(number) - return [int(digit) for digit in number] + return list(number) @classmethod def from_spec(cls, validation_spec: Spec) -> "SerialNumberParser": @@ -71,9 +71,9 @@ def parse(self, number: str) -> SerialNumber: return [self._value_of(ch) for ch in number] @staticmethod - def _value_of(ch: str) -> int: + def _value_of(ch: str) -> str: # Can't find a definitive spec for _why_ the chars are mapped this way, # but I did manage to find the following articles that help to confirm # https://abelable.altervista.org/check-digit-function-for-an-ups-tracking-number/ # https://www.codeproject.com/articles/21224/calculating-the-ups-tracking-number-check-digit - return int(ch) if ch.isdigit() else (ord(ch) - 3) % 10 + return ch if ch.isdigit() else str((ord(ch) - 3) % 10) diff --git a/tracking_numbers/types.py b/tracking_numbers/types.py index a6ed5a8..9ad3aa2 100644 --- a/tracking_numbers/types.py +++ b/tracking_numbers/types.py @@ -8,7 +8,7 @@ Info = Dict[str, Any] Spec = Dict[str, Any] -SerialNumber = List[int] +SerialNumber = List[str] ValidationError = Tuple[str, str] diff --git a/tracking_numbers/value_matcher.py b/tracking_numbers/value_matcher.py index 2bdca3f..0bdc3d3 100644 --- a/tracking_numbers/value_matcher.py +++ b/tracking_numbers/value_matcher.py @@ -33,6 +33,9 @@ def __init__(self, value: str): def __repr__(self): return repr_with_args(self, value=self.value) + def __str__(self) -> str: + return self.value + def matches(self, other: str) -> bool: return self.value == other @@ -44,5 +47,8 @@ def __init__(self, pattern: Pattern): def __repr__(self): return repr_with_args(self, pattern=self.pattern) + def __str__(self) -> str: + return self.pattern.pattern + def matches(self, other: str) -> bool: return bool(self.pattern.match(other)) From a2e82ebd6bf52cc6c3cd4114e988082df1664853 Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 16 Aug 2025 10:22:42 -0700 Subject: [PATCH 06/10] Added more information to the test failure error message. --- tests/test_tracking_numbers.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_tracking_numbers.py b/tests/test_tracking_numbers.py index 3e7c4fe..fde7fa4 100644 --- a/tests/test_tracking_numbers.py +++ b/tests/test_tracking_numbers.py @@ -25,11 +25,19 @@ def pytest_generate_tests(metafunc): ) -def test_tracking_numbers(definition: TrackingNumberDefinition, number: str, expected_valid: bool): +def test_tracking_numbers( + definition: TrackingNumberDefinition, + number: str, + expected_valid: bool, +): tracking_number = definition.test(number) if not tracking_number: assert not expected_valid, "Expected valid tracking number, but wasn't detected" elif not tracking_number.valid: - assert not expected_valid, "Expected valid tracking number, but was invalid" + assert ( + not expected_valid + ), "Expected valid tracking number, but was invalid. Reasons: {}".format( + tracking_number.validation_errors, + ) elif tracking_number.valid: assert expected_valid, "Expected invalid tracking number, but was valid" From 41edd470b9176d89289cbd1add9c58d221fbca83 Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 16 Aug 2025 10:23:12 -0700 Subject: [PATCH 07/10] Updated the TrackingNumber API to allow invalid TrackNumbers to be returned, as well as returning a list of possible candidates. This helps in the situation where we may not be able to identify the exact courier from the number, and could possibly use some external data source to make the final determination. --- tracking_numbers/__init__.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tracking_numbers/__init__.py b/tracking_numbers/__init__.py index a88e3cc..f965f6a 100644 --- a/tracking_numbers/__init__.py +++ b/tracking_numbers/__init__.py @@ -14,15 +14,36 @@ DEFINITIONS = [] -def get_tracking_number(number: str) -> Optional[TrackingNumber]: +def get_tracking_number(number: str, validate=True) -> Optional[TrackingNumber]: + """Returns a TrackingNumber that matches the given number. + + Args: + number (str): The tracking number to match. + validate (bool, optional): Whether to validate the tracking number (e.g + checksum and other rules). Defaults to True. + + Returns: + Optional[TrackingNumber]: The matching TrackingNumber, or None if no match is found. + """ for tn_definition in DEFINITIONS: tracking_number = tn_definition.test(number) - if tracking_number and tracking_number.valid: + if tracking_number and (not validate or tracking_number.valid): return tracking_number return None +def possible_tracking_number(number: str) -> List[TrackingNumber]: + """Returns a list of TrackingNumbers that match the given number.""" + possible_numbers = [] + for tn_definition in DEFINITIONS: + tracking_number = tn_definition.test(number) + if tracking_number: + possible_numbers.append(tracking_number) + + return possible_numbers + + def get_definition(product_name: str) -> Optional[TrackingNumberDefinition]: for tn_definition in DEFINITIONS: if tn_definition.product.name.lower() == product_name.lower(): From 04303447704e8adeb2c63159389389e1a7a8f47a Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 16 Aug 2025 10:24:19 -0700 Subject: [PATCH 08/10] Add type information to a couple of spec.py helper methods. --- tracking_numbers/helpers/spec.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tracking_numbers/helpers/spec.py b/tracking_numbers/helpers/spec.py index cc770b7..d353062 100644 --- a/tracking_numbers/helpers/spec.py +++ b/tracking_numbers/helpers/spec.py @@ -1,6 +1,7 @@ import json import os.path from os import listdir +from typing import Iterator from typing import List from typing import Tuple @@ -13,14 +14,16 @@ TestCase = Tuple[TrackingNumberDefinition, str, bool] -def iter_courier_specs(base_dir: str = DEFAULT_BASE_DIR): +def iter_courier_specs(base_dir: str = DEFAULT_BASE_DIR) -> Iterator[Spec]: for filename in listdir(base_dir): path = os.path.join(base_dir, filename) with open(path) as f: yield json.load(f) -def iter_definitions(courier_spec: Spec): +def iter_definitions( + courier_spec: Spec, +) -> Iterator[Tuple[TrackingNumberDefinition, Spec]]: courier = Courier( name=courier_spec["name"], code=courier_spec["courier_code"], From 8b6b99e2362ba010695937e06045242a09263a4a Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 16 Aug 2025 10:25:08 -0700 Subject: [PATCH 09/10] Added checksum validator tests, and refactored the ChecksumValidator to expose the check digit (mainly to aid in debugging but could in future be part of the official API for generating tracking numbers). --- tests/test_checksum_validators.py | 133 +++++++++++++++++++++++++ tracking_numbers/checksum_validator.py | 88 +++++++++------- 2 files changed, 183 insertions(+), 38 deletions(-) create mode 100644 tests/test_checksum_validators.py diff --git a/tests/test_checksum_validators.py b/tests/test_checksum_validators.py new file mode 100644 index 0000000..6d40b20 --- /dev/null +++ b/tests/test_checksum_validators.py @@ -0,0 +1,133 @@ +from typing import Dict + +import pytest + +from tracking_numbers.checksum_validator import Luhn +from tracking_numbers.checksum_validator import Mod10 +from tracking_numbers.checksum_validator import Mod7 +from tracking_numbers.checksum_validator import Mod_37_36 +from tracking_numbers.checksum_validator import S10 +from tracking_numbers.checksum_validator import SumProductWithWeightsAndModulo +from tracking_numbers.serial_number import DefaultSerialNumberParser + + +serials_and_checks = [ + ( + "12345678", + { + "S10": "5", + "Mod10": "4", + "Mod7": "2", + "Mod_37_36": "W", + "Luhn": "2", + "SumProduct": "4", + }, + ), + ( + "45678", + { + "S10": 8, + "Mod10": "0", + "Mod7": "3", + "Mod_37_36": "V", + "Luhn": "0", + "SumProduct": "2", + }, + ), + ( + "00007", + { + "S10": 1, + "Mod10": "3", + "Mod7": "0", + "Mod_37_36": "I", + "Luhn": "5", + "SumProduct": "0", + }, + ), + ( + "A12345", + { + "Mod_37_36": "J", + }, + ), +] + +algorithms = { + "S10": S10(), + "Mod10": Mod10(), + "Mod7": Mod7(), + "Mod_37_36": Mod_37_36(), + "Luhn": Luhn(), + "SumProduct": SumProductWithWeightsAndModulo([1, 2, 3], 10, 5), +} + +parser = DefaultSerialNumberParser() + + +@pytest.mark.parametrize("serial, checks", serials_and_checks) +@pytest.mark.parametrize("algo_name", algorithms.keys()) +def test_valid_checksum(algo_name, serial: str, checks: Dict[str, str]): + validator = algorithms[algo_name] + parsed_serial = parser.parse(serial) + + if algo_name not in checks: + try: + validator._check_digit(parsed_serial) + + assert False, ( + f"Expected {algo_name} to fail for {serial} " + f"instead it provided check_digit '{validator._check_digit(parsed_serial)}'" + ) + + except ValueError: + pass + + else: + check_digit = checks[algo_name] + assert validator.passes( + parsed_serial, + check_digit, + ), ( + f"Expected {algo_name} to pass for {serial} with check_digit {check_digit} " + f"instead it provided check_digit '{validator._check_digit(parsed_serial)}'" + ) + + +def test_valid_Mod_37_36_checksum(): + # A few extra test cases for Mod_37_36 from + # https://esolutions.dpd.com/dokumente/DPD_Parcel_Label_Specification_2.4.1_EN.pdf + test_cases = [ + ("123AB", "X"), + ("ABC987", "E"), + ] + + validator = algorithms["Mod_37_36"] + for serial, expected in test_cases: + parsed_serial = parser.parse(serial) + assert validator.passes( + parsed_serial, + expected, + ), ( + f"Expected Mod_37_36 to pass for {serial} with check_digit {expected} " + f"instead it provided check_digit '{validator._check_digit(parsed_serial)}'" + ) + + +def test_valid_Luhn_checksum(): + # A few extra test cases for Luhn from + # https://en.wikipedia.org/wiki/Luhn_algorithm + test_cases = [ + ("1789372997", "4"), + ] + + validator = algorithms["Luhn"] + for serial, expected in test_cases: + parsed_serial = parser.parse(serial) + assert validator.passes( + parsed_serial, + expected, + ), ( + f"Expected Luhn to pass for {serial} with check_digit {expected} " + f"instead it provided check_digit '{validator._check_digit(parsed_serial)}'" + ) diff --git a/tracking_numbers/checksum_validator.py b/tracking_numbers/checksum_validator.py index fe1b89b..ccc013e 100644 --- a/tracking_numbers/checksum_validator.py +++ b/tracking_numbers/checksum_validator.py @@ -14,9 +14,21 @@ def __repr__(self): return repr_with_args(self) @abstractmethod - def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> int | str: + """Calculate the check digit for the given serial number. Most + algorithms return a numeric check digit, but some may return a + string representation (most notably Mod_37_36). + """ raise NotImplementedError + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + """Check if the serial number passes the checksum validation.""" + # Default implementation that handles non-numeric check digits. + try: + return self._check_digit(serial_number) == int(check_digit) + except (ValueError, TypeError): + return False + @classmethod def from_spec(cls, validation_spec: Spec) -> Optional["ChecksumValidator"]: checksum_spec = validation_spec.get("checksum") @@ -55,20 +67,19 @@ def from_spec(cls, validation_spec: Spec) -> Optional["ChecksumValidator"]: class S10(ChecksumValidator): WEIGHTS = [8, 6, 4, 2, 3, 5, 9, 7] - def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> int: total = 0 for digit, weight in zip(serial_number, self.WEIGHTS): total += int(digit) * weight remainder = total % 11 if remainder == 1: - check = 0 - elif remainder == 0: - check = 5 - else: - check = 11 - remainder + return 0 + + if remainder == 0: + return 5 - return check == int(check_digit) + return 11 - remainder class Mod10(ChecksumValidator): @@ -87,7 +98,7 @@ def __repr__(self): evens_multiplier=self.evens_multiplier, ) - def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> int: total = 0 for index, digit in enumerate(serial_number): is_even_index = index % 2 == 0 @@ -104,45 +115,48 @@ def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: if check != 0: check = 10 - check - return check == int(check_digit) + return check class Mod7(ChecksumValidator): - def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: - return int(check_digit) == (to_int(serial_number) % 7) + def _check_digit(self, serial_number: SerialNumber) -> int: + return to_int(serial_number) % 7 class Mod_37_36(ChecksumValidator): + MOD = 36 WEIGHTS = {chr(i): i + 10 for i in range(26)} # A=10, B=11, ..., Z=35 - def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> str: # From https://esolutions.dpd.com/dokumente/DPD_Parcel_Label_Specification_2.4.1_EN.pdf - mod = 36 - cd = mod + cd = self.MOD for char in serial_number: if char.isalpha(): - val = self.WEIGHTS[char.upper()] + val = ord(char.upper()) - ord("A") + 10 # A=10, B=11, ..., Z=35 else: val = int(char) cd = val + cd - if cd > mod: - cd = cd - mod + if cd > self.MOD: + cd = cd - self.MOD cd = cd * 2 - if cd > mod: - cd = cd - (mod + 1) - cd = (mod + 1) - cd - if cd == mod: + if cd > self.MOD: + cd = cd - (self.MOD + 1) + cd = (self.MOD + 1) - cd + if cd == self.MOD: cd = 0 - if cd >= 10: - # Find the letter corresponding to the value - computed = next((a for a, val in self.WEIGHTS.items() if val == cd), None) - if computed is not None: - computed = computed - else: - computed = str(cd) - else: - computed = str(cd) - return str(computed) == check_digit + + if cd < 0 or cd >= self.MOD: + raise ValueError( + f"Invalid calculated check digit: {cd} expected range [0-35]", + ) + + if cd < 10: + return str(cd) + + return chr(cd - 10 + ord("A")) # 10=A, 11=B, ..., 35=Z + + def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + return self._check_digit(serial_number) == check_digit class SumProductWithWeightsAndModulo(ChecksumValidator): @@ -159,13 +173,11 @@ def __repr__(self): second_modulo=self.second_modulo, ) - def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> int: total = 0 for digit, weight in zip(serial_number, self.weights): total += int(digit) * weight - - check = total % self.first_modulo % self.second_modulo - return check == int(check_digit) + return total % self.first_modulo % self.second_modulo class Luhn(ChecksumValidator): @@ -174,7 +186,7 @@ class Luhn(ChecksumValidator): See https://en.wikipedia.org/wiki/Luhn_algorithm """ - def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> int: total = 0 digits = list(serial_number)[::-1] for i, c in enumerate(digits): @@ -187,4 +199,4 @@ def passes(self, serial_number: SerialNumber, check_digit: str) -> bool: check = total % 10 if check != 0: check = 10 - check - return check == int(check_digit) + return check From 63d9e7a3777b9ed6979be0e229ea62c89c114897 Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 16 Aug 2025 10:32:29 -0700 Subject: [PATCH 10/10] Regenerated _generated.py based on the latest tracking_number_data data. --- tracking_numbers/_generated.py | 4532 +++++++++++++++++++++++++++++--- 1 file changed, 4237 insertions(+), 295 deletions(-) diff --git a/tracking_numbers/_generated.py b/tracking_numbers/_generated.py index c2d286e..35a2d20 100644 --- a/tracking_numbers/_generated.py +++ b/tracking_numbers/_generated.py @@ -1,5 +1,4 @@ # DO NOT EDIT - Generated by codegen.py - import re from tracking_numbers.checksum_validator import Luhn @@ -22,48 +21,108 @@ DEFINITIONS = [ TrackingNumberDefinition( - courier=Courier(code="cdl", name="CDL"), - product=Product(name="CDL Last Mile Solutions"), + courier=Courier(code="lasership", name="LaserShip"), + product=Product(name="LaserShip LX"), + number_regex=re.compile( + "\\s*L\\s*[AIEHNX]\\s*[1-3]\\s*(?P([0-9]\\s*){7,7})\\s*", + ), + tracking_url_template=None, + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=None, + ), + TrackingNumberDefinition( + courier=Courier(code="lasership", name="LaserShip"), + product=Product(name="LaserShip 1LS7 (15)"), number_regex=re.compile( - "\\s*(?=.*[a-z])(?P([0-9a-f]\\s*){10,10})\\s*", + "\\s*1\\s*L\\s*S\\s*7\\s*[12]\\s*([0-9]\\s*){4,4}(?P([0-9]\\s*){6,6})\\s*", ), - tracking_url_template="https://ship.cdldelivers.com/Xcelerator/Tracking/Tracking?packageitemrefno=%s", + tracking_url_template=None, + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=None, + ), + TrackingNumberDefinition( + courier=Courier(code="lasership", name="LaserShip"), + product=Product(name="LaserShip 1LS7 (18)"), + number_regex=re.compile( + "\\s*1\\s*L\\s*S\\s*7\\s*[12]\\s*([0-9]\\s*){2,2}\\s*0\\s*1\\s*[1234]\\s*\\s*(?P([0-9]\\s*){6,6})-\\s*1\\s*", + ), + tracking_url_template=None, serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=None, - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="dhl", name="DHL"), product=Product(name="DHL Express"), number_regex=re.compile( - "\\s*(?P([0-9]\\s*){9})(?P([0-9]\\s*))", + "\\s*(?P([0-9]\\s*){9,10})(?P([0-9]\\s*))", ), tracking_url_template="http://www.dhl.com/en/express/tracking.html?brand=DHL&AWB=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=Mod7(), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="dhl", name="DHL"), - product=Product(name="DHL Express Air"), + product=Product(name="DHL Express (Piece ID)"), + number_regex=re.compile("\\s*(J[A-Z]{2,3})(?P([0-9]\\s*){9,10})"), + tracking_url_template="http://www.dhl.com/en/express/tracking.html?brand=DHL&AWB=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=None, + ), + TrackingNumberDefinition( + courier=Courier(code="dhl", name="DHL"), + product=Product(name="DHL E-Commerce"), number_regex=re.compile( - "\\s*(?P([0-9]\\s*){10})(?P[0-9]\\s*)", + "(?:GM|LX|RX|UV|CN|SG|TH|IN|HK|MY)\\s*(?P(?=[0-9A-Z\\s]{10,39}\\b)(?=[^0-9A-Z]*[0-9])[0-9A-Z\\s]{10,39})", ), tracking_url_template="http://www.dhl.com/en/express/tracking.html?brand=DHL&AWB=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), - checksum_validator=Mod7(), - additional_validations=[], + additional=[], + additional_validator=None, + checksum_validator=None, + ), + TrackingNumberDefinition( + courier=Courier(code="dhl", name="DHL"), + product=Product(name="DHL E-Commerce (14)"), + number_regex=re.compile("\\s*\\b(?P(?:[0-9]\\s*){14})\\b"), + tracking_url_template="http://www.dhl.com/en/express/tracking.html?brand=DHL&AWB=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=None, ), TrackingNumberDefinition( courier=Courier(code="amazon", name="Amazon"), product=Product(name="Amazon Logistics"), number_regex=re.compile( - "\\s*T\\s*B\\s*A\\s*(?P([0-9]\\s*){12,12})\\s*", + "\\s*T\\s*B\\s*[ACM]\\s*(?P([0-9]\\s*){12,12})\\s*", + ), + tracking_url_template=None, + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=None, + ), + TrackingNumberDefinition( + courier=Courier(code="amazon", name="Amazon"), + product=Product(name="Amazon International"), + number_regex=re.compile( + "\\s*[AFC]\\s*(?P([0-9]\\s*){10,10})\\s*", ), tracking_url_template=None, serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=None, - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="usps", name="United States Postal Service"), @@ -73,20 +132,23 @@ ), tracking_url_template="https://tools.usps.com/go/TrackConfirmAction?tLabels=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), - checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), - additional_validations=[ - AdditionalValidation( + additional=[ + Additional( name="Service Type", regex_group_name="ServiceType", value_matchers=[ - ExactValueMatcher(value="03"), - ExactValueMatcher(value="71"), - ExactValueMatcher(value="73"), - ExactValueMatcher(value="77"), - ExactValueMatcher(value="81"), + (ExactValueMatcher(value="71"), {"name": "Certified Mail"}), + (ExactValueMatcher(value="73"), {"name": "Insured Mail"}), + (ExactValueMatcher(value="77"), {"name": "Registered Mail"}), + ( + ExactValueMatcher(value="81"), + {"name": "Return Receipt For Merchanise"}, + ), ], ), ], + additional_validator=None, + checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), ), TrackingNumberDefinition( courier=Courier(code="usps", name="United States Postal Service"), @@ -96,8 +158,9 @@ ), tracking_url_template="https://tools.usps.com/go/TrackConfirmAction?tLabels=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="usps", name="United States Postal Service"), @@ -112,8 +175,30 @@ content="91", ), ), + additional=[ + Additional( + name="Service Type", + regex_group_name="ServiceType", + value_matchers=[ + (ExactValueMatcher(value="11"), {"name": "First Class (R)"}), + (ExactValueMatcher(value="29"), {"name": "Fedex Smart Post"}), + ], + ), + ], + additional_validator=None, + checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), + ), + TrackingNumberDefinition( + courier=Courier(code="canada_post", name="Canada Post"), + product=Product(name="Canada Post (16)"), + number_regex=re.compile( + "\\s*(?P(?P([0-9]\\s*){7})([0-9]\\s*){8})(?P[0-9]\\s*)", + ), + tracking_url_template="https://www.canadapost-postescanada.ca/track-reperage/en#/search?searchFor=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="fedex", name="FedEx"), @@ -123,40 +208,54 @@ ), tracking_url_template="https://www.fedex.com/apps/fedextrack/?tracknumbers=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=SumProductWithWeightsAndModulo( weights=[3, 1, 7, 3, 1, 7, 3, 1, 7, 3, 1], first_modulo=11, second_modulo=10, ), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="fedex", name="FedEx"), product=Product(name="FedEx Express (34)"), number_regex=re.compile( - "\\s*1\\s*0\\s*0\\s*[0-9]\\s*[0-9]\\s*([0-9]\\s*){10}(?P([0-9]\\s*){5})(?P([0-9]\\s*){13})(?P[0-9]\\s*)", + "\\s*1\\s*0\\s*[0-9]\\s*[0-9]\\s*[0-9]\\s*([0-9]\\s*){10}(?P([0-9]\\s*){5})(?P([0-9]\\s*){13})(?P[0-9]\\s*)", ), tracking_url_template="https://www.fedex.com/apps/fedextrack/?tracknumbers=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=SumProductWithWeightsAndModulo( weights=[1, 7, 3, 1, 7, 3, 1, 7, 3, 1, 7, 3, 1], first_modulo=11, second_modulo=10, ), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="fedex", name="FedEx"), product=Product(name="FedEx SmartPost"), number_regex=re.compile( - "\\s*(?P9\\s*2\\s*)?(?P(?P([0-9]\\s*){3})(?P([0-9]\\s*){9})(?P([0-9]\\s*){7}))(?P([0-9]\\s*))", + "\\s*(?:(?:(?P4\\s*2\\s*0\\s*)(?P([0-9]\\s*){5}))?(?P9\\s*2\\s*))?(?P(?P([0-9]\\s*){2})(?P2\\s*9\\s*)(?P([0-9]\\s*){8})(?P([0-9]\\s*){11}|([0-9]\\s*){7}))(?P([0-9]\\s*))", ), tracking_url_template="https://www.fedex.com/apps/fedextrack/?tracknumbers=%s", serial_number_parser=DefaultSerialNumberParser( prepend_if=PrependIf(matches_regex=re.compile("^(?!92).+"), content="92"), ), + additional=[ + Additional( + name="Service Type", + regex_group_name="ServiceType", + value_matchers=[ + ( + RegexValueMatcher(pattern=re.compile(".")), + {"name": "Delivered by USPS"}, + ), + ], + ), + ], + additional_validator=None, checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="fedex", name="FedEx"), @@ -166,8 +265,9 @@ ), tracking_url_template="https://www.fedex.com/apps/fedextrack/?tracknumbers=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=Mod10(odds_multiplier=3, evens_multiplier=1), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="fedex", name="FedEx"), @@ -177,19 +277,23 @@ ), tracking_url_template="https://www.fedex.com/apps/fedextrack/?tracknumbers=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), - checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), - additional_validations=[ - AdditionalValidation( + additional=[ + Additional( name="Container Type", regex_group_name="ShippingContainerType", value_matchers=[ - ExactValueMatcher(value="00"), - ExactValueMatcher(value="01"), - ExactValueMatcher(value="02"), - ExactValueMatcher(value="04"), + (ExactValueMatcher(value="00"), {"name": "case/carton"}), + (ExactValueMatcher(value="01"), {"name": "pallet"}), + (ExactValueMatcher(value="02"), {"name": "larger than a pallet"}), + ( + ExactValueMatcher(value="04"), + {"name": "internally defined for intra-company use"}, + ), ], ), ], + additional_validator=None, + checksum_validator=Mod10(odds_multiplier=1, evens_multiplier=3), ), TrackingNumberDefinition( courier=Courier(code="fedex", name="FedEx"), @@ -199,8 +303,9 @@ ), tracking_url_template="https://www.fedex.com/apps/fedextrack/?tracknumbers=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=Mod10(odds_multiplier=3, evens_multiplier=1), - additional_validations=[], ), TrackingNumberDefinition( courier=Courier(code="fedex", name="FedEx"), @@ -210,67 +315,205 @@ ), tracking_url_template="https://www.fedex.com/apps/fedextrack/?tracknumbers=%s", serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, checksum_validator=SumProductWithWeightsAndModulo( weights=[1, 7, 3, 1, 7, 3, 1, 7, 3, 1, 7, 3, 1], first_modulo=11, second_modulo=10, ), - additional_validations=[], + ), + TrackingNumberDefinition( + courier=Courier(code="landmark", name="Landmark Global LTN"), + product=Product(name="Landmark Global LTN"), + number_regex=re.compile( + "\\s*L\\s*T\\s*N\\s*(?P([0-9]\\s*){8})\\s*N\\s*1", + ), + tracking_url_template="https://track.landmarkglobal.com/?search=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=None, + ), + TrackingNumberDefinition( + courier=Courier(code="old_dominion", name="Old Dominion Freight Line"), + product=Product(name="Old Dominion"), + number_regex=re.compile( + "\\s*(?P(7\\s*7\\s*[78]\\s*|0\\s*7\\s*2\\s*|7\\s*8\\s*0\\s*)([0-9]\\s*){7})(?P[0-9]\\s*)", + ), + tracking_url_template="https://www.odfl.com/us/en/tools/trace-track-ltl-freight/trace.html?proNumbers=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=Luhn(), + ), + TrackingNumberDefinition( + courier=Courier(code="old_dominion", name="Old Dominion Freight Line"), + product=Product(name="Old Dominion Guaranteed Shipment"), + number_regex=re.compile( + "\\s*(?P8\\s*0\\s*([0-9]\\s*){8})(?P[0-9]\\s*)", + ), + tracking_url_template="https://www.odfl.com/us/en/tools/trace-track-ltl-freight/trace.html?proNumbers=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=Luhn(), ), TrackingNumberDefinition( courier=Courier(code="ups", name="UPS"), product=Product(name="UPS"), number_regex=re.compile( - "\\s*1\\s*Z\\s*(?P(?P(?:[A-Z0-9]\\s*){6,6})(?P(?:[A-Z0-9]\\s*){2,2})(?P(?:[A-Z0-9]\\s*){7,7}))(?P[A-Z0-9]\\s*)", + "\\s*1\\s*Z\\s*(?P(?P(?:[A-Z0-9]\\s*){6,6})(?P(?:[A-Z0-9]\\s*){2,2})(?P(?:[A-Z0-9]\\s*){7,7}))(?P[0-9]\\s*)", ), tracking_url_template="https://wwwapps.ups.com/WebTracking/track?track=yes&trackNums=%s", serial_number_parser=UPSSerialNumberParser(), - checksum_validator=Mod10(odds_multiplier=2, evens_multiplier=1), - additional_validations=[ - AdditionalValidation( + additional=[ + Additional( name="Service Type", regex_group_name="ServiceType", value_matchers=[ - ExactValueMatcher(value="01"), - ExactValueMatcher(value="02"), - ExactValueMatcher(value="03"), - ExactValueMatcher(value="04"), - ExactValueMatcher(value="12"), - ExactValueMatcher(value="13"), - ExactValueMatcher(value="15"), - ExactValueMatcher(value="22"), - ExactValueMatcher(value="32"), - ExactValueMatcher(value="33"), - ExactValueMatcher(value="41"), - ExactValueMatcher(value="42"), - ExactValueMatcher(value="44"), - ExactValueMatcher(value="66"), - ExactValueMatcher(value="67"), - ExactValueMatcher(value="68"), - ExactValueMatcher(value="72"), - ExactValueMatcher(value="78"), - ExactValueMatcher(value="90"), - ExactValueMatcher(value="A0"), - ExactValueMatcher(value="A1"), - ExactValueMatcher(value="A2"), - ExactValueMatcher(value="A8"), - ExactValueMatcher(value="A9"), - ExactValueMatcher(value="AA"), - ExactValueMatcher(value="YW"), + ( + ExactValueMatcher(value="01"), + {"name": "UPS United States Next Day Air (Red)"}, + ), + ( + ExactValueMatcher(value="02"), + {"name": "UPS United States Second Day Air (Blue)"}, + ), + ( + ExactValueMatcher(value="03"), + {"name": "UPS United States Ground"}, + ), + ( + ExactValueMatcher(value="12"), + {"name": "UPS United States Third Day Select"}, + ), + ( + ExactValueMatcher(value="13"), + {"name": "UPS United States Next Day Air Saver (Red Saver)"}, + ), + ( + ExactValueMatcher(value="15"), + {"name": "UPS United States Next Day Air Early A.M."}, + ), + ( + ExactValueMatcher(value="22"), + { + "name": "UPS United States Ground - Returns Plus - Three Pickup Attempts", + }, + ), + ( + ExactValueMatcher(value="32"), + {"name": "UPS United States Next Day Air Early A.M. - COD"}, + ), + ( + ExactValueMatcher(value="33"), + { + "name": "UPS United States Next Day Air Early A.M. - Saturday Delivery, COD", + }, + ), + ( + ExactValueMatcher(value="41"), + { + "name": "UPS United States Next Day Air Early A.M. - Saturday Delivery", + }, + ), + ( + ExactValueMatcher(value="42"), + {"name": "UPS United States Ground - Signature Required"}, + ), + ( + ExactValueMatcher(value="44"), + {"name": "UPS United States Next Day Air - Saturday Delivery"}, + ), + ( + ExactValueMatcher(value="66"), + {"name": "UPS United States Worldwide Express"}, + ), + ( + ExactValueMatcher(value="72"), + {"name": "UPS United States Ground - Collect on Delivery"}, + ), + ( + ExactValueMatcher(value="78"), + { + "name": "UPS United States Ground - Returns Plus - One Pickup Attempt", + }, + ), + ( + ExactValueMatcher(value="90"), + { + "name": "UPS United States Ground - Returns - UPS Prints and Mails Label", + }, + ), + ( + ExactValueMatcher(value="A0"), + { + "name": "UPS United States Next Day Air Early A.M. - Adult Signature Required", + }, + ), + ( + ExactValueMatcher(value="A1"), + { + "name": "UPS United States Next Day Air Early A.M. - Saturday Delivery, Adult Signature Required", + }, + ), + ( + ExactValueMatcher(value="A2"), + { + "name": "UPS United States Next Day Air - Adult Signature Required", + }, + ), + ( + ExactValueMatcher(value="A8"), + {"name": "UPS United States Ground - Adult Signature Required"}, + ), + ( + ExactValueMatcher(value="A9"), + { + "name": "UPS United States Next Day Air Early A.M. - Adult Signature Required, COD", + }, + ), + ( + ExactValueMatcher(value="AA"), + { + "name": "UPS United States Next Day Air Early A.M. - Saturday Delivery, Adult Signature Required, COD", + }, + ), + ( + ExactValueMatcher(value="YW"), + {"name": "UPS SurePost - Delivered by the USPS"}, + ), ], ), ], + additional_validator=None, + checksum_validator=Mod10(odds_multiplier=2, evens_multiplier=1), ), TrackingNumberDefinition( courier=Courier(code="ups", name="UPS"), - product=Product(name="UPS Mail Innovations - Sequence Number"), + product=Product(name="UPS Waybill"), number_regex=re.compile( - "\\s*8\\s*0\\s*(?P([0-9]\\s*){16,16})\\s*", + "\\s*(?P([AHJKTV]\\s*){1})(?P(?:[0-9]\\s*){9})(?P[0-9]\\s*){1}", ), tracking_url_template="https://wwwapps.ups.com/WebTracking/track?track=yes&trackNums=%s", serial_number_parser=UPSSerialNumberParser(), - checksum_validator=None, - additional_validations=[], + additional=[ + Additional( + name="Service Type", + regex_group_name="ServiceType", + value_matchers=[ + (ExactValueMatcher(value="J"), {"name": "UPS Next Day Express"}), + (ExactValueMatcher(value="K"), {"name": "UPS Ground"}), + ( + ExactValueMatcher(value="V"), + {"name": "UPS WorldWide Express Saver"}, + ), + ], + ), + ], + additional_validator=None, + checksum_validator=Mod10(odds_multiplier=2, evens_multiplier=1), ), TrackingNumberDefinition( courier=Courier(code="s10", name="S10 International Standard"), @@ -280,236 +523,3935 @@ ), tracking_url_template=None, serial_number_parser=DefaultSerialNumberParser(prepend_if=None), - checksum_validator=S10(), - additional_validations=[ - AdditionalValidation( + additional=[ + Additional( name="Service Type", regex_group_name="ServiceType", value_matchers=[ - RegexValueMatcher(pattern=re.compile("E[A-Z]")), - RegexValueMatcher(pattern=re.compile("L[A-Z]")), - RegexValueMatcher(pattern=re.compile("M[A-Z]")), - RegexValueMatcher(pattern=re.compile("Q[A-M]")), - RegexValueMatcher(pattern=re.compile("R[A-Z]")), - RegexValueMatcher(pattern=re.compile("U[A-Z]")), - RegexValueMatcher(pattern=re.compile("V[A-Z]")), - RegexValueMatcher(pattern=re.compile("C[A-Z]")), - RegexValueMatcher(pattern=re.compile("H[A-Z]")), - RegexValueMatcher( - pattern=re.compile("([BDNPZ][A-Z]|A[V-Z]|G[AD])"), + ( + RegexValueMatcher(pattern=re.compile("E[A-Z]")), + { + "name": "EMS", + "description": "International Express Mail Service", + }, + ), + ( + RegexValueMatcher(pattern=re.compile("L[A-Z]")), + {"name": "Letter Post Express", "description": ""}, + ), + ( + RegexValueMatcher(pattern=re.compile("M[A-Z]")), + { + "name": "Letter Post M-bag", + "description": "Direct sacks of printed matter sent to a single foreign addressee at a single address", + }, + ), + ( + RegexValueMatcher(pattern=re.compile("Q[A-M]")), + { + "name": "Letter Post IBRS", + "description": "International Business Reply Service", + }, + ), + ( + RegexValueMatcher(pattern=re.compile("R[A-Z]")), + { + "name": "Letter Post Registered", + "description": "Prepaid first-class mail that is recorded by the post office before being sent and at each point along its route to safeguard against loss, theft, or damage.", + }, + ), + ( + RegexValueMatcher(pattern=re.compile("U[A-Z]")), + {"name": "Letter Post Misc", "description": ""}, + ), + ( + RegexValueMatcher(pattern=re.compile("V[A-Z]")), + {"name": "Letter Post Insured", "description": ""}, + ), + ( + RegexValueMatcher(pattern=re.compile("C[A-Z]")), + {"name": "Parcel Post", "description": ""}, + ), + ( + RegexValueMatcher(pattern=re.compile("H[A-Z]")), + {"name": "Parcel Post (e-commerce)", "description": ""}, + ), + ( + RegexValueMatcher( + pattern=re.compile("([BDNPZ][A-Z]|A[V-Z]|G[AD])"), + ), + { + "name": "Domestic", + "description": "Mail designated for domestic, bilateral, or multilateral use", + }, ), ], ), - AdditionalValidation( + Additional( name="Courier", regex_group_name="CountryCode", value_matchers=[ - ExactValueMatcher(value="AF"), - ExactValueMatcher(value="AL"), - ExactValueMatcher(value="DZ"), - ExactValueMatcher(value="AO"), - ExactValueMatcher(value="AG"), - ExactValueMatcher(value="AR"), - ExactValueMatcher(value="AM"), - ExactValueMatcher(value="AU"), - ExactValueMatcher(value="AT"), - ExactValueMatcher(value="AZ"), - ExactValueMatcher(value="BS"), - ExactValueMatcher(value="BH"), - ExactValueMatcher(value="BD"), - ExactValueMatcher(value="BB"), - ExactValueMatcher(value="BY"), - ExactValueMatcher(value="BE"), - ExactValueMatcher(value="BZ"), - ExactValueMatcher(value="BJ"), - ExactValueMatcher(value="BT"), - ExactValueMatcher(value="BO"), - ExactValueMatcher(value="BA"), - ExactValueMatcher(value="BW"), - ExactValueMatcher(value="BR"), - ExactValueMatcher(value="BN"), - ExactValueMatcher(value="BG"), - ExactValueMatcher(value="BF"), - ExactValueMatcher(value="BI"), - ExactValueMatcher(value="KH"), - ExactValueMatcher(value="CM"), - ExactValueMatcher(value="CA"), - ExactValueMatcher(value="CV"), - ExactValueMatcher(value="CF"), - ExactValueMatcher(value="TD"), - ExactValueMatcher(value="CL"), - ExactValueMatcher(value="CN"), - ExactValueMatcher(value="HK"), - ExactValueMatcher(value="CO"), - ExactValueMatcher(value="KM"), - ExactValueMatcher(value="CG"), - ExactValueMatcher(value="CR"), - ExactValueMatcher(value="HR"), - ExactValueMatcher(value="CU"), - ExactValueMatcher(value="CY"), - ExactValueMatcher(value="CZ"), - ExactValueMatcher(value="CI"), - ExactValueMatcher(value="KP"), - ExactValueMatcher(value="CD"), - ExactValueMatcher(value="DK"), - ExactValueMatcher(value="DJ"), - ExactValueMatcher(value="DM"), - ExactValueMatcher(value="DO"), - ExactValueMatcher(value="EC"), - ExactValueMatcher(value="EG"), - ExactValueMatcher(value="SV"), - ExactValueMatcher(value="GQ"), - ExactValueMatcher(value="ER"), - ExactValueMatcher(value="EE"), - ExactValueMatcher(value="ET"), - ExactValueMatcher(value="FJ"), - ExactValueMatcher(value="FI"), - ExactValueMatcher(value="FR"), - ExactValueMatcher(value="GA"), - ExactValueMatcher(value="GM"), - ExactValueMatcher(value="GE"), - ExactValueMatcher(value="DE"), - ExactValueMatcher(value="GH"), - ExactValueMatcher(value="GB"), - ExactValueMatcher(value="GR"), - ExactValueMatcher(value="GD"), - ExactValueMatcher(value="GT"), - ExactValueMatcher(value="GN"), - ExactValueMatcher(value="GW"), - ExactValueMatcher(value="GY"), - ExactValueMatcher(value="HT"), - ExactValueMatcher(value="HN"), - ExactValueMatcher(value="HU"), - ExactValueMatcher(value="IS"), - ExactValueMatcher(value="IN"), - ExactValueMatcher(value="ID"), - ExactValueMatcher(value="IR"), - ExactValueMatcher(value="IQ"), - ExactValueMatcher(value="IE"), - ExactValueMatcher(value="IL"), - ExactValueMatcher(value="IT"), - ExactValueMatcher(value="JM"), - ExactValueMatcher(value="JP"), - ExactValueMatcher(value="JO"), - ExactValueMatcher(value="KZ"), - ExactValueMatcher(value="KE"), - ExactValueMatcher(value="KI"), - ExactValueMatcher(value="KR"), - ExactValueMatcher(value="KW"), - ExactValueMatcher(value="KG"), - ExactValueMatcher(value="LA"), - ExactValueMatcher(value="LV"), - ExactValueMatcher(value="LB"), - ExactValueMatcher(value="LS"), - ExactValueMatcher(value="LR"), - ExactValueMatcher(value="LY"), - ExactValueMatcher(value="LI"), - ExactValueMatcher(value="LT"), - ExactValueMatcher(value="LU"), - ExactValueMatcher(value="MG"), - ExactValueMatcher(value="MW"), - ExactValueMatcher(value="MY"), - ExactValueMatcher(value="MV"), - ExactValueMatcher(value="ML"), - ExactValueMatcher(value="MT"), - ExactValueMatcher(value="MR"), - ExactValueMatcher(value="MU"), - ExactValueMatcher(value="MX"), - ExactValueMatcher(value="MD"), - ExactValueMatcher(value="MC"), - ExactValueMatcher(value="MN"), - ExactValueMatcher(value="ME"), - ExactValueMatcher(value="MA"), - ExactValueMatcher(value="MZ"), - ExactValueMatcher(value="MM"), - ExactValueMatcher(value="NA"), - ExactValueMatcher(value="NR"), - ExactValueMatcher(value="NP"), - ExactValueMatcher(value="NL"), - ExactValueMatcher(value="NZ"), - ExactValueMatcher(value="NI"), - ExactValueMatcher(value="NE"), - ExactValueMatcher(value="NG"), - ExactValueMatcher(value="NO"), - ExactValueMatcher(value="OM"), - ExactValueMatcher(value="PK"), - ExactValueMatcher(value="PA"), - ExactValueMatcher(value="PG"), - ExactValueMatcher(value="PY"), - ExactValueMatcher(value="PE"), - ExactValueMatcher(value="PH"), - ExactValueMatcher(value="PL"), - ExactValueMatcher(value="PT"), - ExactValueMatcher(value="QA"), - ExactValueMatcher(value="RO"), - ExactValueMatcher(value="RU"), - ExactValueMatcher(value="RW"), - ExactValueMatcher(value="KN"), - ExactValueMatcher(value="LC"), - ExactValueMatcher(value="VC"), - ExactValueMatcher(value="WS"), - ExactValueMatcher(value="SM"), - ExactValueMatcher(value="ST"), - ExactValueMatcher(value="SA"), - ExactValueMatcher(value="SN"), - ExactValueMatcher(value="RS"), - ExactValueMatcher(value="SC"), - ExactValueMatcher(value="SL"), - ExactValueMatcher(value="SG"), - ExactValueMatcher(value="SK"), - ExactValueMatcher(value="SI"), - ExactValueMatcher(value="SB"), - ExactValueMatcher(value="SO"), - ExactValueMatcher(value="ZA"), - ExactValueMatcher(value="SS"), - ExactValueMatcher(value="ES"), - ExactValueMatcher(value="LK"), - ExactValueMatcher(value="SD"), - ExactValueMatcher(value="SR"), - ExactValueMatcher(value="SZ"), - ExactValueMatcher(value="SE"), - ExactValueMatcher(value="CH"), - ExactValueMatcher(value="SY"), - ExactValueMatcher(value="TJ"), - ExactValueMatcher(value="TZ"), - ExactValueMatcher(value="TH"), - ExactValueMatcher(value="MK"), - ExactValueMatcher(value="TL"), - ExactValueMatcher(value="TG"), - ExactValueMatcher(value="TO"), - ExactValueMatcher(value="TT"), - ExactValueMatcher(value="TN"), - ExactValueMatcher(value="TR"), - ExactValueMatcher(value="TM"), - ExactValueMatcher(value="TV"), - ExactValueMatcher(value="UG"), - ExactValueMatcher(value="UA"), - ExactValueMatcher(value="AE"), - ExactValueMatcher(value="US"), - ExactValueMatcher(value="UY"), - ExactValueMatcher(value="UZ"), - ExactValueMatcher(value="VU"), - ExactValueMatcher(value="VA"), - ExactValueMatcher(value="VE"), - ExactValueMatcher(value="VN"), - ExactValueMatcher(value="YE"), - ExactValueMatcher(value="ZM"), - ExactValueMatcher(value="ZW"), - ], - ), - ], - ), - TrackingNumberDefinition( - courier=Courier(code="ontrac", name="OnTrac"), - product=Product(name="OnTrac"), - number_regex=re.compile( - "\\s*C\\s*(?P([0-9]\\s*){13})(?P[0-9]\\s*)", - ), - tracking_url_template="http://www.ontrac.com/trackingres.asp?tracking_number=%s", - serial_number_parser=DefaultSerialNumberParser( - prepend_if=PrependIf(matches_regex=re.compile("^(?!4).+$"), content="4"), - ), - checksum_validator=Mod10(odds_multiplier=2, evens_multiplier=1), - additional_validations=[], + ( + ExactValueMatcher(value="AF"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/afghanistan.html", + "courier": "Afghan Post", + "courier_url": "http://postalcode.afghanpost.gov.af/", + "country": "Afghanistan", + }, + ), + ( + ExactValueMatcher(value="AL"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/albania.html", + "courier": "Posta Shqiptare", + "courier_url": "http://www.postashqiptare.al/", + "country": "Albania", + }, + ), + ( + ExactValueMatcher(value="DZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/algeria.html", + "courier": "Algérie Poste", + "courier_url": "http://www.poste.dz/codepostal/", + "country": "Algeria", + }, + ), + ( + ExactValueMatcher(value="AO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/angola.html", + "courier": "Correios de Angola", + "courier_url": "http://www.correiosdeangola.co.ao/", + "country": "Angola", + }, + ), + ( + ExactValueMatcher(value="AG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/antigua-and-barbuda.html", + "courier": "Antigua Postal Services", + "courier_url": None, + "country": "Antigua and Barbuda", + }, + ), + ( + ExactValueMatcher(value="AR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/argentina.html", + "courier": "Correo Argentino", + "courier_url": "http://www.correoargentino.com.ar/formularios/cpa", + "country": "Argentina", + }, + ), + ( + ExactValueMatcher(value="AM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/armenia.html", + "courier": "Haypost - Armenian Postal Service", + "courier_url": "http://www.haypost.am/view-lang-eng-page-25.html", + "country": "Armenia", + }, + ), + ( + ExactValueMatcher(value="AU"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/australia.html", + "courier": "Australia Post", + "courier_url": "http://www1.auspost.com.au/postcodes/", + "country": "Australia", + }, + ), + ( + ExactValueMatcher(value="AT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/austria.html", + "courier": "Österreichische Post AG", + "courier_url": "http://www.post.at/en/index.php", + "country": "Austria", + }, + ), + ( + ExactValueMatcher(value="AZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/azerbaijan.html", + "courier": "Azarpoçt", + "courier_url": "http://www.azerpost.az/?options=content&id=188&language=en", + "country": "Azerbaijan", + }, + ), + ( + ExactValueMatcher(value="BS"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/bahamas.html", + "courier": "Bahamas Postal Service", + "courier_url": "http://www.bahamas.gov.bs/postalservice", + "country": "Bahamas", + }, + ), + ( + ExactValueMatcher(value="BH"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/bahrain.html", + "courier": "Bahrain Post", + "courier_url": "http://www.transportation.gov.bh/en/modules.php?name=Content&pa=showpage&pid=97", + "country": "Bahrain", + }, + ), + ( + ExactValueMatcher(value="BD"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/bangladesh.html", + "courier": "Bangladesh Post Office", + "courier_url": "http://www.bangladeshpost.gov.bd/PostCode.asp", + "country": "Bangladesh", + }, + ), + ( + ExactValueMatcher(value="BB"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/barbados.html", + "courier": "Barbados Postal Service", + "courier_url": "http://www.bps.gov.bb/", + "country": "Barbados", + }, + ), + ( + ExactValueMatcher(value="BY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/belarus.html", + "courier": "Belpochta", + "courier_url": "http://zip.belpost.by/", + "country": "Belarus", + }, + ), + ( + ExactValueMatcher(value="BE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/belgium.html", + "courier": "bpost", + "courier_url": "http://www.bpost.be/site/fr/residential/customerservice/search/postal_codes.html", + "country": "Belgium", + }, + ), + ( + ExactValueMatcher(value="BZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/belize.html", + "courier": "Belize Postal Service", + "courier_url": "http://www.belizepostalservice.gov.bz/site/", + "country": "Belize", + }, + ), + ( + ExactValueMatcher(value="BJ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/benin.html", + "courier": "La Poste du Bénin", + "courier_url": "http://www.laposte.bj/index1.php?id_page=1", + "country": "Benin", + }, + ), + ( + ExactValueMatcher(value="BT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/bhutan.html", + "courier": "Bhutan Post", + "courier_url": "http://www.bhutanpost.com.bt/postcode/postcode.php", + "country": "Bhutan", + }, + ), + ( + ExactValueMatcher(value="BO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/bolivia.html", + "courier": "ECOBOL – Empresa de Correos de Bolivia", + "courier_url": "http://www.correosbolivia.com/", + "country": "Bolivia", + }, + ), + ( + ExactValueMatcher(value="BA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/bosnia-and-herzegovina.html", + "courier": "JP BH POŠTA d.o.o. Sarajevo", + "courier_url": "http://www.post.ba/postanski_brojevi_bih.php", + "country": "Bosnia and Herzegovina", + }, + ), + ( + ExactValueMatcher(value="BW"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/botswana.html", + "courier": "BotswanaPost", + "courier_url": "http://www.botspost.co.bw/", + "country": "Botswana", + }, + ), + ( + ExactValueMatcher(value="BR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/brazil.html", + "courier": "CORREIOS", + "courier_url": "http://www.buscacep.correios.com.br/servicos/dnec/index.do", + "country": "Brazil", + }, + ), + ( + ExactValueMatcher(value="BN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/brunei-darussalam.html", + "courier": "Brunei Postal Services", + "courier_url": "http://www.post.gov.bn/", + "country": "Brunei Darussalam", + }, + ), + ( + ExactValueMatcher(value="BG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/bulgaria-rep.html", + "courier": "Bulgarian Posts", + "courier_url": "http://www.bgpost.bg/?cid=131", + "country": "Bulgaria (Rep.)", + }, + ), + ( + ExactValueMatcher(value="BF"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/burkina-faso.html", + "courier": "SONAPOST", + "courier_url": "http://www.sonapost.bf/", + "country": "Burkina Faso", + }, + ), + ( + ExactValueMatcher(value="BI"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/burundi.html", + "courier": "RNP – Régie nationale des postes", + "courier_url": "http://www.poste.bi/", + "country": "Burundi", + }, + ), + ( + ExactValueMatcher(value="KH"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/cambodia.html", + "courier": "Ministry of Posts and Telecommunications", + "courier_url": "http://www.mptc.gov.kh/", + "country": "Cambodia", + }, + ), + ( + ExactValueMatcher(value="CM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/cameroon.html", + "courier": "CAMPOST – Cameroon Postal Services", + "courier_url": "http://campostonline.com/", + "country": "Cameroon", + }, + ), + ( + ExactValueMatcher(value="CA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/canada.html", + "courier": "Canada Post", + "courier_url": "http://www.canadapost.ca/cpotools/apps/fpc/personal/findByCity?execution=e1s1", + "country": "Canada", + }, + ), + ( + ExactValueMatcher(value="CV"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/cape-verde.html", + "courier": "Correios de Cabo Verde", + "courier_url": "http://www.correios.cv/", + "country": "Cape Verde", + }, + ), + ( + ExactValueMatcher(value="CF"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/central-african-rep.html", + "courier": "Direction des services postaux de l'Office National des Postes et de l'Épargne", + "courier_url": None, + "country": "Central African Rep.", + }, + ), + ( + ExactValueMatcher(value="TD"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/chad.html", + "courier": "Société tchadienne des postes et de l'épargne", + "courier_url": None, + "country": "Chad", + }, + ), + ( + ExactValueMatcher(value="CL"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/chile.html", + "courier": "Correos de Chile", + "courier_url": "http://www.correos.cl/SitePages/home.aspx", + "country": "Chile", + }, + ), + ( + ExactValueMatcher(value="CN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/china-peoples-rep.html", + "courier": "China Post", + "courier_url": "http://www.cpdc.com.cn/web/index.php?m=postsearch&c=index&a=init&t=addr", + "country": "China (People's Rep.)", + }, + ), + ( + ExactValueMatcher(value="HK"), + { + "upu_reference_url": "", + "courier": "Hong Kong Post", + "courier_url": "http://www.hongkongpost.hk", + "country": "China", + }, + ), + ( + ExactValueMatcher(value="CO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/colombia.html", + "courier": "4-72 La Red Postal de Colombia", + "courier_url": "http://visor.codigopostal.gov.co/472/visor/", + "country": "Colombia", + }, + ), + ( + ExactValueMatcher(value="KM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/comoros.html", + "courier": "Societé Nationale des Postes et des Services Financiers", + "courier_url": "http://www.lapostecomores.com/bureaux.php", + "country": "Comoros", + }, + ), + ( + ExactValueMatcher(value="CG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/congo-rep.html", + "courier": "Congolese Posts and Savings Company", + "courier_url": None, + "country": "Congo (Rep.)", + }, + ), + ( + ExactValueMatcher(value="CR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/costa-rica.html", + "courier": "Correos de Costa Rica", + "courier_url": "https://www.correos.go.cr/nosotros/codigopostal/busqueda.html", + "country": "Costa Rica", + }, + ), + ( + ExactValueMatcher(value="HR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/croatia.html", + "courier": "Hrvatska Posta - Croatian Post", + "courier_url": "http://www.posta.hr/default.aspx?pretpum&id=3417", + "country": "Croatia", + }, + ), + ( + ExactValueMatcher(value="CU"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/cuba.html", + "courier": "Ministerio de la Informática y las comunicaciones de Cuba", + "courier_url": "http://www.mic.gov.cu/", + "country": "Cuba", + }, + ), + ( + ExactValueMatcher(value="CY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/cyprus.html", + "courier": "Cyprus Post", + "courier_url": "http://www.mcw.gov.cy/mcw/dps/dps.nsf/index_en/index_en?OpenDocument", + "country": "Cyprus", + }, + ), + ( + ExactValueMatcher(value="CZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/czech-rep.html", + "courier": "Česká Pošta", + "courier_url": "http://psc.cpost.cz/CleanForm.action?request_locale=en", + "country": "Czech Rep.", + }, + ), + ( + ExactValueMatcher(value="CI"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/cote-divoire-rep.html", + "courier": "La Poste de Côte d’Ivoire", + "courier_url": "http://www.laposte.ci/bureau.php", + "country": "Côte d'Ivoire (Rep.)", + }, + ), + ( + ExactValueMatcher(value="KP"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/dem-peoples-rep-of-korea.html", + "courier": "Korea Post and Telecommunications Corporation", + "courier_url": None, + "country": "Dem People's Rep. of Korea", + }, + ), + ( + ExactValueMatcher(value="CD"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/democratic-republic-of-the-congo.html", + "courier": "Congolese Posts and Telecommunications Corporation", + "courier_url": None, + "country": "Democratic Republic of the Congo", + }, + ), + ( + ExactValueMatcher(value="DK"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/denmark.html", + "courier": "Post Danmark", + "courier_url": "http://www.postdanmark.dk/en/find_postcode/Pages/home.aspx", + "country": "Denmark", + }, + ), + ( + ExactValueMatcher(value="DJ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/djibouti.html", + "courier": "La Poste de Djibouti", + "courier_url": None, + "country": "Djibouti", + }, + ), + ( + ExactValueMatcher(value="DM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/dominica.html", + "courier": "General Post Office", + "courier_url": "http://publicworks.gov.dm/index.php/divisions/general-post-office/20-gpo-about-us", + "country": "Dominica", + }, + ), + ( + ExactValueMatcher(value="DO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/dominican-republic.html", + "courier": "INPOSDOM – Instituto Postal Dominicano", + "courier_url": "http://www.inposdom.gob.do/servicios/codigo-postal", + "country": "Dominican Republic", + }, + ), + ( + ExactValueMatcher(value="EC"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/ecuador.html", + "courier": "Correos del Ecuador", + "courier_url": "http://www.codigopostal.gob.ec/#", + "country": "Ecuador", + }, + ), + ( + ExactValueMatcher(value="EG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/egypt.html", + "courier": "Egypt Post", + "courier_url": "http://www.egyptpost.org/", + "country": "Egypt", + }, + ), + ( + ExactValueMatcher(value="SV"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/el-salvador.html", + "courier": "Correos de El Salvador", + "courier_url": "http://www.correos.gob.sv/", + "country": "El Salvador", + }, + ), + ( + ExactValueMatcher(value="GQ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/equatorial-guinea.html", + "courier": "Equatorial Guinea Post", + "courier_url": None, + "country": "Equatorial Guinea", + }, + ), + ( + ExactValueMatcher(value="ER"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/eritrea.html", + "courier": "Eritrean Postal Service", + "courier_url": "http://www.eriposta.com/", + "country": "Eritrea", + }, + ), + ( + ExactValueMatcher(value="EE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/estonia.html", + "courier": "Eesti Post", + "courier_url": "http://www.post.ee/", + "country": "Estonia", + }, + ), + ( + ExactValueMatcher(value="ET"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/ethiopia.html", + "courier": "Ethiopian postal service", + "courier_url": "http://www.ethiopostal.com/", + "country": "Ethiopia", + }, + ), + ( + ExactValueMatcher(value="FJ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/fiji.html", + "courier": "Post Fiji", + "courier_url": "http://www.postfiji.com.fj/", + "country": "Fiji", + }, + ), + ( + ExactValueMatcher(value="FI"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/finland-including-the-aaland-islands.html", + "courier": "Posti Ltd", + "courier_url": "http://www.verkkoposti.com/e3/english/postalcodecatalog", + "country": "Finland (including the Åland Islands)", + }, + ), + ( + ExactValueMatcher(value="FR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/france.html", + "courier": "La Poste", + "courier_url": "http://www.laposte.fr/Entreprise/Outils-Indispensables/Outils/Trouvez-un-code-postal", + "country": "France", + }, + ), + ( + ExactValueMatcher(value="GA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/gabon.html", + "courier": "La Poste SA", + "courier_url": "http://www.laposte.ga/", + "country": "Gabon", + }, + ), + ( + ExactValueMatcher(value="GM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/gambia.html", + "courier": "Gambia Postal services Corporation", + "courier_url": "http://www.gampost.gm/", + "country": "Gambia", + }, + ), + ( + ExactValueMatcher(value="GE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/georgia.html", + "courier": "Georgian Post", + "courier_url": "http://www.georgianpost.ge/?site-lang=en&site-path=help/zipcodes/&letter=A", + "country": "Georgia", + }, + ), + ( + ExactValueMatcher(value="DE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/germany.html", + "courier": "Deutsche Post", + "courier_url": "http://www.postdirekt.de/plzserver/PlzSearchServlet?lang=en_GB&id=viewstreet", + "country": "Germany", + }, + ), + ( + ExactValueMatcher(value="GH"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/ghana.html", + "courier": "Ghana Post", + "courier_url": "http://www.ghanapostgh.com/", + "country": "Ghana", + }, + ), + ( + ExactValueMatcher(value="GB"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/great-britain.html", + "courier": "Royal Mail Group plc", + "courier_url": "http://www.royalmail.com/postcode-finder?gear=postcode&campaignid=postcodefinder_redirect", + "country": "Great Britain", + }, + ), + ( + ExactValueMatcher(value="GR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/greece.html", + "courier": "Hellenic Post ELTA", + "courier_url": "http://www.elta.gr/en-us/findapostcode.aspx", + "country": "Greece", + }, + ), + ( + ExactValueMatcher(value="GD"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/grenada.html", + "courier": "Grenada Postal Corporation", + "courier_url": "http://www.grenadapostal.com/index.html", + "country": "Grenada", + }, + ), + ( + ExactValueMatcher(value="GT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/guatemala.html", + "courier": "El Correo", + "courier_url": "http://www.elcorreo.com.gt/cdgcorreo/index.php?option=com_content&view=article&id=104&Itemid=233", + "country": "Guatemala", + }, + ), + ( + ExactValueMatcher(value="GN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/guinea.html", + "courier": "Office de la poste guinéenne", + "courier_url": None, + "country": "Guinea", + }, + ), + ( + ExactValueMatcher(value="GW"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/guinea-bissau.html", + "courier": "Correios da Guiné-Bissau", + "courier_url": None, + "country": "Guinea-Bissau", + }, + ), + ( + ExactValueMatcher(value="GY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/guyana.html", + "courier": "Guyana Post Office Corporation", + "courier_url": "http://guypost.gy/gpoc/", + "country": "Guyana", + }, + ), + ( + ExactValueMatcher(value="HT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/haiti.html", + "courier": "Office des Postes d’Haiti", + "courier_url": "http://postehaiti.gouv.ht/notre-reseau-postal", + "country": "Haiti", + }, + ), + ( + ExactValueMatcher(value="HN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/honduras-rep.html", + "courier": "Honducor", + "courier_url": "http://honducor.gob.hn/codpost/consulta.php", + "country": "Honduras (Rep.)", + }, + ), + ( + ExactValueMatcher(value="HU"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/hungary.html", + "courier": "Magyar Posta", + "courier_url": "http://www.posta.hu/ugyfelszolgalat/iranyitoszam_kereso", + "country": "Hungary", + }, + ), + ( + ExactValueMatcher(value="IS"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/iceland.html", + "courier": "Íslandspóstur hf", + "courier_url": "http://www.postur.is/en/desktopdefault.aspx/tabid-450/700_read-1715/", + "country": "Iceland", + }, + ), + ( + ExactValueMatcher(value="IN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/india.html", + "courier": "India Post", + "courier_url": "http://www.indiapost.gov.in/", + "country": "India", + }, + ), + ( + ExactValueMatcher(value="ID"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/indonesia.html", + "courier": "Pos Indonesia", + "courier_url": "http://kodepos.indonesiaweb.info/en/street/", + "country": "Indonesia", + }, + ), + ( + ExactValueMatcher(value="IR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/iran-islamic-rep.html", + "courier": "Islamic Republic of Iran Post Co.", + "courier_url": "http://www.post.ir/Homepage.aspx?site=PostPortal&lang=fa-IR&tabid=0", + "country": "Iran (Islamic Rep.)", + }, + ), + ( + ExactValueMatcher(value="IQ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/iraq.html", + "courier": "Iraqi Post", + "courier_url": "http://www.iraqipost.net/", + "country": "Iraq", + }, + ), + ( + ExactValueMatcher(value="IE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/ireland.html", + "courier": "AN Post - regulatory and International affairs Unit", + "courier_url": "http://locator.anpost.ie/", + "country": "Ireland", + }, + ), + ( + ExactValueMatcher(value="IL"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/israel.html", + "courier": "Israel Post", + "courier_url": "http://www.israelpost.co.il/zipcode.nsf/demozip?openform", + "country": "Israel", + }, + ), + ( + ExactValueMatcher(value="IT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/italy.html", + "courier": "Poste Italiane", + "courier_url": "http://www.poste.it/online/cercacap/", + "country": "Italy", + }, + ), + ( + ExactValueMatcher(value="JM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/jamaica.html", + "courier": "Jamaica Post", + "courier_url": "http://www.jamaicapost.gov.jm/", + "country": "Jamaica", + }, + ), + ( + ExactValueMatcher(value="JP"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/japan.html", + "courier": "Japan Post", + "courier_url": "http://www.post.japanpost.jp/zipcode/index.html", + "country": "Japan", + }, + ), + ( + ExactValueMatcher(value="JO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/jordan.html", + "courier": "Jordan Post", + "courier_url": "http://www.jordanpost.com.jo/", + "country": "Jordan", + }, + ), + ( + ExactValueMatcher(value="KZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/kazakhstan.html", + "courier": "Kazpost", + "courier_url": "http://www.kazpost.kz/ru/poisk-pochtovogo-indeksa-0", + "country": "Kazakhstan", + }, + ), + ( + ExactValueMatcher(value="KE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/kenya.html", + "courier": "Posta Kenya", + "courier_url": "http://www.posta.co.ke/postOfficeFind.asp", + "country": "Kenya", + }, + ), + ( + ExactValueMatcher(value="KI"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/kiribati.html", + "courier": "Kiribati Public Service Public", + "courier_url": None, + "country": "Kiribati", + }, + ), + ( + ExactValueMatcher(value="KR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/korea-rep.html", + "courier": "Korea Post", + "courier_url": "http://www.epost.go.kr/roadAreaCdEng.retrieveRdEngAreaCdList.comm", + "country": "Korea (Rep.)", + }, + ), + ( + ExactValueMatcher(value="KW"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/kuwait.html", + "courier": "Kuwait Ministry of Communications", + "courier_url": "http://moc.kw/English/index.html", + "country": "Kuwait", + }, + ), + ( + ExactValueMatcher(value="KG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/kyrgyzstan.html", + "courier": "Kyrgyz Post", + "courier_url": "http://kyrgyzpost.kg/ru/news.html", + "country": "Kyrgyzstan", + }, + ), + ( + ExactValueMatcher(value="LA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/lao-peoples-dem-rep.html", + "courier": "Entreprise des Postes Lao", + "courier_url": None, + "country": "Laos", + }, + ), + ( + ExactValueMatcher(value="LV"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/latvia.html", + "courier": "Latvia Post", + "courier_url": "http://www.pasts.lv/lv/uzzinas/parbaudit-adresi/", + "country": "Latvia", + }, + ), + ( + ExactValueMatcher(value="LB"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/lebanon.html", + "courier": "LibanPost", + "courier_url": "http://www.libanpost.com.lb/", + "country": "Lebanon", + }, + ), + ( + ExactValueMatcher(value="LS"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/lesotho.html", + "courier": "Lesotho Post", + "courier_url": "http://lesothopost.org.ls/", + "country": "Lesotho", + }, + ), + ( + ExactValueMatcher(value="LR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/liberia.html", + "courier": "Ministry of Posts and Telecommunications", + "courier_url": "http://www.mopt.gov.lr/", + "country": "Liberia", + }, + ), + ( + ExactValueMatcher(value="LY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/libya.html", + "courier": "Libya Post", + "courier_url": "http://libyapost.ly/en/", + "country": "Libya", + }, + ), + ( + ExactValueMatcher(value="LI"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/liechtenstein.html", + "courier": "Liechtensteinische Post AG", + "courier_url": "http://www.post.li/", + "country": "Liechtenstein", + }, + ), + ( + ExactValueMatcher(value="LT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/lithuania.html", + "courier": "Lietuvos Pastas", + "courier_url": "http://www.post.lt/en/help/postal-code-search", + "country": "Lithuania", + }, + ), + ( + ExactValueMatcher(value="LU"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/luxembourg.html", + "courier": "Post", + "courier_url": "http://www.post.lu/en/particuliers/courrier/rechercher-un-code-postal", + "country": "Luxembourg", + }, + ), + ( + ExactValueMatcher(value="MG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/madagascar.html", + "courier": "PAOSITRA MALAGASY", + "courier_url": "http://www.mtpc.gov.mg/", + "country": "Madagascar", + }, + ), + ( + ExactValueMatcher(value="MW"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/malawi.html", + "courier": "Malawi Posts Corporation", + "courier_url": "http://www.malawiposts.com/", + "country": "Malawi", + }, + ), + ( + ExactValueMatcher(value="MY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/malaysia.html", + "courier": "Pos Malaysia", + "courier_url": "http://www.pos.com.my/pos/homepage.aspx", + "country": "Malaysia", + }, + ), + ( + ExactValueMatcher(value="MV"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/maldives.html", + "courier": "Maldives Post", + "courier_url": "http://www.maldivespost.com/index.php?lid=10", + "country": "Maldives", + }, + ), + ( + ExactValueMatcher(value="ML"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/mali.html", + "courier": "Office national des postes", + "courier_url": "http://www.laposte.ml/", + "country": "Mali", + }, + ), + ( + ExactValueMatcher(value="MT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/malta.html", + "courier": "Malta Post", + "courier_url": "http://postcodes.maltapost.com/", + "country": "Malta", + }, + ), + ( + ExactValueMatcher(value="MR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/mauritania.html", + "courier": "MAURIPOST – Société Mauritanienne des Postes", + "courier_url": "http://www.mauripost.mr/", + "country": "Mauritania", + }, + ), + ( + ExactValueMatcher(value="MU"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/mauritius.html", + "courier": "Mauritius Post", + "courier_url": "http://www.mauritiuspost.mu/", + "country": "Mauritius", + }, + ), + ( + ExactValueMatcher(value="MX"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/mexico.html", + "courier": "Correos de México", + "courier_url": "http://www.correosdemexico.gob.mx/ServiciosLinea/Paginas/ccpostales.aspx", + "country": "Mexico", + }, + ), + ( + ExactValueMatcher(value="MD"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/moldova.html", + "courier": "Posta Moldovei", + "courier_url": "http://www.posta.md/ro/postal_code.html", + "country": "Moldova", + }, + ), + ( + ExactValueMatcher(value="MC"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/monaco.html", + "courier": "La Poste Monaco", + "courier_url": "http://www.lapostemonaco.mc/", + "country": "Monaco", + }, + ), + ( + ExactValueMatcher(value="MN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/mongolia.html", + "courier": "Mongol Post - Монгол шуудан компани", + "courier_url": "http://www.zipcode.mn/", + "country": "Mongolia", + }, + ), + ( + ExactValueMatcher(value="ME"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/montenegro-rep.html", + "courier": "Pošta Crne Gore", + "courier_url": "http://www.postacg.me/main.php?idstr=177", + "country": "Montenegro (Rep.)", + }, + ), + ( + ExactValueMatcher(value="MA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/morocco.html", + "courier": "Barid Al-Maghrib – Poste Maroc", + "courier_url": "http://www.codepostal.ma/search_mot.aspx?keyword=", + "country": "Morocco", + }, + ), + ( + ExactValueMatcher(value="MZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/mozambique.html", + "courier": "Correios de Moçambique", + "courier_url": "http://www.correios.co.mz/", + "country": "Mozambique", + }, + ), + ( + ExactValueMatcher(value="MM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/myanmar.html", + "courier": "Myanmar Post and Telecommunications Department", + "courier_url": None, + "country": "Myanmar", + }, + ), + ( + ExactValueMatcher(value="NA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/namibia.html", + "courier": "NAM Post", + "courier_url": "https://www.nampost.com.na/", + "country": "Namibia", + }, + ), + ( + ExactValueMatcher(value="NR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/nauru.html", + "courier": "Nauru General Post Office", + "courier_url": None, + "country": "Nauru", + }, + ), + ( + ExactValueMatcher(value="NP"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/nepal.html", + "courier": "Nepal Postal Services", + "courier_url": "http://www.gpo.gov.np/postalcode.aspx", + "country": "Nepal", + }, + ), + ( + ExactValueMatcher(value="NL"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/netherlands.html", + "courier": "PostNL", + "courier_url": "http://www.postnl.nl/voorthuis/", + "country": "Netherlands", + }, + ), + ( + ExactValueMatcher(value="NZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/new-zealand-including-the-ross-dependency.html", + "courier": "New Zealand Post", + "courier_url": "http://www.nzpost.co.nz/Cultures/en-NZ/OnlineTools/PostCodeFinder/", + "country": "New Zealand (including the Ross Dependency)", + }, + ), + ( + ExactValueMatcher(value="NI"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/nicaragua.html", + "courier": "Correos de Nicaragua", + "courier_url": "http://www.correos.gob.ni/", + "country": "Nicaragua", + }, + ), + ( + ExactValueMatcher(value="NE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/niger.html", + "courier": "Niger Poste", + "courier_url": "http://www.nigerposte.net/", + "country": "Niger", + }, + ), + ( + ExactValueMatcher(value="NG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/nigeria.html", + "courier": "Nigerian Postal Service", + "courier_url": "http://www.nigeriapostcodes.com/", + "country": "Nigeria", + }, + ), + ( + ExactValueMatcher(value="NO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/norway.html", + "courier": "Posten", + "courier_url": "http://adressesok.posten.no/en/postal_codes/search", + "country": "Norway", + }, + ), + ( + ExactValueMatcher(value="OM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/oman.html", + "courier": "Oman Post", + "courier_url": "http://www.omanpost.om/Portals/2/Skins/skins//tabid/64/Default.aspx", + "country": "Oman", + }, + ), + ( + ExactValueMatcher(value="PK"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/pakistan.html", + "courier": "Pakistan Post", + "courier_url": "http://www.pakpost.gov.pk/postcode/postcode.html", + "country": "Pakistan", + }, + ), + ( + ExactValueMatcher(value="PA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/panama-rep.html", + "courier": "Correos de Panamá", + "courier_url": "http://www.correospanama.gob.pa/", + "country": "Panama (Rep.)", + }, + ), + ( + ExactValueMatcher(value="PG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/papua-new-guinea.html", + "courier": "Post PNG", + "courier_url": "http://www.postpng.com.pg/", + "country": "Papua New Guinea", + }, + ), + ( + ExactValueMatcher(value="PY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/paraguay.html", + "courier": "Correo Paraguayo", + "courier_url": "http://www.correoparaguayo.gov.py/", + "country": "Paraguay", + }, + ), + ( + ExactValueMatcher(value="PE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/peru.html", + "courier": "SERPOST – Servicios Postales del Perú", + "courier_url": "http://www.mtc.gob.pe/portal/CPOSTAL/Listado_codigo_postal.html", + "country": "Peru", + }, + ), + ( + ExactValueMatcher(value="PH"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/philippines.html", + "courier": "PHLPOST – Philippine Postal Corporation", + "courier_url": "https://www.phlpost.gov.ph/zip-code-search.php", + "country": "Philippines", + }, + ), + ( + ExactValueMatcher(value="PL"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/poland.html", + "courier": "Poczta Polska", + "courier_url": "http://kody.poczta-polska.pl/", + "country": "Poland", + }, + ), + ( + ExactValueMatcher(value="PT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/portugal.html", + "courier": "CTT - Correios", + "courier_url": "http://www.ctt.pt/feapl_2/app/open/tools.jspx?tool=1", + "country": "Portugal", + }, + ), + ( + ExactValueMatcher(value="QA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/qatar.html", + "courier": "Qatar Post", + "courier_url": "http://www.qpost.com.qa/", + "country": "Qatar", + }, + ), + ( + ExactValueMatcher(value="RO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/romania.html", + "courier": "Posta Romana", + "courier_url": "http://www.posta-romana.ro/postal_codes", + "country": "Romania", + }, + ), + ( + ExactValueMatcher(value="RU"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/russian-federation.html", + "courier": "Russian Post", + "courier_url": "http://www.russianpost.ru/rp/servise/ru/home/postuslug/searchops", + "country": "Russian Federation", + }, + ), + ( + ExactValueMatcher(value="RW"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/rwanda.html", + "courier": "National Post Office (Iposita)", + "courier_url": "http://i-posita.rw/spip.php?article1", + "country": "Rwanda", + }, + ), + ( + ExactValueMatcher(value="KN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/saint-christopher-saint-kitts-and-nevis.html", + "courier": "St. Kitts & Nevis Postal Services", + "courier_url": "http://www.post.gov.kn/", + "country": "Saint Christopher (Saint Kitts) and Nevis", + }, + ), + ( + ExactValueMatcher(value="LC"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/saint-lucia.html", + "courier": "Saint Lucia Postal Service", + "courier_url": "http://www.stluciapostal.com/", + "country": "Saint Lucia", + }, + ), + ( + ExactValueMatcher(value="VC"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/saint-vincent-and-the-grenadines.html", + "courier": "SVG Postal Corporation", + "courier_url": "http://www.svgpost.gov.vc/", + "country": "Saint Vincent and the Grenadines", + }, + ), + ( + ExactValueMatcher(value="WS"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/samoa.html", + "courier": "Samoa Post", + "courier_url": "http://www.samoapost.ws/", + "country": "Samoa", + }, + ), + ( + ExactValueMatcher(value="SM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/san-marino.html", + "courier": "Poste San Marino", + "courier_url": "http://www.poste.sm/on-line/home.html", + "country": "San Marino", + }, + ), + ( + ExactValueMatcher(value="ST"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/sao-tome-and-principe.html", + "courier": "Correios de São Tomé e Príncipe", + "courier_url": "http://www.inh.st/correios.st.htm", + "country": "Sao Tome and Principe", + }, + ), + ( + ExactValueMatcher(value="SA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/saudi-arabia.html", + "courier": "Saudi Post", + "courier_url": "http://maps.address.gov.sa", + "country": "Saudi Arabia", + }, + ), + ( + ExactValueMatcher(value="SN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/senegal.html", + "courier": "La Poste Senegal", + "courier_url": "http://www.laposte.sn/laposte/trouver_codepostal.php", + "country": "Senegal", + }, + ), + ( + ExactValueMatcher(value="RS"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/serbia-rep.html", + "courier": 'PTT Communications "Srbija"', + "courier_url": "http://www.posta.rs/struktura/eng/aplikacije/pronadji/nadji-pak-rezultat.asp", + "country": "Serbia (Rep.)", + }, + ), + ( + ExactValueMatcher(value="SC"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/seychelles.html", + "courier": "Seychelles Postal Service", + "courier_url": "http://www.seychellespost.gov.sc/", + "country": "Seychelles", + }, + ), + ( + ExactValueMatcher(value="SL"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/sierra-leone.html", + "courier": "Sierra Leone Postal Services", + "courier_url": "http://www.salpost.sl/", + "country": "Sierra Leone", + }, + ), + ( + ExactValueMatcher(value="SG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/singapore.html", + "courier": "SingPost", + "courier_url": "http://www.singpost.com.sg/quick_services/index.htm", + "country": "Singapore", + }, + ), + ( + ExactValueMatcher(value="SK"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/slovakia.html", + "courier": "Slovenská Posta", + "courier_url": "http://psc.posta.sk/", + "country": "Slovakia", + }, + ), + ( + ExactValueMatcher(value="SI"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/slovenia.html", + "courier": "Posta Slovenije d.o.o.", + "courier_url": "http://www.posta.si/postne-stevilke-doma", + "country": "Slovenia", + }, + ), + ( + ExactValueMatcher(value="SB"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/solomon-islands.html", + "courier": "Solomon Post", + "courier_url": None, + "country": "Solomon Islands", + }, + ), + ( + ExactValueMatcher(value="SO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/somalia.html", + "courier": "Somali Post", + "courier_url": None, + "country": "Somalia", + }, + ), + ( + ExactValueMatcher(value="ZA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/south-africa.html", + "courier": "South African Post Office", + "courier_url": "http://www.postoffice.co.za/ContactUs/postalcode.html", + "country": "South Africa", + }, + ), + ( + ExactValueMatcher(value="SS"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/south-sudan-rep.html", + "courier": "Minister of Telecommunication and Postal Services", + "courier_url": None, + "country": "South Sudan (Rep.)", + }, + ), + ( + ExactValueMatcher(value="ES"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/spain.html", + "courier": "Correos y Telégrafos", + "courier_url": "http://www.correos.es/ss/Satellite/site/pagina-buscador_codigos_postales/sidioma=es_ES", + "country": "Spain", + }, + ), + ( + ExactValueMatcher(value="LK"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/sri-lanka.html", + "courier": "Sri Lanka Post", + "courier_url": "http://www.slpost.gov.lk/", + "country": "Sri Lanka", + }, + ), + ( + ExactValueMatcher(value="SD"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/sudan.html", + "courier": "Sudapost", + "courier_url": "http://sudapost.sd/index.php/en/", + "country": "Sudan", + }, + ), + ( + ExactValueMatcher(value="SR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/suriname.html", + "courier": "SURPOST", + "courier_url": "http://www.surpost.com/surpost2/index.php", + "country": "Suriname", + }, + ), + ( + ExactValueMatcher(value="SZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/swaziland.html", + "courier": "Swaziland Posts & Telecommunications Corporation", + "courier_url": "http://www.sptc.co.sz/swazipost/codes.php", + "country": "Swaziland", + }, + ), + ( + ExactValueMatcher(value="SE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/sweden.html", + "courier": "Posten Sweden Post", + "courier_url": "http://www.posten.se/oldurls/old_postnummersok.jspv", + "country": "Sweden", + }, + ), + ( + ExactValueMatcher(value="CH"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/switzerland.html", + "courier": "La Poste Suisse", + "courier_url": "http://www.swisspost.ch/post-startseite.htm", + "country": "Switzerland", + }, + ), + ( + ExactValueMatcher(value="SY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/syrian-arab-rep.html", + "courier": "Syrian Post", + "courier_url": "http://www.syrianpost.gov.sy/", + "country": "Syrian Arab Rep.", + }, + ), + ( + ExactValueMatcher(value="TJ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/tajikistan.html", + "courier": "Tajikistan’s communications service agency", + "courier_url": None, + "country": "Tajikistan", + }, + ), + ( + ExactValueMatcher(value="TZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/tanzania-united-rep.html", + "courier": "Tanzania Posts Corporation", + "courier_url": "http://www.posta.co.tz/", + "country": "Tanzania (United Rep.)", + }, + ), + ( + ExactValueMatcher(value="TH"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/thailand.html", + "courier": "Thailand Post", + "courier_url": "http://www.thailandpost.com/search.php", + "country": "Thailand", + }, + ), + ( + ExactValueMatcher(value="MK"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/the-former-yugoslav-republic-of-macedonia.html", + "courier": "Macedonian Post & Telecommunications", + "courier_url": "http://www.posta.mk/pravilno_adresiranje.html", + "country": "The former Yugoslav Republic of Macedonia", + }, + ), + ( + ExactValueMatcher(value="TL"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/timor-leste-dem-rep.html", + "courier": "Correios de Timor Leste", + "courier_url": None, + "country": "Timor-Leste (Dem. Rep.)", + }, + ), + ( + ExactValueMatcher(value="TG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/togo.html", + "courier": "La Poste du Togo", + "courier_url": "http://www.laposte.tg/", + "country": "Togo", + }, + ), + ( + ExactValueMatcher(value="TO"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/tonga-including-niuafoou.html", + "courier": "Tonga Post", + "courier_url": "http://tongapost.to/", + "country": "Tonga (including Niuafo'ou)", + }, + ), + ( + ExactValueMatcher(value="TT"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/trinidad-and-tobago.html", + "courier": "Trinidad and Tobago Postal Corporation", + "courier_url": "http://www.ttpost.net/", + "country": "Trinidad and Tobago", + }, + ), + ( + ExactValueMatcher(value="TN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/tunisia.html", + "courier": "La Poste Tunisienne", + "courier_url": "http://www.poste.tn/codes.php", + "country": "Tunisia", + }, + ), + ( + ExactValueMatcher(value="TR"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/turkey.html", + "courier": "Turkey Post", + "courier_url": "http://postakodu.ptt.gov.tr/", + "country": "Turkey", + }, + ), + ( + ExactValueMatcher(value="TM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/turkmenistan.html", + "courier": "Turkmenpost", + "courier_url": "http://www.turkmenpost.gov.tm/about_index.php", + "country": "Turkmenistan", + }, + ), + ( + ExactValueMatcher(value="TV"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/tuvalu.html", + "courier": "Tuvalu Philatelic Bureau", + "courier_url": None, + "country": "Tuvalu", + }, + ), + ( + ExactValueMatcher(value="UG"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/uganda.html", + "courier": "Posta Uganda", + "courier_url": "http://www.ugapost.co.ug/", + "country": "Uganda", + }, + ), + ( + ExactValueMatcher(value="UA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/ukraine.html", + "courier": "Ukrposhta", + "courier_url": "http://services.ukrposhta.com/postindex_new/default.aspx?lang=en", + "country": "Ukraine", + }, + ), + ( + ExactValueMatcher(value="AE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/united-arab-emirates.html", + "courier": "Emirates Post", + "courier_url": "http://www.emiratespost.com/content/english/index.jsp;jsessionid=35fbe352a6c441a491929d81d54fa0c6", + "country": "United Arab Emirates", + }, + ), + ( + ExactValueMatcher(value="US"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/united-states-of-america.html", + "courier": "United States Postal Service", + "courier_url": "http://zip4.usps.com/zip4/welcome.jsp", + "country": "United States of America", + }, + ), + ( + ExactValueMatcher(value="UY"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/uruguay.html", + "courier": "Correo Uruguayo", + "courier_url": "http://www.correo.com.uy/index.asp?codPag=codPost&switchMapa=codPost", + "country": "Uruguay", + }, + ), + ( + ExactValueMatcher(value="UZ"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/eastern-europe-and-northern-asia/uzbekistan.html", + "courier": "Post of Uzbekistan", + "courier_url": "http://www.pochta.uz/index.php/en/postal-indexes/9", + "country": "Uzbekistan", + }, + ), + ( + ExactValueMatcher(value="VU"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/vanuatu.html", + "courier": "Vanuatu Post", + "courier_url": "http://www.vanuatupost.vu/", + "country": "Vanuatu", + }, + ), + ( + ExactValueMatcher(value="VA"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/western-europe/vatican.html", + "courier": "Vatican post", + "courier_url": "http://www.vaticanstate.va/EN/Services/Philatelic_and_Numismatic_Office/", + "country": "Vatican", + }, + ), + ( + ExactValueMatcher(value="VE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/americas/venezuela.html", + "courier": "IPOSTEL – Instituto Postal Telegráfico de Venezuela", + "courier_url": "http://www.ipostel.gob.ve/nlinea/codigo_postal.php", + "country": "Venezuela", + }, + ), + ( + ExactValueMatcher(value="VN"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/viet-nam.html", + "courier": "VNPT – Vietnam Posts and Telecommunications Group", + "courier_url": "http://postcode.vnpost.vn/services/search.aspx", + "country": "Viet Nam", + }, + ), + ( + ExactValueMatcher(value="YE"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/southern-asia-and-oceania/yemen.html", + "courier": "Yemen Post", + "courier_url": "http://www.post.ye/", + "country": "Yemen", + }, + ), + ( + ExactValueMatcher(value="ZM"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/zambia.html", + "courier": "Zambia Postal Services Corporation (ZAMPOST)", + "courier_url": "http://www.zampost.com.zm/", + "country": "Zambia", + }, + ), + ( + ExactValueMatcher(value="ZW"), + { + "upu_reference_url": "http://www.upu.int/en/the-upu/member-countries/africa/zimbabwe.html", + "courier": "Zimpost – Zimbabwe Posts", + "courier_url": "http://www.zimpost.co.zw/", + "country": "Zimbabwe", + }, + ), + ], + ), + ], + additional_validator=AdditionalValidator(exists=["Courier"]), + checksum_validator=S10(), + ), + TrackingNumberDefinition( + courier=Courier(code="ontrac", name="OnTrac"), + product=Product(name="OnTrac"), + number_regex=re.compile( + "\\s*C\\s*(?P([0-9]\\s*){13})(?P[0-9]\\s*)", + ), + tracking_url_template="http://www.ontrac.com/tracking/?number=%s", + serial_number_parser=DefaultSerialNumberParser( + prepend_if=PrependIf(matches_regex=re.compile("^(?!4).+$"), content="4"), + ), + additional=[], + additional_validator=None, + checksum_validator=Mod10(odds_multiplier=2, evens_multiplier=1), + ), + TrackingNumberDefinition( + courier=Courier(code="ontrac", name="OnTrac"), + product=Product(name="OnTrac D"), + number_regex=re.compile( + "\\s*D\\s*(?P([0-9]\\s*){13})(?P[0-9]\\s*)", + ), + tracking_url_template="http://www.ontrac.com/tracking/?number=%s", + serial_number_parser=DefaultSerialNumberParser( + prepend_if=PrependIf(matches_regex=re.compile("^(?!5).+$"), content="5"), + ), + additional=[], + additional_validator=None, + checksum_validator=Mod10(odds_multiplier=2, evens_multiplier=1), + ), + TrackingNumberDefinition( + courier=Courier(code="dpd", name="DPD"), + product=Product(name="DPD (28)"), + number_regex=re.compile( + "\\s*(?P(?P([0-9]\\s*){7})([0-9]\\s*){14}(?P([0-9]\\s*){3})(?P([0-9]\\s*){3}))(?P[0-9A-Z]\\s*)", + ), + tracking_url_template="https://www.dpdgroup.com/nl/mydpd/my-parcels/track?lang=en&parcelNumber=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[ + Additional( + name="Service Type", + regex_group_name="ServiceType", + value_matchers=[ + ( + ExactValueMatcher(value="101"), + {"description": "normal parcel", "name": "D"}, + ), + ( + ExactValueMatcher(value="102"), + { + "description": "normal parcel, hazardous goods", + "name": "D-HAZ", + }, + ), + ( + ExactValueMatcher(value="105"), + {"description": "normal parcel, ex works", "name": "D-EXW"}, + ), + ( + ExactValueMatcher(value="106"), + { + "description": "normal parcel, ex works, hazardous goods", + "name": "D-EXW-HAZ", + }, + ), + ( + ExactValueMatcher(value="109"), + {"description": "normal parcel, C.O.D.", "name": "D-COD"}, + ), + ( + ExactValueMatcher(value="110"), + { + "description": "normal parcel, C.O.D., hazardous goods", + "name": "D-COD-HAZ", + }, + ), + ( + ExactValueMatcher(value="113"), + {"description": "normal parcel, exchange", "name": "D-SWAP"}, + ), + ( + ExactValueMatcher(value="136"), + {"description": "small parcel", "name": "D"}, + ), + ( + ExactValueMatcher(value="154"), + {"description": "PARCELLetter", "name": "PARCELLetter"}, + ), + ( + ExactValueMatcher(value="155"), + {"description": "Guarantee", "name": "PM2"}, + ), + ( + ExactValueMatcher(value="161"), + {"description": "Guarantee, C.O.D.", "name": "PM2-COD"}, + ), + ( + ExactValueMatcher(value="179"), + {"description": "DPD 10:00", "name": "AM1"}, + ), + ( + ExactValueMatcher(value="191"), + {"description": "DPD 10:00, C.O.D.", "name": "AM1-COD"}, + ), + ( + ExactValueMatcher(value="225"), + {"description": "DPD 12:00", "name": "AM2"}, + ), + ( + ExactValueMatcher(value="237"), + {"description": "DPD 12:00, C.O.D.", "name": "AM2-COD"}, + ), + ( + ExactValueMatcher(value="350"), + {"description": "DPD 8:30", "name": "AM0"}, + ), + ], + ), + Additional( + name="Country Code", + regex_group_name="CountryCode", + value_matchers=[ + ( + ExactValueMatcher(value="818"), + { + "country_code": "EGY", + "country": "Aegypten", + "country_short_code": "EG", + }, + ), + ( + ExactValueMatcher(value="226"), + { + "country_code": "GNQ", + "country": "Aequatorial-Guinea", + "country_short_code": "GQ", + }, + ), + ( + ExactValueMatcher(value="231"), + { + "country_code": "ETH", + "country": "Aethiopien", + "country_short_code": "ET", + }, + ), + ( + ExactValueMatcher(value="004"), + { + "country_code": "AFG", + "country": "Afghanistan", + "country_short_code": "AF", + }, + ), + ( + ExactValueMatcher(value="248"), + { + "country_code": "ALA", + "country": "Aland-Inseln", + "country_short_code": "AX", + }, + ), + ( + ExactValueMatcher(value="008"), + { + "country_code": "ALB", + "country": "Albanien", + "country_short_code": "AL", + }, + ), + ( + ExactValueMatcher(value="012"), + { + "country_code": "DZA", + "country": "Algerien", + "country_short_code": "DZ", + }, + ), + ( + ExactValueMatcher(value="016"), + { + "country_code": "ASM", + "country": "Amerikanisch-Samoa", + "country_short_code": "AS", + }, + ), + ( + ExactValueMatcher(value="020"), + { + "country_code": "AND", + "country": "Andorra", + "country_short_code": "AD", + }, + ), + ( + ExactValueMatcher(value="024"), + { + "country_code": "AGO", + "country": "Angola", + "country_short_code": "AO", + }, + ), + ( + ExactValueMatcher(value="660"), + { + "country_code": "AIA", + "country": "Anguilla", + "country_short_code": "AI", + }, + ), + ( + ExactValueMatcher(value="010"), + { + "country_code": "ATA", + "country": "Antarctica", + "country_short_code": "AQ", + }, + ), + ( + ExactValueMatcher(value="028"), + { + "country_code": "ATG", + "country": "Antigua & Barbuda", + "country_short_code": "AG", + }, + ), + ( + ExactValueMatcher(value="032"), + { + "country_code": "ARG", + "country": "Argentinien", + "country_short_code": "AR", + }, + ), + ( + ExactValueMatcher(value="051"), + { + "country_code": "ARM", + "country": "Armenien", + "country_short_code": "AM", + }, + ), + ( + ExactValueMatcher(value="533"), + { + "country_code": "ABW", + "country": "Aruba", + "country_short_code": "AW", + }, + ), + ( + ExactValueMatcher(value="031"), + { + "country_code": "AZE", + "country": "Aserbaidschan", + "country_short_code": "AZ", + }, + ), + ( + ExactValueMatcher(value="036"), + { + "country_code": "AUS", + "country": "Australien", + "country_short_code": "AU", + }, + ), + ( + ExactValueMatcher(value="044"), + { + "country_code": "BHS", + "country": "Bahamas", + "country_short_code": "BS", + }, + ), + ( + ExactValueMatcher(value="048"), + { + "country_code": "BHR", + "country": "Bahrain", + "country_short_code": "BH", + }, + ), + ( + ExactValueMatcher(value="050"), + { + "country_code": "BGD", + "country": "Bangladesh", + "country_short_code": "BD", + }, + ), + ( + ExactValueMatcher(value="052"), + { + "country_code": "BRB", + "country": "Barbados", + "country_short_code": "BB", + }, + ), + ( + ExactValueMatcher(value="056"), + { + "country_code": "BEL", + "country": "Belgien", + "country_short_code": "BE", + }, + ), + ( + ExactValueMatcher(value="084"), + { + "country_code": "BLZ", + "country": "Belize", + "country_short_code": "BZ", + }, + ), + ( + ExactValueMatcher(value="204"), + { + "country_code": "BEN", + "country": "Benin", + "country_short_code": "BJ", + }, + ), + ( + ExactValueMatcher(value="060"), + { + "country_code": "BMU", + "country": "Bermudas", + "country_short_code": "BM", + }, + ), + ( + ExactValueMatcher(value="064"), + { + "country_code": "BTN", + "country": "Bhutan", + "country_short_code": "BT", + }, + ), + ( + ExactValueMatcher(value="068"), + { + "country_code": "BOL", + "country": "Bolivien", + "country_short_code": "BO", + }, + ), + ( + ExactValueMatcher(value="535"), + { + "country_code": "BES", + "country": "Bonaire, Sint Eustatius und Saba", + "country_short_code": "BQ", + }, + ), + ( + ExactValueMatcher(value="070"), + { + "country_code": "BIH", + "country": "Bosnien & Herzegowina", + "country_short_code": "BA", + }, + ), + ( + ExactValueMatcher(value="072"), + { + "country_code": "BWA", + "country": "Botswana", + "country_short_code": "BW", + }, + ), + ( + ExactValueMatcher(value="074"), + { + "country_code": "BVT", + "country": "Bouvet-Insel", + "country_short_code": "BV", + }, + ), + ( + ExactValueMatcher(value="076"), + { + "country_code": "BRA", + "country": "Brasilien", + "country_short_code": "BR", + }, + ), + ( + ExactValueMatcher(value="086"), + { + "country_code": "IOT", + "country": "British Indian Ocean Territory", + "country_short_code": "IO", + }, + ), + ( + ExactValueMatcher(value="096"), + { + "country_code": "BRN", + "country": "Brunei Darussalam", + "country_short_code": "BN", + }, + ), + ( + ExactValueMatcher(value="100"), + { + "country_code": "BGR", + "country": "Bulgarien", + "country_short_code": "BG", + }, + ), + ( + ExactValueMatcher(value="854"), + { + "country_code": "BFA", + "country": "Burkina Faso", + "country_short_code": "BF", + }, + ), + ( + ExactValueMatcher(value="108"), + { + "country_code": "BDI", + "country": "Burundi", + "country_short_code": "BI", + }, + ), + ( + ExactValueMatcher(value="136"), + { + "country_code": "CYM", + "country": "Cayman-Inseln", + "country_short_code": "KY", + }, + ), + ( + ExactValueMatcher(value="152"), + { + "country_code": "CHL", + "country": "Chile", + "country_short_code": "CL", + }, + ), + ( + ExactValueMatcher(value="156"), + { + "country_code": "CHN", + "country": "China", + "country_short_code": "CN", + }, + ), + ( + ExactValueMatcher(value="184"), + { + "country_code": "COK", + "country": "Cook Inseln", + "country_short_code": "CK", + }, + ), + ( + ExactValueMatcher(value="188"), + { + "country_code": "CRI", + "country": "Costa Rica", + "country_short_code": "CR", + }, + ), + ( + ExactValueMatcher(value="531"), + { + "country_code": "CUW", + "country": "Curacao", + "country_short_code": "CW", + }, + ), + ( + ExactValueMatcher(value="208"), + { + "country_code": "DNK", + "country": "Daenemark", + "country_short_code": "DK", + }, + ), + ( + ExactValueMatcher(value="276"), + { + "country_code": "DEU", + "country": "Deutschland", + "country_short_code": "DE", + }, + ), + ( + ExactValueMatcher(value="212"), + { + "country_code": "DMA", + "country": "Dominica", + "country_short_code": "DM", + }, + ), + ( + ExactValueMatcher(value="214"), + { + "country_code": "DOM", + "country": "Dominikanische Republik", + "country_short_code": "DO", + }, + ), + ( + ExactValueMatcher(value="262"), + { + "country_code": "DJI", + "country": "Dschibuti", + "country_short_code": "DJ", + }, + ), + ( + ExactValueMatcher(value="218"), + { + "country_code": "ECU", + "country": "Ecuador", + "country_short_code": "EC", + }, + ), + ( + ExactValueMatcher(value="222"), + { + "country_code": "SLV", + "country": "El Salvador", + "country_short_code": "SV", + }, + ), + ( + ExactValueMatcher(value="384"), + { + "country_code": "CIV", + "country": "Elfenbeinkueste", + "country_short_code": "CI", + }, + ), + ( + ExactValueMatcher(value="232"), + { + "country_code": "ERI", + "country": "Eritrea", + "country_short_code": "ER", + }, + ), + ( + ExactValueMatcher(value="233"), + { + "country_code": "EST", + "country": "Estland", + "country_short_code": "EE", + }, + ), + ( + ExactValueMatcher(value="234"), + { + "country_code": "FRO", + "country": "Faeroer Inseln", + "country_short_code": "FO", + }, + ), + ( + ExactValueMatcher(value="238"), + { + "country_code": "FLK", + "country": "Falkland Inseln", + "country_short_code": "FK", + }, + ), + ( + ExactValueMatcher(value="242"), + { + "country_code": "FJI", + "country": "Fidschi", + "country_short_code": "FJ", + }, + ), + ( + ExactValueMatcher(value="246"), + { + "country_code": "FIN", + "country": "Finnland", + "country_short_code": "FI", + }, + ), + ( + ExactValueMatcher(value="250"), + { + "country_code": "FRA", + "country": "Frankreich", + "country_short_code": "FR", + }, + ), + ( + ExactValueMatcher(value="260"), + { + "country_code": "ATF", + "country": "Franzoesische Sued- und Antarktisterritorien", + "country_short_code": "TF", + }, + ), + ( + ExactValueMatcher(value="258"), + { + "country_code": "PYF", + "country": "Franzoesisch-Polynesien", + "country_short_code": "PF", + }, + ), + ( + ExactValueMatcher(value="266"), + { + "country_code": "GAB", + "country": "Gabun", + "country_short_code": "GA", + }, + ), + ( + ExactValueMatcher(value="270"), + { + "country_code": "GMB", + "country": "Gambia", + "country_short_code": "GM", + }, + ), + ( + ExactValueMatcher(value="268"), + { + "country_code": "GEO", + "country": "Georgien", + "country_short_code": "GE", + }, + ), + ( + ExactValueMatcher(value="288"), + { + "country_code": "GHA", + "country": "Ghana", + "country_short_code": "GH", + }, + ), + ( + ExactValueMatcher(value="292"), + { + "country_code": "GIB", + "country": "Gibraltar", + "country_short_code": "GI", + }, + ), + ( + ExactValueMatcher(value="308"), + { + "country_code": "GRD", + "country": "Grenada", + "country_short_code": "GD", + }, + ), + ( + ExactValueMatcher(value="300"), + { + "country_code": "GRC", + "country": "Griechenland", + "country_short_code": "GR", + }, + ), + ( + ExactValueMatcher(value="304"), + { + "country_code": "GRL", + "country": "Groenland", + "country_short_code": "GL", + }, + ), + ( + ExactValueMatcher(value="826"), + { + "country_code": "GBR", + "country": "Grossbritannien & Nordirland", + "country_short_code": "GB", + }, + ), + ( + ExactValueMatcher(value="312"), + { + "country_code": "GLP", + "country": "Guadeloupe", + "country_short_code": "GP", + }, + ), + ( + ExactValueMatcher(value="316"), + { + "country_code": "GUM", + "country": "Guam", + "country_short_code": "GU", + }, + ), + ( + ExactValueMatcher(value="320"), + { + "country_code": "GTM", + "country": "Guatemala", + "country_short_code": "GT", + }, + ), + ( + ExactValueMatcher(value="831"), + { + "country_code": "GGY", + "country": "Guernsey", + "country_short_code": "GG", + }, + ), + ( + ExactValueMatcher(value="324"), + { + "country_code": "GIN", + "country": "Guinea", + "country_short_code": "GN", + }, + ), + ( + ExactValueMatcher(value="624"), + { + "country_code": "GNB", + "country": "Guinea-Bissau", + "country_short_code": "GW", + }, + ), + ( + ExactValueMatcher(value="328"), + { + "country_code": "GUY", + "country": "Guyana", + "country_short_code": "GY", + }, + ), + ( + ExactValueMatcher(value="254"), + { + "country_code": "GUF", + "country": "Guyana (Franzoesisch)", + "country_short_code": "GF", + }, + ), + ( + ExactValueMatcher(value="332"), + { + "country_code": "HTI", + "country": "Haiti", + "country_short_code": "HT", + }, + ), + ( + ExactValueMatcher(value="334"), + { + "country_code": "HMD", + "country": "Heard & Mc Donalds Inseln", + "country_short_code": "HM", + }, + ), + ( + ExactValueMatcher(value="340"), + { + "country_code": "HND", + "country": "Honduras", + "country_short_code": "HN", + }, + ), + ( + ExactValueMatcher(value="344"), + { + "country_code": "HKG", + "country": "Hong Kong", + "country_short_code": "HK", + }, + ), + ( + ExactValueMatcher(value="356"), + { + "country_code": "IND", + "country": "Indien", + "country_short_code": "IN", + }, + ), + ( + ExactValueMatcher(value="360"), + { + "country_code": "IDN", + "country": "Indonesien", + "country_short_code": "ID", + }, + ), + ( + ExactValueMatcher(value="364"), + { + "country_code": "IRN", + "country": "Iran", + "country_short_code": "IR", + }, + ), + ( + ExactValueMatcher(value="368"), + { + "country_code": "IRQ", + "country": "Iraq", + "country_short_code": "IQ", + }, + ), + ( + ExactValueMatcher(value="372"), + { + "country_code": "IRL", + "country": "Irland", + "country_short_code": "IE", + }, + ), + ( + ExactValueMatcher(value="352"), + { + "country_code": "ISL", + "country": "Island", + "country_short_code": "IS", + }, + ), + ( + ExactValueMatcher(value="833"), + { + "country_code": "IMN", + "country": "Isle of Man", + "country_short_code": "IM", + }, + ), + ( + ExactValueMatcher(value="376"), + { + "country_code": "ISR", + "country": "Israel", + "country_short_code": "IL", + }, + ), + ( + ExactValueMatcher(value="380"), + { + "country_code": "ITA", + "country": "Italien", + "country_short_code": "IT", + }, + ), + ( + ExactValueMatcher(value="388"), + { + "country_code": "JAM", + "country": "Jamaika", + "country_short_code": "JM", + }, + ), + ( + ExactValueMatcher(value="392"), + { + "country_code": "JPN", + "country": "Japan", + "country_short_code": "JP", + }, + ), + ( + ExactValueMatcher(value="887"), + { + "country_code": "YEM", + "country": "Jemen", + "country_short_code": "YE", + }, + ), + ( + ExactValueMatcher(value="832"), + { + "country_code": "JEY", + "country": "Jersey", + "country_short_code": "JE", + }, + ), + ( + ExactValueMatcher(value="400"), + { + "country_code": "JOR", + "country": "Jordanien", + "country_short_code": "JO", + }, + ), + ( + ExactValueMatcher(value="092"), + { + "country_code": "VGB", + "country": "Jungferninseln (britisch)", + "country_short_code": "VG", + }, + ), + ( + ExactValueMatcher(value="116"), + { + "country_code": "KHM", + "country": "Kambodscha", + "country_short_code": "KH", + }, + ), + ( + ExactValueMatcher(value="120"), + { + "country_code": "CMR", + "country": "Kamerun", + "country_short_code": "CM", + }, + ), + ( + ExactValueMatcher(value="124"), + { + "country_code": "CAN", + "country": "Kanada", + "country_short_code": "CA", + }, + ), + ( + ExactValueMatcher(value="991"), + { + "country_code": "ISC", + "country": "Kanarische Inseln", + "country_short_code": "IC", + }, + ), + ( + ExactValueMatcher(value="132"), + { + "country_code": "CPV", + "country": "Kapverdische Inseln", + "country_short_code": "CV", + }, + ), + ( + ExactValueMatcher(value="583"), + { + "country_code": "FSM", + "country": "Karolinen Inseln", + "country_short_code": "FM", + }, + ), + ( + ExactValueMatcher(value="398"), + { + "country_code": "KAZ", + "country": "Kasachstan", + "country_short_code": "KZ", + }, + ), + ( + ExactValueMatcher(value="634"), + { + "country_code": "QAT", + "country": "Katar", + "country_short_code": "QA", + }, + ), + ( + ExactValueMatcher(value="404"), + { + "country_code": "KEN", + "country": "Kenia", + "country_short_code": "KE", + }, + ), + ( + ExactValueMatcher(value="417"), + { + "country_code": "KGZ", + "country": "Kirgistan", + "country_short_code": "KG", + }, + ), + ( + ExactValueMatcher(value="296"), + { + "country_code": "KIR", + "country": "Kiribati", + "country_short_code": "KI", + }, + ), + ( + ExactValueMatcher(value="581"), + { + "country_code": "UMI", + "country": "Kleine vorgelagerte Inseln Vereinigter Staaten", + "country_short_code": "UM", + }, + ), + ( + ExactValueMatcher(value="166"), + { + "country_code": "CCK", + "country": "Kokos Inseln", + "country_short_code": "CC", + }, + ), + ( + ExactValueMatcher(value="170"), + { + "country_code": "COL", + "country": "Kolumbien", + "country_short_code": "CO", + }, + ), + ( + ExactValueMatcher(value="174"), + { + "country_code": "COM", + "country": "Komoren", + "country_short_code": "KM", + }, + ), + ( + ExactValueMatcher(value="178"), + { + "country_code": "COG", + "country": "Kongo", + "country_short_code": "CG", + }, + ), + ( + ExactValueMatcher(value="180"), + { + "country_code": "COD", + "country": "Kongo, Dem. Rep.", + "country_short_code": "CD", + }, + ), + ( + ExactValueMatcher(value="191"), + { + "country_code": "HRV", + "country": "Kroatien", + "country_short_code": "HR", + }, + ), + ( + ExactValueMatcher(value="192"), + { + "country_code": "CUB", + "country": "Kuba", + "country_short_code": "CU", + }, + ), + ( + ExactValueMatcher(value="414"), + { + "country_code": "KWT", + "country": "Kuwait", + "country_short_code": "KW", + }, + ), + ( + ExactValueMatcher(value="418"), + { + "country_code": "LAO", + "country": "Laos", + "country_short_code": "LA", + }, + ), + ( + ExactValueMatcher(value="426"), + { + "country_code": "LSO", + "country": "Lesotho", + "country_short_code": "LS", + }, + ), + ( + ExactValueMatcher(value="428"), + { + "country_code": "LVA", + "country": "Lettland", + "country_short_code": "LV", + }, + ), + ( + ExactValueMatcher(value="422"), + { + "country_code": "LBN", + "country": "Libanon", + "country_short_code": "LB", + }, + ), + ( + ExactValueMatcher(value="430"), + { + "country_code": "LBR", + "country": "Liberia", + "country_short_code": "LR", + }, + ), + ( + ExactValueMatcher(value="434"), + { + "country_code": "LBY", + "country": "Libyen", + "country_short_code": "LY", + }, + ), + ( + ExactValueMatcher(value="438"), + { + "country_code": "LIE", + "country": "Liechtenstein", + "country_short_code": "LI", + }, + ), + ( + ExactValueMatcher(value="440"), + { + "country_code": "LTU", + "country": "Litauen", + "country_short_code": "LT", + }, + ), + ( + ExactValueMatcher(value="442"), + { + "country_code": "LUX", + "country": "Luxemburg", + "country_short_code": "LU", + }, + ), + ( + ExactValueMatcher(value="446"), + { + "country_code": "MAC", + "country": "Macao", + "country_short_code": "MO", + }, + ), + ( + ExactValueMatcher(value="450"), + { + "country_code": "MDG", + "country": "Madagaskar", + "country_short_code": "MG", + }, + ), + ( + ExactValueMatcher(value="454"), + { + "country_code": "MWI", + "country": "Malawi", + "country_short_code": "MW", + }, + ), + ( + ExactValueMatcher(value="458"), + { + "country_code": "MYS", + "country": "Malaysia", + "country_short_code": "MY", + }, + ), + ( + ExactValueMatcher(value="462"), + { + "country_code": "MDV", + "country": "Malediven", + "country_short_code": "MV", + }, + ), + ( + ExactValueMatcher(value="466"), + { + "country_code": "MLI", + "country": "Mali", + "country_short_code": "ML", + }, + ), + ( + ExactValueMatcher(value="470"), + { + "country_code": "MLT", + "country": "Malta", + "country_short_code": "MT", + }, + ), + ( + ExactValueMatcher(value="504"), + { + "country_code": "MAR", + "country": "Marokko", + "country_short_code": "MA", + }, + ), + ( + ExactValueMatcher(value="584"), + { + "country_code": "MHL", + "country": "Marshall Inseln", + "country_short_code": "MH", + }, + ), + ( + ExactValueMatcher(value="474"), + { + "country_code": "MTQ", + "country": "Martinique", + "country_short_code": "MQ", + }, + ), + ( + ExactValueMatcher(value="478"), + { + "country_code": "MRT", + "country": "Mauretanien", + "country_short_code": "MR", + }, + ), + ( + ExactValueMatcher(value="480"), + { + "country_code": "MUS", + "country": "Mauritius", + "country_short_code": "MU", + }, + ), + ( + ExactValueMatcher(value="175"), + { + "country_code": "MYT", + "country": "Mayotte", + "country_short_code": "YT", + }, + ), + ( + ExactValueMatcher(value="807"), + { + "country_code": "MKD", + "country": "Mazedonien", + "country_short_code": "MK", + }, + ), + ( + ExactValueMatcher(value="484"), + { + "country_code": "MEX", + "country": "Mexiko", + "country_short_code": "MX", + }, + ), + ( + ExactValueMatcher(value="498"), + { + "country_code": "MDA", + "country": "Moldawien", + "country_short_code": "MD", + }, + ), + ( + ExactValueMatcher(value="492"), + { + "country_code": "MCO", + "country": "Monaco", + "country_short_code": "MC", + }, + ), + ( + ExactValueMatcher(value="496"), + { + "country_code": "MNG", + "country": "Mongolei", + "country_short_code": "MN", + }, + ), + ( + ExactValueMatcher(value="499"), + { + "country_code": "MNE", + "country": "Montenegro", + "country_short_code": "ME", + }, + ), + ( + ExactValueMatcher(value="500"), + { + "country_code": "MSR", + "country": "Montserrat", + "country_short_code": "MS", + }, + ), + ( + ExactValueMatcher(value="508"), + { + "country_code": "MOZ", + "country": "Mosambik", + "country_short_code": "MZ", + }, + ), + ( + ExactValueMatcher(value="104"), + { + "country_code": "MMR", + "country": "Myanmar", + "country_short_code": "MM", + }, + ), + ( + ExactValueMatcher(value="516"), + { + "country_code": "NAM", + "country": "Namibia", + "country_short_code": "NA", + }, + ), + ( + ExactValueMatcher(value="520"), + { + "country_code": "NRU", + "country": "Nauru", + "country_short_code": "NR", + }, + ), + ( + ExactValueMatcher(value="524"), + { + "country_code": "NPL", + "country": "Nepal", + "country_short_code": "NP", + }, + ), + ( + ExactValueMatcher(value="540"), + { + "country_code": "NCL", + "country": "Neukaledonien", + "country_short_code": "NC", + }, + ), + ( + ExactValueMatcher(value="554"), + { + "country_code": "NZL", + "country": "Neuseeland", + "country_short_code": "NZ", + }, + ), + ( + ExactValueMatcher(value="558"), + { + "country_code": "NIC", + "country": "Nicaragua", + "country_short_code": "NI", + }, + ), + ( + ExactValueMatcher(value="530"), + { + "country_code": "ANT", + "country": "Niederlaendische Antillen", + "country_short_code": "AN", + }, + ), + ( + ExactValueMatcher(value="528"), + { + "country_code": "NLD", + "country": "Niederlande", + "country_short_code": "NL", + }, + ), + ( + ExactValueMatcher(value="562"), + { + "country_code": "NER", + "country": "Niger", + "country_short_code": "NE", + }, + ), + ( + ExactValueMatcher(value="566"), + { + "country_code": "NGA", + "country": "Nigeria", + "country_short_code": "NG", + }, + ), + ( + ExactValueMatcher(value="570"), + { + "country_code": "NIU", + "country": "Niue", + "country_short_code": "NU", + }, + ), + ( + ExactValueMatcher(value="580"), + { + "country_code": "MNP", + "country": "Noerdliche Marianen", + "country_short_code": "MP", + }, + ), + ( + ExactValueMatcher(value="408"), + { + "country_code": "PRK", + "country": "Nordkorea", + "country_short_code": "KP", + }, + ), + ( + ExactValueMatcher(value="574"), + { + "country_code": "NFK", + "country": "Norfolk Inseln", + "country_short_code": "NF", + }, + ), + ( + ExactValueMatcher(value="578"), + { + "country_code": "NOR", + "country": "Norwegen", + "country_short_code": "NO", + }, + ), + ( + ExactValueMatcher(value="040"), + { + "country_code": "AUT", + "country": "Oesterreich", + "country_short_code": "AT", + }, + ), + ( + ExactValueMatcher(value="512"), + { + "country_code": "OMN", + "country": "Oman", + "country_short_code": "OM", + }, + ), + ( + ExactValueMatcher(value="626"), + { + "country_code": "TLS", + "country": "Osttimor", + "country_short_code": "TL", + }, + ), + ( + ExactValueMatcher(value="586"), + { + "country_code": "PAK", + "country": "Pakistan", + "country_short_code": "PK", + }, + ), + ( + ExactValueMatcher(value="275"), + { + "country_code": "PSE", + "country": "Palaestina", + "country_short_code": "PS", + }, + ), + ( + ExactValueMatcher(value="585"), + { + "country_code": "PLW", + "country": "Palau", + "country_short_code": "PW", + }, + ), + ( + ExactValueMatcher(value="591"), + { + "country_code": "PAN", + "country": "Panama", + "country_short_code": "PA", + }, + ), + ( + ExactValueMatcher(value="598"), + { + "country_code": "PNG", + "country": "Papua-Neuguinea", + "country_short_code": "PG", + }, + ), + ( + ExactValueMatcher(value="600"), + { + "country_code": "PRY", + "country": "Paraguay", + "country_short_code": "PY", + }, + ), + ( + ExactValueMatcher(value="604"), + { + "country_code": "PER", + "country": "Peru", + "country_short_code": "PE", + }, + ), + ( + ExactValueMatcher(value="608"), + { + "country_code": "PHL", + "country": "Philippinen", + "country_short_code": "PH", + }, + ), + ( + ExactValueMatcher(value="612"), + { + "country_code": "PCN", + "country": "Pitcairn", + "country_short_code": "PN", + }, + ), + ( + ExactValueMatcher(value="616"), + { + "country_code": "POL", + "country": "Polen", + "country_short_code": "PL", + }, + ), + ( + ExactValueMatcher(value="620"), + { + "country_code": "PRT", + "country": "Portugal", + "country_short_code": "PT", + }, + ), + ( + ExactValueMatcher(value="630"), + { + "country_code": "PRI", + "country": "Puerto Rico", + "country_short_code": "PR", + }, + ), + ( + ExactValueMatcher(value="638"), + { + "country_code": "REU", + "country": "Reunion", + "country_short_code": "RE", + }, + ), + ( + ExactValueMatcher(value="646"), + { + "country_code": "RWA", + "country": "Ruanda", + "country_short_code": "RW", + }, + ), + ( + ExactValueMatcher(value="642"), + { + "country_code": "ROU", + "country": "Rumaenien", + "country_short_code": "RO", + }, + ), + ( + ExactValueMatcher(value="643"), + { + "country_code": "RUS", + "country": "Russland", + "country_short_code": "RU", + }, + ), + ( + ExactValueMatcher(value="663"), + { + "country_code": "MAF", + "country": "Saint Martin", + "country_short_code": "MF", + }, + ), + ( + ExactValueMatcher(value="894"), + { + "country_code": "ZMB", + "country": "Samibia", + "country_short_code": "ZM", + }, + ), + ( + ExactValueMatcher(value="882"), + { + "country_code": "WSM", + "country": "Samoa", + "country_short_code": "WS", + }, + ), + ( + ExactValueMatcher(value="674"), + { + "country_code": "SMR", + "country": "San Marino", + "country_short_code": "SM", + }, + ), + ( + ExactValueMatcher(value="678"), + { + "country_code": "STP", + "country": "Sao Tome & Principe", + "country_short_code": "ST", + }, + ), + ( + ExactValueMatcher(value="682"), + { + "country_code": "SAU", + "country": "Saudi Arabien", + "country_short_code": "SA", + }, + ), + ( + ExactValueMatcher(value="752"), + { + "country_code": "SWE", + "country": "Schweden", + "country_short_code": "SE", + }, + ), + ( + ExactValueMatcher(value="756"), + { + "country_code": "CHE", + "country": "Schweiz", + "country_short_code": "CH", + }, + ), + ( + ExactValueMatcher(value="686"), + { + "country_code": "SEN", + "country": "Senegal", + "country_short_code": "SN", + }, + ), + ( + ExactValueMatcher(value="688"), + { + "country_code": "SRB", + "country": "Serbien", + "country_short_code": "RS", + }, + ), + ( + ExactValueMatcher(value="690"), + { + "country_code": "SYC", + "country": "Seychellen", + "country_short_code": "SC", + }, + ), + ( + ExactValueMatcher(value="694"), + { + "country_code": "SLE", + "country": "Sierra Leone", + "country_short_code": "SL", + }, + ), + ( + ExactValueMatcher(value="716"), + { + "country_code": "ZWE", + "country": "Simbabwe", + "country_short_code": "ZW", + }, + ), + ( + ExactValueMatcher(value="702"), + { + "country_code": "SGP", + "country": "Singapur", + "country_short_code": "SG", + }, + ), + ( + ExactValueMatcher(value="534"), + { + "country_code": "SXM", + "country": "Sint Maarten (niederlaendischer Teil)", + "country_short_code": "SX", + }, + ), + ( + ExactValueMatcher(value="703"), + { + "country_code": "SVK", + "country": "Slowakei", + "country_short_code": "SK", + }, + ), + ( + ExactValueMatcher(value="705"), + { + "country_code": "SVN", + "country": "Slowenien", + "country_short_code": "SI", + }, + ), + ( + ExactValueMatcher(value="090"), + { + "country_code": "SLB", + "country": "Solomon Inseln", + "country_short_code": "SB", + }, + ), + ( + ExactValueMatcher(value="706"), + { + "country_code": "SOM", + "country": "Somalia", + "country_short_code": "SO", + }, + ), + ( + ExactValueMatcher(value="724"), + { + "country_code": "ESP", + "country": "Spanien", + "country_short_code": "ES", + }, + ), + ( + ExactValueMatcher(value="144"), + { + "country_code": "LKA", + "country": "Sri Lanka", + "country_short_code": "LK", + }, + ), + ( + ExactValueMatcher(value="654"), + { + "country_code": "SHN", + "country": "St. Helena", + "country_short_code": "SH", + }, + ), + ( + ExactValueMatcher(value="659"), + { + "country_code": "KNA", + "country": "St. Kitts und Nevis", + "country_short_code": "KN", + }, + ), + ( + ExactValueMatcher(value="662"), + { + "country_code": "LCA", + "country": "St. Lucia", + "country_short_code": "LC", + }, + ), + ( + ExactValueMatcher(value="666"), + { + "country_code": "SPM", + "country": "St. Pierre & Miquelon", + "country_short_code": "PM", + }, + ), + ( + ExactValueMatcher(value="670"), + { + "country_code": "VCT", + "country": "St. Vincent und die Grenadinen", + "country_short_code": "VC", + }, + ), + ( + ExactValueMatcher(value="736"), + { + "country_code": "SDN", + "country": "Sudan", + "country_short_code": "SD", + }, + ), + ( + ExactValueMatcher(value="710"), + { + "country_code": "ZAF", + "country": "Suedafrika", + "country_short_code": "ZA", + }, + ), + ( + ExactValueMatcher(value="239"), + { + "country_code": "SGS", + "country": "Suedgeorgien und die Suedlichen Sandwichinseln", + "country_short_code": "GS", + }, + ), + ( + ExactValueMatcher(value="410"), + { + "country_code": "KOR", + "country": "Suedkorea", + "country_short_code": "KR", + }, + ), + ( + ExactValueMatcher(value="728"), + { + "country_code": "SSD", + "country": "Suedsudan", + "country_short_code": "SS", + }, + ), + ( + ExactValueMatcher(value="740"), + { + "country_code": "SUR", + "country": "Suriname", + "country_short_code": "SR", + }, + ), + ( + ExactValueMatcher(value="744"), + { + "country_code": "SJM", + "country": "Svalbard & Jan Mayen Inseln", + "country_short_code": "SJ", + }, + ), + ( + ExactValueMatcher(value="748"), + { + "country_code": "SWZ", + "country": "Swasiland", + "country_short_code": "SZ", + }, + ), + ( + ExactValueMatcher(value="760"), + { + "country_code": "SYR", + "country": "Syrien", + "country_short_code": "SY", + }, + ), + ( + ExactValueMatcher(value="762"), + { + "country_code": "TJK", + "country": "Tadschikistan", + "country_short_code": "TJ", + }, + ), + ( + ExactValueMatcher(value="158"), + { + "country_code": "TWN", + "country": "Taiwan", + "country_short_code": "TW", + }, + ), + ( + ExactValueMatcher(value="834"), + { + "country_code": "TZA", + "country": "Tansania", + "country_short_code": "TZ", + }, + ), + ( + ExactValueMatcher(value="764"), + { + "country_code": "THA", + "country": "Thailand", + "country_short_code": "TH", + }, + ), + ( + ExactValueMatcher(value="768"), + { + "country_code": "TGO", + "country": "Togo", + "country_short_code": "TG", + }, + ), + ( + ExactValueMatcher(value="772"), + { + "country_code": "TKL", + "country": "Tokelau", + "country_short_code": "TK", + }, + ), + ( + ExactValueMatcher(value="776"), + { + "country_code": "TON", + "country": "Tonga", + "country_short_code": "TO", + }, + ), + ( + ExactValueMatcher(value="780"), + { + "country_code": "TTO", + "country": "Trinidad & Tobago", + "country_short_code": "TT", + }, + ), + ( + ExactValueMatcher(value="148"), + { + "country_code": "TCD", + "country": "Tschad", + "country_short_code": "TD", + }, + ), + ( + ExactValueMatcher(value="203"), + { + "country_code": "CZE", + "country": "Tschechien (Republik)", + "country_short_code": "CZ", + }, + ), + ( + ExactValueMatcher(value="792"), + { + "country_code": "TUR", + "country": "Tuerkei", + "country_short_code": "TR", + }, + ), + ( + ExactValueMatcher(value="788"), + { + "country_code": "TUN", + "country": "Tunesien", + "country_short_code": "TN", + }, + ), + ( + ExactValueMatcher(value="795"), + { + "country_code": "TKM", + "country": "Turkmenistan", + "country_short_code": "TM", + }, + ), + ( + ExactValueMatcher(value="796"), + { + "country_code": "TCA", + "country": "Turks & Caicos-Inseln", + "country_short_code": "TC", + }, + ), + ( + ExactValueMatcher(value="798"), + { + "country_code": "TUV", + "country": "Tuvalu", + "country_short_code": "TV", + }, + ), + ( + ExactValueMatcher(value="800"), + { + "country_code": "UGA", + "country": "Uganda", + "country_short_code": "UG", + }, + ), + ( + ExactValueMatcher(value="804"), + { + "country_code": "UKR", + "country": "Ukraine", + "country_short_code": "UA", + }, + ), + ( + ExactValueMatcher(value="348"), + { + "country_code": "HUN", + "country": "Ungarn", + "country_short_code": "HU", + }, + ), + ( + ExactValueMatcher(value="858"), + { + "country_code": "URY", + "country": "Uruguay", + "country_short_code": "UY", + }, + ), + ( + ExactValueMatcher(value="850"), + { + "country_code": "VIR", + "country": "US Virgin Islands", + "country_short_code": "VI", + }, + ), + ( + ExactValueMatcher(value="840"), + { + "country_code": "USA", + "country": "USA", + "country_short_code": "US", + }, + ), + ( + ExactValueMatcher(value="860"), + { + "country_code": "UZB", + "country": "Usbekistan", + "country_short_code": "UZ", + }, + ), + ( + ExactValueMatcher(value="548"), + { + "country_code": "VUT", + "country": "Vanuatu", + "country_short_code": "VU", + }, + ), + ( + ExactValueMatcher(value="336"), + { + "country_code": "VAT", + "country": "Vatikan", + "country_short_code": "VA", + }, + ), + ( + ExactValueMatcher(value="862"), + { + "country_code": "VEN", + "country": "Venezuela", + "country_short_code": "VE", + }, + ), + ( + ExactValueMatcher(value="784"), + { + "country_code": "ARE", + "country": "Vereinigte Arabische Emirate", + "country_short_code": "AE", + }, + ), + ( + ExactValueMatcher(value="704"), + { + "country_code": "VNM", + "country": "Vietnam", + "country_short_code": "VN", + }, + ), + ( + ExactValueMatcher(value="876"), + { + "country_code": "WLF", + "country": "Wallis & Futuna", + "country_short_code": "WF", + }, + ), + ( + ExactValueMatcher(value="162"), + { + "country_code": "CXR", + "country": "Weihnachtsinseln", + "country_short_code": "CX", + }, + ), + ( + ExactValueMatcher(value="112"), + { + "country_code": "BLR", + "country": "Weissrussland", + "country_short_code": "BY", + }, + ), + ( + ExactValueMatcher(value="732"), + { + "country_code": "ESH", + "country": "West Sahara", + "country_short_code": "EH", + }, + ), + ( + ExactValueMatcher(value="140"), + { + "country_code": "CAF", + "country": "Zentralafrika", + "country_short_code": "CF", + }, + ), + ( + ExactValueMatcher(value="196"), + { + "country_code": "CYP", + "country": "Zypern", + "country_short_code": "CY", + }, + ), + ], + ), + ], + additional_validator=None, + checksum_validator=Mod_37_36(), + ), + TrackingNumberDefinition( + courier=Courier(code="dpd", name="DPD"), + product=Product(name="DPD (14)"), + number_regex=re.compile( + "\\s*(?P([0-9]\\s*){14})(?P[0-9A-Z]\\s*)", + ), + tracking_url_template="https://www.dpdgroup.com/nl/mydpd/my-parcels/track?lang=en&parcelNumber=%s", + serial_number_parser=DefaultSerialNumberParser(prepend_if=None), + additional=[], + additional_validator=None, + checksum_validator=Mod_37_36(), ), ]