karrio-usps 2025.5rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. karrio/mappers/usps/__init__.py +3 -0
  2. karrio/mappers/usps/mapper.py +94 -0
  3. karrio/mappers/usps/proxy.py +155 -0
  4. karrio/mappers/usps/settings.py +26 -0
  5. karrio/plugins/usps/__init__.py +24 -0
  6. karrio/providers/usps/__init__.py +26 -0
  7. karrio/providers/usps/error.py +56 -0
  8. karrio/providers/usps/manifest.py +100 -0
  9. karrio/providers/usps/pickup/__init__.py +4 -0
  10. karrio/providers/usps/pickup/cancel.py +40 -0
  11. karrio/providers/usps/pickup/create.py +102 -0
  12. karrio/providers/usps/pickup/update.py +109 -0
  13. karrio/providers/usps/rate.py +204 -0
  14. karrio/providers/usps/shipment/__init__.py +9 -0
  15. karrio/providers/usps/shipment/cancel.py +53 -0
  16. karrio/providers/usps/shipment/create.py +279 -0
  17. karrio/providers/usps/tracking.py +112 -0
  18. karrio/providers/usps/units.py +303 -0
  19. karrio/providers/usps/utils.py +320 -0
  20. karrio/schemas/usps/__init__.py +0 -0
  21. karrio/schemas/usps/error_response.py +31 -0
  22. karrio/schemas/usps/label_request.py +142 -0
  23. karrio/schemas/usps/label_response.py +84 -0
  24. karrio/schemas/usps/pickup_request.py +49 -0
  25. karrio/schemas/usps/pickup_response.py +58 -0
  26. karrio/schemas/usps/pickup_update_request.py +55 -0
  27. karrio/schemas/usps/pickup_update_response.py +58 -0
  28. karrio/schemas/usps/rate_request.py +38 -0
  29. karrio/schemas/usps/rate_response.py +89 -0
  30. karrio/schemas/usps/scan_form_request.py +36 -0
  31. karrio/schemas/usps/scan_form_response.py +46 -0
  32. karrio/schemas/usps/tracking_response.py +113 -0
  33. karrio_usps-2025.5rc1.dist-info/METADATA +45 -0
  34. karrio_usps-2025.5rc1.dist-info/RECORD +37 -0
  35. karrio_usps-2025.5rc1.dist-info/WHEEL +5 -0
  36. karrio_usps-2025.5rc1.dist-info/entry_points.txt +2 -0
  37. karrio_usps-2025.5rc1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,112 @@
