From e84a2c795c82363720d4976e15edc3d44b91970c Mon Sep 17 00:00:00 2001 From: Ansh Dev Nagar Date: Wed, 24 Dec 2025 22:32:52 +0530 Subject: [PATCH 1/3] fix(easypost): resolve service mis-mapping for carriers with duplicate service names --- .../karrio/providers/easypost/units.py | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/plugins/easypost/karrio/providers/easypost/units.py b/plugins/easypost/karrio/providers/easypost/units.py index 8ea596d..8690a87 100644 --- a/plugins/easypost/karrio/providers/easypost/units.py +++ b/plugins/easypost/karrio/providers/easypost/units.py @@ -863,12 +863,35 @@ class Service(lib.StrEnum): @staticmethod def info(serviceName, carrier): rate_provider = CarrierId.map(carrier).name_or_key - service = Service.map(serviceName) + + # Try carrier-qualified lookup first to avoid service name collisions + service_code = None + carrier_enum = CarrierId.map(carrier) + + if carrier_enum.name and serviceName: + # Construct carrier-qualified service code: easypost_{carrier}_{service} + carrier_code = carrier_enum.name + service_snake = lib.to_snake_case(serviceName) + qualified_service_name = f"easypost_{carrier_code}_{service_snake}" + + # Check if the qualified service exists + # Note: We cannot use Service.map() because Python enums with duplicate + # values create aliases, so Service.easypost_usps_priority and + # Service.easypost_canadapost_priority are the SAME object (both = 'Priority') + if hasattr(Service, qualified_service_name): + service_code = qualified_service_name + + # Fallback to original behavior if qualified lookup fails + if not service_code: + service = Service.map(serviceName) + service_code = service.name_or_key + + # Format the service name for display service_name = re.sub( - r"((?<=[a-z])[A-Z]|(? Date: Thu, 25 Dec 2025 00:58:02 +0530 Subject: [PATCH 2/3] test(easypost): add service collision resolution test --- plugins/easypost/tests/easypost/test_rate.py | 115 +++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/plugins/easypost/tests/easypost/test_rate.py b/plugins/easypost/tests/easypost/test_rate.py index b43d161..e853e54 100644 --- a/plugins/easypost/tests/easypost/test_rate.py +++ b/plugins/easypost/tests/easypost/test_rate.py @@ -40,6 +40,14 @@ def test_parse_error_response(self): self.assertListEqual(DP.to_dict(parsed_response), ParsedErrorResponse) + def test_service_collision_fix(self): + """Test that service collisions are resolved using carrier context""" + with patch("karrio.mappers.easypost.proxy.lib.request") as mock: + mock.return_value = ServiceCollisionResponseJSON + parsed_response = Rating.fetch(self.RateRequest).from_(gateway).parse() + + self.assertListEqual(DP.to_dict(parsed_response), ParsedServiceCollisionResponse) + if __name__ == "__main__": unittest.main() @@ -303,3 +311,110 @@ def test_parse_error_response(self): } } """ + +# Test data for service collision fix +ServiceCollisionResponseJSON = """{ + "id": "shp_test_collision", + "object": "Shipment", + "rates": [ + { + "id": "rate_usps_priority", + "object": "Rate", + "carrier_account_id": "ca_usps", + "service": "Priority", + "rate": "15.85", + "carrier": "USPS", + "shipment_id": "shp_test_collision", + "delivery_days": 3, + "created_at": "2025-12-24T10:00:00Z", + "updated_at": "2025-12-24T10:00:00Z" + }, + { + "id": "rate_ups_ground", + "object": "Rate", + "carrier_account_id": "ca_ups", + "service": "Ground", + "rate": "17.09", + "carrier": "UPS", + "shipment_id": "shp_test_collision", + "delivery_days": 3, + "created_at": "2025-12-24T10:00:00Z", + "updated_at": "2025-12-24T10:00:00Z" + }, + { + "id": "rate_canadapost_priority", + "object": "Rate", + "carrier_account_id": "ca_canadapost", + "service": "Priority", + "rate": "18.50", + "carrier": "Canada Post", + "shipment_id": "shp_test_collision", + "delivery_days": 2, + "created_at": "2025-12-24T10:00:00Z", + "updated_at": "2025-12-24T10:00:00Z" + }, + { + "id": "rate_canpar_ground", + "object": "Rate", + "carrier_account_id": "ca_canpar", + "service": "Ground", + "rate": "12.99", + "carrier": "Canpar", + "shipment_id": "shp_test_collision", + "delivery_days": 4, + "created_at": "2025-12-24T10:00:00Z", + "updated_at": "2025-12-24T10:00:00Z" + } + ] +} +""" + +ParsedServiceCollisionResponse = [ + [ + { + "carrier_id": "easypost", + "carrier_name": "easypost", + "meta": { + "rate_provider": "usps", + "service_name": "usps_priority", + }, + "service": "easypost_usps_priority", + "total_charge": 15.85, + "transit_days": 3, + }, + { + "carrier_id": "easypost", + "carrier_name": "easypost", + "meta": { + "rate_provider": "ups", + "service_name": "ups_ground", + }, + "service": "easypost_ups_ground", + "total_charge": 17.09, + "transit_days": 3, + }, + { + "carrier_id": "easypost", + "carrier_name": "easypost", + "meta": { + "rate_provider": "canadapost", + "service_name": "canadapost_priority", + }, + "service": "easypost_canadapost_priority", + "total_charge": 18.5, + "transit_days": 2, + }, + { + "carrier_id": "easypost", + "carrier_name": "easypost", + "meta": { + "rate_provider": "canpar", + "service_name": "canpar_ground", + }, + "service": "easypost_canpar_ground", + "total_charge": 12.99, + "transit_days": 4, + }, + ], + [], +] From de0bc6430b464d242b20ffc7a89a5f2cc7b07443 Mon Sep 17 00:00:00 2001 From: Ansh Dev Nagar Date: Thu, 25 Dec 2025 01:59:36 +0530 Subject: [PATCH 3/3] fix(easypost): add carrier name normalization for API variants --- .../easypost/karrio/providers/easypost/units.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/easypost/karrio/providers/easypost/units.py b/plugins/easypost/karrio/providers/easypost/units.py index 8690a87..611a457 100644 --- a/plugins/easypost/karrio/providers/easypost/units.py +++ b/plugins/easypost/karrio/providers/easypost/units.py @@ -5,6 +5,16 @@ import karrio.core.models as models +# EasyPost API carrier name normalization mapping +# Maps EasyPost's carrier variations to Karrio's standard CarrierId enum names +CARRIER_NAME_NORMALIZATION = { + "UPSDAP": "UPS", + "UPS DAP": "UPS", + "FedExDefault": "FedEx", + "FedEx Default": "FedEx", +} + + class LabelType(lib.Enum): PDF = "PDF" ZPL = "ZPL" @@ -862,11 +872,13 @@ class Service(lib.StrEnum): @staticmethod def info(serviceName, carrier): - rate_provider = CarrierId.map(carrier).name_or_key + # Normalize carrier name to handle EasyPost API variations + normalized_carrier = CARRIER_NAME_NORMALIZATION.get(carrier, carrier) + rate_provider = CarrierId.map(normalized_carrier).name_or_key # Try carrier-qualified lookup first to avoid service name collisions service_code = None - carrier_enum = CarrierId.map(carrier) + carrier_enum = CarrierId.map(normalized_carrier) if carrier_enum.name and serviceName: # Construct carrier-qualified service code: easypost_{carrier}_{service}