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/.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/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. diff --git a/codegen.py b/codegen.py index 24d36a0..102c6fc 100644 --- a/codegen.py +++ b/codegen.py @@ -9,11 +9,14 @@ 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 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/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 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/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/tests/test_tracking_numbers.py b/tests/test_tracking_numbers.py index 58a7052..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, 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" 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" 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 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(): diff --git a/tracking_numbers/_generated.py b/tracking_numbers/_generated.py index 6bb1718..35a2d20 100644 --- a/tracking_numbers/_generated.py +++ b/tracking_numbers/_generated.py @@ -1,11 +1,14 @@ # 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 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 @@ -18,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*(?=.*[a-z])(?P([0-9a-f]\\s*){10,10})\\s*", + "\\s*L\\s*[AIEHNX]\\s*[1-3]\\s*(?P([0-9]\\s*){7,7})\\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 (15)"), + number_regex=re.compile( + "\\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=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"), @@ -69,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"), @@ -92,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"), @@ -108,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"), @@ -119,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"), @@ -162,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"), @@ -173,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"), @@ -195,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"), @@ -206,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"), @@ -276,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(), ), ] diff --git a/tracking_numbers/checksum_validator.py b/tracking_numbers/checksum_validator.py index abb5f0a..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: int) -> 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") @@ -26,13 +38,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,26 +58,28 @@ 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 _check_digit(self, serial_number: SerialNumber) -> int: total = 0 for digit, weight in zip(serial_number, self.WEIGHTS): - total += digit * weight + total += int(digit) * weight remainder = total % 11 if remainder == 1: - check = 0 - elif remainder == 0: - check = 5 - else: - check = 11 - remainder + return 0 - return check == check_digit + if remainder == 0: + return 5 + + return 11 - remainder class Mod10(ChecksumValidator): @@ -78,29 +98,65 @@ def __repr__(self): evens_multiplier=self.evens_multiplier, ) - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> int: 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 class Mod7(ChecksumValidator): - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: - return 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 _check_digit(self, serial_number: SerialNumber) -> str: + # From https://esolutions.dpd.com/dokumente/DPD_Parcel_Label_Specification_2.4.1_EN.pdf + cd = self.MOD + for char in serial_number: + if char.isalpha(): + val = ord(char.upper()) - ord("A") + 10 # A=10, B=11, ..., Z=35 + else: + val = int(char) + cd = val + cd + if cd > self.MOD: + cd = cd - self.MOD + cd = cd * 2 + if cd > self.MOD: + cd = cd - (self.MOD + 1) + cd = (self.MOD + 1) - cd + if cd == self.MOD: + cd = 0 + + 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): @@ -117,10 +173,30 @@ def __repr__(self): second_modulo=self.second_modulo, ) - def passes(self, serial_number: SerialNumber, check_digit: int) -> bool: + def _check_digit(self, serial_number: SerialNumber) -> int: total = 0 for digit, weight in zip(serial_number, self.weights): - total += digit * weight + total += int(digit) * weight + return total % self.first_modulo % self.second_modulo + + +class Luhn(ChecksumValidator): + """Luhn algorithm for validating a checksum digit. - check = total % self.first_modulo % self.second_modulo - return check == check_digit + See https://en.wikipedia.org/wiki/Luhn_algorithm + """ + + def _check_digit(self, serial_number: SerialNumber) -> int: + 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 diff --git a/tracking_numbers/definition.py b/tracking_numbers/definition.py index 82b31c2..29517f0 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 @@ -170,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: @@ -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/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"], 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 6e35d61..9ad3aa2 100644 --- a/tracking_numbers/types.py +++ b/tracking_numbers/types.py @@ -5,8 +5,10 @@ from typing import Optional from typing import Tuple + +Info = Dict[str, Any] Spec = Dict[str, Any] -SerialNumber = List[int] +SerialNumber = List[str] 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))) 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))