1
+ """Karrio USPS rating API implementation."""
2
+
3
+ # import karrio.schemas.usps.tracking_request as usps
4
+ import karrio.schemas.usps.tracking_response as tracking
5
+
6
+ import typing
7
+ import karrio.lib as lib
8
+ import karrio.core.units as units
9
+ import karrio.core.models as models
10
+ import karrio.providers.usps.error as error
11
+ import karrio.providers.usps.utils as provider_utils
12
+ import karrio.providers.usps.units as provider_units
13
+
14
+
15
+ def parse_tracking_response(
16
+ _response: lib.Deserializable[typing.List[typing.Tuple[str, dict]]],
17
+ settings: provider_utils.Settings,
18
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
19
+ responses = _response.deserialize()
20
+
21
+ messages: typing.List[models.Message] = sum(
22
+ [
23
+ error.parse_error_response(response, settings, tracking_number=_)
24
+ for _, response in responses
25
+ ],
26
+ start=[],
27
+ )
28
+ tracking_details = [
29
+ _extract_details(details, settings)
30
+ for _, details in responses
31
+ if "error" not in details
32
+ ]
33
+
34
+ return tracking_details, messages
35
+
36
+
37
+ def _extract_details(
38
+ data: dict,
39
+ settings: provider_utils.Settings,
40
+ ) -> models.TrackingDetails:
41
+ details = lib.to_object(tracking.TrackingResponseType, data)
42
+ status = next(
43
+ (
44
+ status.name
45
+ for status in list(provider_units.TrackingStatus)
46
+ if any(
47
+ _.lower() in getattr(details, "status", "").lower()
48
+ for _ in status.value
49
+ )
50
+ or any(
51
+ _.lower() in getattr(details, "statusCategory", "").lower()
52
+ for _ in status.value
53
+ )
54
+ ),
55
+ provider_units.TrackingStatus.in_transit.name,
56
+ )
57
+
58
+ return models.TrackingDetails(
59
+ carrier_id=settings.carrier_id,
60
+ carrier_name=settings.carrier_name,
61
+ tracking_number=details.trackingNumber,
62
+ events=[
63
+ models.TrackingEvent(
64
+ date=lib.fdate(
65
+ event.eventTimestamp,
66
+ try_formats=["%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S"],
67
+ ),
68
+ description=event.eventType,
69
+ code=event.eventCode,
70
+ time=lib.flocaltime(
71
+ event.eventTimestamp,
72
+ try_formats=["%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S"],
73
+ ),
74
+ location=lib.text(
75
+ event.eventCity,
76
+ event.eventZIP,
77
+ event.eventState,
78
+ event.eventCountry,
79
+ separator=", ",
80
+ ),
81
+ )
82
+ for event in details.trackingEvents
83
+ ],
84
+ estimated_delivery=lib.fdate(
85
+ details.expectedDeliveryTimeStamp,
86
+ try_formats=["%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S"],
87
+ ),
88
+ delivered=status == "delivered",
89
+ status=status,
90
+ info=models.TrackingInfo(
91
+ # fmt: off
92
+ carrier_tracking_link=settings.tracking_url.format(details.trackingNumber),
93
+ expected_delivery=lib.fdate(details.expectedDeliveryTimeStamp, "%Y-%m-%dT%H:%M:%SZ"),
94
+ shipment_service=provider_units.ShippingService.map(details.mailClass).name_or_key,
95
+ shipment_origin_country=details.originCountry,
96
+ shipment_origin_postal_code=details.originZIP,
97
+ shipment_destination_country=details.destinationCountryCode,
98
+ shipment_destination_postal_code=details.destinationZIP,
99
+ # fmt: on
100
+ ),
101
+ )
102
+
103
+
104
+ def tracking_request(
105
+ payload: models.TrackingRequest,
106
+ settings: provider_utils.Settings,
107
+ ) -> lib.Serializable:
108
+
109
+ # map data to convert karrio model to usps specific type
110
+ request = payload.tracking_numbers
111
+
112
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,303 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+ import karrio.core.units as units
4
+
5
+
6
+ class PackagingType(lib.StrEnum):
7
+ """Carrier specific packaging type"""
8
+
9
+ usps_3_digit = "3D"
10
+ usps_3_digit_dimensional_rectangular = "3N"
11
+ usps_3_digit_dimensional_nonrectangular = "3R"
12
+ usps_5_digit = "5D"
13
+ usps_basic = "BA"
14
+ usps_mixed_ndc = "BB"
15
+ usps_ndc = "BM"
16
+ usps_cubic_pricing_tier_1 = "C1"
17
+ usps_cubic_pricing_tier_2 = "C2"
18
+ usps_cubic_pricing_tier_3 = "C3"
19
+ usps_cubic_pricing_tier_4 = "C4"
20
+ usps_cubic_pricing_tier_5 = "C5"
21
+ usps_cubic_parcel = "CP"
22
+ usps_connect_local = "CM"
23
+ usps_non_presorted = "NP"
24
+ usps_full_tray_box = "O1"
25
+ usps_half_tray_box = "O2"
26
+ usps_emm_tray_box = "O3"
27
+ usps_flat_tub_tray_box = "O4"
28
+ usps_surface_transported_pallet = "O5"
29
+ usps_half_pallet_box = "O7"
30
+ usps_oversized = "OS"
31
+ usps_cubic_soft_pack_tier_1 = "P5"
32
+ usps_cubic_soft_pack_tier_2 = "P6"
33
+ usps_cubic_soft_pack_tier_3 = "P7"
34
+ usps_cubic_soft_pack_tier_4 = "P8"
35
+ usps_cubic_soft_pack_tier_5 = "P9"
36
+ usps_cubic_soft_pack_tier_6 = "Q6"
37
+ usps_cubic_soft_pack_tier_7 = "Q7"
38
+ usps_cubic_soft_pack_tier_8 = "Q8"
39
+ usps_cubic_soft_pack_tier_9 = "Q9"
40
+ usps_cubic_soft_pack_tier_10 = "Q0"
41
+ usps_priority_mail_express_single_piece = "PA"
42
+ usps_large_flat_rate_box = "PL"
43
+ usps_large_flat_rate_box_apofpo = "PM"
44
+ usps_presorted = "PR"
45
+ usps_small_flat_rate_bag = "SB"
46
+ usps_scf_dimensional_nonrectangular = "SN"
47
+ usps_scf_dimensional_rectangular = "SR"
48
+ usps_single_piece = "SP"
49
+
50
+ """ Unified Packaging type mapping """
51
+ envelope = usps_single_piece
52
+ pak = usps_single_piece
53
+ tube = usps_scf_dimensional_nonrectangular
54
+ pallet = usps_large_flat_rate_box
55
+ small_box = usps_single_piece
56
+ medium_box = usps_scf_dimensional_rectangular
57
+ your_packaging = usps_single_piece
58
+
59
+
60
+ class ContentType(lib.StrEnum):
61
+ HAZMAT = "HAZMAT"
62
+ CREMATED_REMAINS = "CREMATED_REMAINS"
63
+ BEES = "BEES"
64
+ DAY_OLD_POULTRY = "DAY_OLD_POULTRY"
65
+ ADULT_BIRDS = "ADULT_BIRDS"
66
+ OTHER_LIVES = "OTHER_LIVES"
67
+ PERISHABLE = "PERISHABLE"
68
+ PHARMACEUTICALS = "PHARMACEUTICALS"
69
+ MEDICAL_SUPPLIES = "MEDICAL_SUPPLIES"
70
+ FRUITS = "FRUITS"
71
+ VEGETABLES = "VEGETABLES"
72
+ LIVE_PLANTS = "LIVE_PLANTS"
73
+
74
+
75
+ class LabelType(lib.StrEnum):
76
+ """Carrier specific label type"""
77
+
78
+ PDF = "PDF"
79
+ TIFF = "TIFF"
80
+ JPG = "JPG"
81
+ SVG = "SVG"
82
+ ZPL203DPI = "ZPL203DPI"
83
+ ZPL300DPI = "ZPL300DPI"
84
+ LABEL_BROKER = "LABEL_BROKER"
85
+ NONE = "NONE"
86
+
87
+ """ Unified Label type mapping """
88
+ ZPL = ZPL203DPI
89
+ PNG = JPG
90
+
91
+
92
+ class ShippingService(lib.StrEnum):
93
+ """Carrier specific services"""
94
+
95
+ usps_parcel_select_lightweight = "PARCEL_SELECT_LIGHTWEIGHT"
96
+ usps_parcel_select = "PARCEL_SELECT"
97
+ usps_priority_mail_express = "PRIORITY_MAIL_EXPRESS"
98
+ usps_priority_mail = "PRIORITY_MAIL"
99
+ usps_library_mail = "LIBRARY_MAIL"
100
+ usps_media_mail = "MEDIA_MAIL"
101
+ usps_bound_printed_matter = "BOUND_PRINTED_MATTER"
102
+ usps_connect_local = "USPS_CONNECT_LOCAL"
103
+ usps_connect_mail = "USPS_CONNECT_MAIL"
104
+ usps_connect_next_day = "USPS_CONNECT_NEXT_DAY"
105
+ usps_connect_regional = "USPS_CONNECT_REGIONAL"
106
+ usps_connect_same_day = "USPS_CONNECT_SAME_DAY"
107
+ usps_ground_advantage = "USPS_GROUND_ADVANTAGE"
108
+ usps_domestic_matter_for_the_blind = "DOMESTIC_MATTER_FOR_THE_BLIND"
109
+ usps_all = "ALL"
110
+
111
+ @classmethod
112
+ def to_product_code(cls, product_name: str) -> str:
113
+ """Convert product description to product code
114
+
115
+ to_product_code("Priority Mail Padded Flat Rate Envelope") -> "usps_priority_mail_padded_flat_rate_envelope"
116
+
117
+ Args:
118
+ product_name (str): Product name
119
+
120
+ Returns:
121
+ str: Service code
122
+ """
123
+ return lib.to_slug("usps", product_name).replace("usps_usps_", "usps_")
124
+
125
+ @classmethod
126
+ def to_product_name(cls, product_code: str) -> str:
127
+ """Convert product code to product name
128
+
129
+ to_product_name("usps_priority_mail_padded_flat_rate_envelope") -> "USPS PRIORITY MAIL PADDED FLAT RATE ENVELOPE"
130
+
131
+ Args:
132
+ product_code (str): Service code
133
+
134
+ Returns:
135
+ str: Product name
136
+ """
137
+ if not product_code:
138
+ return ""
139
+
140
+ # Remove the "usps_" prefix if present
141
+ if product_code.startswith("usps_"):
142
+ product_code = product_code[5:] # Remove "usps_" (5 characters)
143
+
144
+ # Replace underscores with spaces and convert to uppercase
145
+ product_name = product_code.replace("_", " ").upper()
146
+
147
+ # Add "USPS" prefix
148
+ return f"USPS {product_name}"
149
+
150
+ @classmethod
151
+ def to_mail_class(cls, product_code: str) -> typing.Optional['ShippingService']:
152
+ """Convert product code to mail class
153
+
154
+ to_mail_class("usps_priority_mail_padded_flat_rate_envelope") -> "PRIORITY_MAIL"
155
+
156
+ Args:
157
+ product_code (str): Service code
158
+
159
+ Returns:
160
+ str: ShippingService
161
+ """
162
+ return ShippingService.map(
163
+ next((
164
+ service for service in list(cls) if service.name in product_code
165
+ ), None)
166
+ )
167
+
168
+
169
+ INCOMPATIBLE_SERVICES = [
170
+ ShippingService.usps_ground_advantage.name, # type: ignore
171
+ ]
172
+
173
+
174
+ class ShippingOption(lib.Enum):
175
+ """Carrier specific options"""
176
+
177
+ # fmt: off
178
+ usps_label_delivery_service = lib.OptionEnum("415", bool)
179
+ usps_tracking_plus_6_months = lib.OptionEnum("480", bool)
180
+ usps_tracking_plus_1_year = lib.OptionEnum("481", bool)
181
+ usps_tracking_plus_3_years = lib.OptionEnum("482", bool)
182
+ usps_tracking_plus_5_years = lib.OptionEnum("483", bool)
183
+ usps_tracking_plus_7_years = lib.OptionEnum("484", bool)
184
+ usps_tracking_plus_10_years = lib.OptionEnum("485", bool)
185
+ usps_tracking_plus_signature_3_years = lib.OptionEnum("486", bool)
186
+ usps_tracking_plus_signature_5_years = lib.OptionEnum("487", bool)
187
+ usps_tracking_plus_signature_7_years = lib.OptionEnum("488", bool)
188
+ usps_tracking_plus_signature_10_years = lib.OptionEnum("489", bool)
189
+ usps_hazardous_materials_air_eligible_ethanol = lib.OptionEnum("810", bool)
190
+ usps_hazardous_materials_class_1_toy_propellant_safety_fuse_package = lib.OptionEnum("811", bool)
191
+ usps_hazardous_materials_class_3_flammable_and_combustible_liquids = lib.OptionEnum("812", bool)
192
+ usps_hazardous_materials_class_7_radioactive_materials = lib.OptionEnum("813", bool)
193
+ usps_hazardous_materials_class_8_air_eligible_corrosive_materials = lib.OptionEnum("814", bool)
194
+ usps_hazardous_materials_class_8_nonspillable_wet_batteries = lib.OptionEnum("815", bool)
195
+ usps_hazardous_materials_class_9_lithium_battery_marked_ground_only = lib.OptionEnum("816", bool)
196
+ usps_hazardous_materials_class_9_lithium_battery_returns = lib.OptionEnum("817", bool)
197
+ usps_hazardous_materials_class_9_marked_lithium_batteries = lib.OptionEnum("818", bool)
198
+ usps_hazardous_materials_class_9_dry_ice = lib.OptionEnum("819", bool)
199
+ usps_hazardous_materials_class_9_unmarked_lithium_batteries = lib.OptionEnum("820", bool)
200
+ usps_hazardous_materials_class_9_magnetized_materials = lib.OptionEnum("821", bool)
201
+ usps_hazardous_materials_division_4_1_mailable_flammable_solids_and_safety_matches = lib.OptionEnum("822", bool)
202
+ usps_hazardous_materials_division_5_1_oxidizers = lib.OptionEnum("823", bool)
203
+ usps_hazardous_materials_division_5_2_organic_peroxides = lib.OptionEnum("824", bool)
204
+ usps_hazardous_materials_division_6_1_toxic_materials = lib.OptionEnum("825", bool)
205
+ usps_hazardous_materials_division_6_2_biological_materials = lib.OptionEnum("826", bool)
206
+ usps_hazardous_materials_excepted_quantity_provision = lib.OptionEnum("827", bool)
207
+ usps_hazardous_materials_ground_only_hazardous_materials = lib.OptionEnum("828", bool)
208
+ usps_hazardous_materials_air_eligible_id8000_consumer_commodity = lib.OptionEnum("829", bool)
209
+ usps_hazardous_materials_lighters = lib.OptionEnum("830", bool)
210
+ usps_hazardous_materials_limited_quantity_ground = lib.OptionEnum("831", bool)
211
+ usps_hazardous_materials_small_quantity_provision_markings_required = lib.OptionEnum("832", bool)
212
+ usps_hazardous_materials = lib.OptionEnum("857", bool)
213
+ usps_certified_mail = lib.OptionEnum("910", bool)
214
+ usps_certified_mail_restricted_delivery = lib.OptionEnum("911", bool)
215
+ usps_certified_mail_adult_signature_required = lib.OptionEnum("912", bool)
216
+ usps_certified_mail_adult_signature_restricted_delivery = lib.OptionEnum("913", bool)
217
+ usps_collect_on_delivery = lib.OptionEnum("915", float)
218
+ usps_collect_on_delivery_restricted_delivery = lib.OptionEnum("917", bool)
219
+ usps_tracking_electronic = lib.OptionEnum("920", bool)
220
+ usps_signature_confirmation = lib.OptionEnum("921", bool)
221
+ usps_adult_signature_required = lib.OptionEnum("922", bool)
222
+ usps_adult_signature_restricted_delivery = lib.OptionEnum("923", bool)
223
+ usps_signature_confirmation_restricted_delivery = lib.OptionEnum("924", bool)
224
+ usps_priority_mail_express_merchandise_insurance = lib.OptionEnum("925", bool)
225
+ usps_insurance_below_500 = lib.OptionEnum("930", float)
226
+ usps_insurance_above_500 = lib.OptionEnum("931", float)
227
+ usps_insurance_restricted_delivery = lib.OptionEnum("934", bool)
228
+ usps_registered_mail = lib.OptionEnum("940", bool)
229
+ usps_registered_mail_restricted_delivery = lib.OptionEnum("941", bool)
230
+ usps_return_receipt = lib.OptionEnum("955", bool)
231
+ usps_return_receipt_electronic = lib.OptionEnum("957", bool)
232
+ usps_signature_requested_priority_mail_express_only = lib.OptionEnum("981", bool)
233
+ usps_parcel_locker_delivery = lib.OptionEnum("984", bool)
234
+ usps_po_to_addressee_priority_mail_express_only = lib.OptionEnum("986", bool)
235
+ usps_sunday_delivery = lib.OptionEnum("981", bool)
236
+
237
+ """ Custom Options """
238
+ usps_mail_class = lib.OptionEnum("mailClass", ShippingService)
239
+ usps_facility_id = lib.OptionEnum("facilityId")
240
+ usps_machinable_piece = lib.OptionEnum("machinable", bool)
241
+ usps_hold_for_pickup = lib.OptionEnum("holdForPickup", bool)
242
+ usps_processing_category = lib.OptionEnum("processingCategory")
243
+ usps_carrier_release = lib.OptionEnum("carrierRelease", bool)
244
+ usps_physical_signature_required = lib.OptionEnum("physicalSignatureRequired", bool)
245
+ usps_price_type = lib.OptionEnum("priceType", lib.units.create_enum("priceType", ["RETAIL", "COMMERCIAL", "CONTRACT"]))
246
+ usps_destination_entry_facility_type = lib.OptionEnum("destinationEntryFacilityType", lib.units.create_enum("destinationEntryFacilityType", ["NONE", "DESTINATION_NETWORK_DISTRIBUTION_CENTER", "DESTINATION_SECTIONAL_CENTER_FACILITY", "DESTINATION_DELIVERY_UNIT", "DESTINATION_SERVICE_HUB"]))
247
+ usps_extra_services = lib.OptionEnum("extraServices", list)
248
+ usps_shipping_filter = lib.OptionEnum("shippingFilter", lib.units.create_enum("shippingFilter", ["PRICE", "SERVICE_STANDARDS"]))
249
+
250
+ """ Unified Option type mapping """
251
+ cash_on_delivery = usps_collect_on_delivery
252
+ signature_confirmation = usps_signature_confirmation
253
+ sunday_delivery = usps_sunday_delivery
254
+ hold_at_location = usps_hold_for_pickup
255
+ # fmt: on
256
+
257
+
258
+ CUSTOM_OPTIONS = [
259
+ ShippingOption.usps_mail_class.name,
260
+ ShippingOption.usps_extra_services.name,
261
+ ShippingOption.usps_facility_id.name,
262
+ ShippingOption.usps_machinable_piece.name,
263
+ ShippingOption.usps_hold_for_pickup.name,
264
+ ShippingOption.usps_processing_category.name,
265
+ ShippingOption.usps_carrier_release.name,
266
+ ShippingOption.usps_physical_signature_required.name,
267
+ ShippingOption.usps_price_type.name,
268
+ ShippingOption.usps_destination_entry_facility_type.name,
269
+ ShippingOption.usps_shipping_filter.name,
270
+ ]
271
+
272
+
273
+ def shipping_options_initializer(
274
+ options: dict,
275
+ package_options: units.ShippingOptions = None,
276
+ ) -> units.ShippingOptions:
277
+ """
278
+ Apply default values to the given options.
279
+ """
280
+
281
+ if package_options is not None:
282
+ options.update(package_options.content)
283
+
284
+ if "insurance" in options:
285
+ if lib.to_money(options["insurance"]) > 500:
286
+ options[ShippingOption.usps_insurance_above_500.name] = options["insurance"]
287
+ else:
288
+ options[ShippingOption.usps_insurance_below_500.name] = options["insurance"]
289
+
290
+ def items_filter(key: str) -> bool:
291
+ return key in ShippingOption # type: ignore
292
+
293
+ return units.ShippingOptions(options, ShippingOption, items_filter=items_filter)
294
+
295
+
296
+ class TrackingStatus(lib.Enum):
297
+ on_hold = ["on hold"]
298
+ delivered = ["delivered"]
299
+ in_transit = ["in transit"]
300
+ delivery_failed = ["delivery failed"]
301
+ delivery_delayed = ["delivery delayed"]
302
+ out_for_delivery = ["out for delivery"]
303
+ ready_for_pickup = ["ready for pickup"]