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,109 @@
1
+ """Karrio USPS update pickup implementation."""
2
+
3
+ import karrio.schemas.usps.pickup_update_request as usps
4
+ import karrio.schemas.usps.pickup_update_response as pickup
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_pickup_update_response(
16
+ _response: lib.Deserializable[dict],
17
+ settings: provider_utils.Settings,
18
+ ) -> typing.Tuple[typing.List[models.PickupDetails], typing.List[models.Message]]:
19
+ response = _response.deserialize()
20
+
21
+ messages = error.parse_error_response(response, settings)
22
+ pickup = (
23
+ _extract_details(response, settings)
24
+ if "confirmationNumber" in response
25
+ else None
26
+ )
27
+
28
+ return pickup, messages
29
+
30
+
31
+ def _extract_details(
32
+ data: dict,
33
+ settings: provider_utils.Settings,
34
+ ) -> models.PickupDetails:
35
+ details = lib.to_object(pickup.PickupUpdateResponseType, data)
36
+
37
+ return models.PickupDetails(
38
+ carrier_id=settings.carrier_id,
39
+ carrier_name=settings.carrier_name,
40
+ confirmation_number=details.confirmationNumber,
41
+ pickup_date=lib.fdate(details.pickupDate),
42
+ )
43
+
44
+
45
+ def pickup_update_request(
46
+ payload: models.PickupUpdateRequest,
47
+ settings: provider_utils.Settings,
48
+ ) -> lib.Serializable:
49
+ address = lib.to_address(payload.address)
50
+ packages = lib.to_packages(payload.parcels)
51
+ options = lib.units.Options(
52
+ payload.options,
53
+ option_type=lib.units.create_enum(
54
+ "PickupOptions",
55
+ # fmt: off
56
+ {
57
+ "usps_package_type": lib.OptionEnum("usps_package_type"),
58
+ },
59
+ # fmt: on
60
+ ),
61
+ )
62
+
63
+ # map data to convert karrio model to usps specific type
64
+ request = usps.PickupUpdateRequestType(
65
+ pickupDate=lib.fdate(payload.pickup_date),
66
+ carrierPickupRequest=usps.CarrierPickupRequestType(
67
+ pickupDate=lib.fdate(payload.pickup_date),
68
+ pickupAddress=usps.PickupAddressType(
69
+ firstName=address.person_name,
70
+ lastName=None,
71
+ firm=address.company_name,
72
+ address=usps.AddressType(
73
+ streetAddress=address.address_line1,
74
+ secondaryAddress=address.address_line2,
75
+ city=address.city,
76
+ state=address.state,
77
+ ZIPCode=lib.to_zip5(address.postal_code),
78
+ ZIPPlus4=lib.to_zip4(address.postal_code) or "",
79
+ urbanization=None,
80
+ ),
81
+ contact=[
82
+ usps.ContactType(email=address.email)
83
+ for _ in [address.email]
84
+ if _ is not None
85
+ ],
86
+ ),
87
+ packages=[
88
+ usps.PackageType(
89
+ packageType=options.usps_package_type.state or "OTHER",
90
+ packageCount=len(packages),
91
+ )
92
+ ],
93
+ estimatedWeight=packages.weight.LB,
94
+ pickupLocation=lib.identity(
95
+ usps.PickupLocationType(
96
+ packageLocation=payload.package_location,
97
+ specialInstructions=payload.instruction,
98
+ )
99
+ if any([payload.package_location, payload.instruction])
100
+ else None
101
+ ),
102
+ ),
103
+ )
104
+
105
+ return lib.Serializable(
106
+ request,
107
+ lib.to_dict,
108
+ dict(confirmationNumber=payload.confirmation_number),
109
+ )
@@ -0,0 +1,204 @@
1
+ """Karrio USPS rating API implementation."""
2
+
3
+ import karrio.schemas.usps.rate_request as usps
4
+ import karrio.schemas.usps.rate_response as rating
5
+
6
+ import time
7
+ import typing
8
+ import karrio.lib as lib
9
+ import karrio.core.units as units
10
+ import karrio.core.models as models
11
+ import karrio.core.errors as errors
12
+ import karrio.providers.usps.error as error
13
+ import karrio.providers.usps.utils as provider_utils
14
+ import karrio.providers.usps.units as provider_units
15
+
16
+
17
+ def parse_rate_response(
18
+ _response: lib.Deserializable[dict],
19
+ settings: provider_utils.Settings,
20
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
21
+ responses = _response.deserialize()
22
+
23
+ messages = error.parse_error_response(responses, settings)
24
+ rates = lib.to_multi_piece_rates(
25
+ [
26
+ (
27
+ f"{_}",
28
+ [
29
+ _ for _ in [
30
+ _extract_details(dict(rate=rate, rateOption=rateOption), settings, _response.ctx)
31
+ for pricingOption in response.get("pricingOptions", [])
32
+ for shippingOption in pricingOption.get("shippingOptions", [])
33
+ for rateOption in shippingOption.get("rateOptions", [])
34
+ for rate in rateOption.get("rates", [])
35
+ ]
36
+ if _ is not None
37
+ ],
38
+ )
39
+ for _, response in enumerate(responses, start=1)
40
+ ]
41
+ )
42
+
43
+ return rates, messages
44
+
45
+
46
+ def _extract_details(
47
+ data: dict,
48
+ settings: provider_utils.Settings,
49
+ ctx: dict = dict(),
50
+ ) -> typing.Optional[models.RateDetails]:
51
+ currency = "USD"
52
+ machinable_piece = data.get("machinable_piece")
53
+ rate = lib.to_object(rating.RateType, data['rate'])
54
+ rateOption = lib.to_object(rating.RateOptionType, data['rateOption'])
55
+ product_name = rate.productName or rate.description or rate.mailClass
56
+ service_code = provider_units.ShippingService.to_product_code(product_name)
57
+ service_name = provider_units.ShippingService.to_product_name(service_code)
58
+
59
+ if machinable_piece is True and "machinable" not in service_code:
60
+ return None
61
+ if machinable_piece is False and "machinable" in service_code:
62
+ return None
63
+
64
+ transit_days = lib.to_int(next(iter(rateOption.commitment.name.split(" "))))
65
+ estimated_delivery = rateOption.commitment.scheduleDeliveryDate
66
+ charges = [
67
+ ("Base Price", lib.to_money(rateOption.totalBasePrice)),
68
+ *[(extra.name, lib.to_money(extra.price)) for extra in rateOption.extraServices],
69
+ ]
70
+
71
+ return models.RateDetails(
72
+ carrier_id=settings.carrier_id,
73
+ carrier_name=settings.carrier_name,
74
+ service=service_code,
75
+ total_charge=lib.to_money(rateOption.totalPrice),
76
+ currency=currency,
77
+ extra_charges=[
78
+ models.ChargeDetails(
79
+ currency=currency,
80
+ amount=amount,
81
+ name=name,
82
+ )
83
+ for name, amount in charges
84
+ ],
85
+ estimated_delivery=estimated_delivery,
86
+ transit_days=transit_days,
87
+ meta=dict(
88
+ service_name=service_name,
89
+ usps_mail_class=rate.mailClass,
90
+ usps_guaranteed_delivery=lib.failsafe(lambda: rateOption.commitment.guaranteedDelivery),
91
+ usps_processing_category=lib.failsafe(lambda: rate.processingCategory),
92
+ usps_dimensional_weight=lib.failsafe(lambda: rate.dimensionalWeight),
93
+ usps_rate_indicator=lib.failsafe(lambda: rate.rateIndicator),
94
+ usps_price_type=lib.failsafe(lambda: rate.priceType),
95
+ usps_rate_sku=lib.failsafe(lambda: rate.SKU),
96
+ usps_zone=lib.failsafe(lambda: rate.zone),
97
+ rate_zone=lib.failsafe(lambda: rate.zone),
98
+ usps_extra_services=lib.failsafe(
99
+ lambda: [lib.to_int(_.extraService) for _ in rateOption.extraServices]
100
+ ),
101
+ ),
102
+ )
103
+
104
+
105
+ def rate_request(
106
+ payload: models.RateRequest,
107
+ settings: provider_utils.Settings,
108
+ ) -> lib.Serializable:
109
+ shipper = lib.to_address(payload.shipper)
110
+ recipient = lib.to_address(payload.recipient)
111
+
112
+ if (
113
+ shipper.country_code is not None
114
+ and shipper.country_code != units.Country.US.name
115
+ ):
116
+ raise errors.OriginNotServicedError(shipper.country_code)
117
+
118
+ if (
119
+ recipient.country_code is not None
120
+ and recipient.country_code != units.Country.US.name
121
+ ):
122
+ raise errors.DestinationNotServicedError(recipient.country_code)
123
+
124
+ services = lib.to_services(
125
+ [provider_units.ShippingService.to_mail_class(_).name_or_key for _ in payload.services],
126
+ provider_units.ShippingService
127
+ )
128
+ options = lib.to_shipping_options(
129
+ payload.options,
130
+ initializer=provider_units.shipping_options_initializer,
131
+ )
132
+ packages = lib.to_packages(
133
+ payload.parcels,
134
+ options=options,
135
+ package_option_type=provider_units.ShippingOption,
136
+ shipping_options_initializer=provider_units.shipping_options_initializer,
137
+ )
138
+ price_type = lib.identity(
139
+ options.usps_price_type.state
140
+ or settings.connection_config.price_type.state
141
+ or "RETAIL"
142
+ )
143
+
144
+ package_mail_class = lambda package: lib.identity(
145
+ provider_units.ShippingService.to_mail_class(package.options.usps_mail_class.state).value
146
+ if package.options.usps_mail_class.state
147
+ else getattr(services.first, "value", "ALL")
148
+ )
149
+ package_options = lambda package: lib.identity(
150
+ package.options
151
+ if package_mail_class(package) not in provider_units.INCOMPATIBLE_SERVICES
152
+ else {}
153
+ )
154
+
155
+ # map data to convert karrio model to usps specific type
156
+ request = [
157
+ usps.RateRequestType(
158
+ pricingOptions=[
159
+ usps.PricingOptionType(
160
+ priceType=price_type,
161
+ paymentAccount=usps.PaymentAccountType(
162
+ accountType=settings.account_type,
163
+ accountNumber=settings.account_number,
164
+ ),
165
+ )
166
+ ],
167
+ originZIPCode=shipper.postal_code,
168
+ destinationZIPCode=recipient.postal_code,
169
+ destinationEntryFacilityType=lib.identity(
170
+ options.usps_destination_entry_facility_type.state
171
+ or "NONE"
172
+ ),
173
+ packageDescription=usps.PackageDescriptionType(
174
+ weight=package.weight.LB,
175
+ length=package.length.IN,
176
+ height=package.height.IN,
177
+ width=package.width.IN,
178
+ girth=lib.identity(
179
+ package.girth.value if package.packaging_type == "tube" else None
180
+ ),
181
+ mailClass=package_mail_class(package),
182
+ extraServices=[
183
+ lib.to_int(_.code)
184
+ for __, _ in package_options(package).items()
185
+ if __ not in provider_units.CUSTOM_OPTIONS
186
+ ],
187
+ packageValue=package.options.package_value.state,
188
+ mailingDate=lib.fdate(
189
+ package.options.shipment_date.state or time.strftime("%Y-%m-%d")
190
+ ),
191
+ ),
192
+ shippingFilter=package.options.usps_shipping_filter.state,
193
+ )
194
+ for package in packages
195
+ ]
196
+
197
+ return lib.Serializable(
198
+ request,
199
+ lib.to_dict,
200
+ dict(
201
+ price_type=price_type,
202
+ machinable_piece=options.usps_machinable_piece.state,
203
+ ),
204
+ )
@@ -0,0 +1,9 @@
1
+
2
+ from karrio.providers.usps.shipment.create import (
3
+ parse_shipment_response,
4
+ shipment_request,
5
+ )
6
+ from karrio.providers.usps.shipment.cancel import (
7
+ parse_shipment_cancel_response,
8
+ shipment_cancel_request,
9
+ )
@@ -0,0 +1,53 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+ import karrio.core.models as models
4
+ import karrio.providers.usps.error as error
5
+ import karrio.providers.usps.utils as provider_utils
6
+ import karrio.providers.usps.units as provider_units
7
+
8
+
9
+ def parse_shipment_cancel_response(
10
+ _response: lib.Deserializable[typing.List[typing.Tuple[str, dict]]],
11
+ settings: provider_utils.Settings,
12
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
13
+ responses = _response.deserialize()
14
+ messages: typing.List[models.Message] = sum(
15
+ [
16
+ error.parse_error_response(response, settings, tracking_number=_)
17
+ for _, response in responses
18
+ ],
19
+ start=[],
20
+ )
21
+ success = all([_.get("status") == "CANCELED" for __, _ in responses])
22
+
23
+ confirmation = (
24
+ models.ConfirmationDetails(
25
+ carrier_id=settings.carrier_id,
26
+ carrier_name=settings.carrier_name,
27
+ operation="Cancel Shipment",
28
+ success=success,
29
+ )
30
+ if success
31
+ else None
32
+ )
33
+
34
+ return confirmation, messages
35
+
36
+
37
+ def shipment_cancel_request(
38
+ payload: models.ShipmentCancelRequest,
39
+ settings: provider_utils.Settings,
40
+ ) -> lib.Serializable:
41
+
42
+ # map data to convert karrio model to usps specific type
43
+ request = [
44
+ dict(trackingNumber=_)
45
+ for _ in set(
46
+ [
47
+ payload.shipment_identifier,
48
+ *((payload.options or {}).get("shipment_identifiers") or []),
49
+ ]
50
+ )
51
+ ]
52
+
53
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,279 @@
1
+ """Karrio USPS create label implementation."""
2
+
3
+ import karrio.schemas.usps.label_request as usps
4
+ import karrio.schemas.usps.label_response as shipping
5
+
6
+ import time
7
+ import typing
8
+ import karrio.lib as lib
9
+ import karrio.core.units as units
10
+ import karrio.core.models as models
11
+ import karrio.core.errors as errors
12
+ import karrio.providers.usps.error as error
13
+ import karrio.providers.usps.utils as provider_utils
14
+ import karrio.providers.usps.units as provider_units
15
+
16
+
17
+ def parse_shipment_response(
18
+ _response: lib.Deserializable[typing.List[dict]],
19
+ settings: provider_utils.Settings,
20
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
21
+ responses = _response.deserialize()
22
+ shipment = lib.to_multi_piece_shipment(
23
+ [
24
+ (
25
+ f"{_}",
26
+ _extract_details(response, settings, _response.ctx),
27
+ )
28
+ for _, response in enumerate(responses, start=1)
29
+ if response.get("error") is None
30
+ and response.get("labelMetadata") is not None
31
+ ]
32
+ )
33
+ messages: typing.List[models.Message] = sum(
34
+ [error.parse_error_response(response, settings) for response in responses],
35
+ start=[],
36
+ )
37
+
38
+ return shipment, messages
39
+
40
+
41
+ def _extract_details(
42
+ data: dict,
43
+ settings: provider_utils.Settings,
44
+ ctx: dict = None,
45
+ ) -> models.ShipmentDetails:
46
+ details = lib.to_object(shipping.LabelResponseType, data)
47
+ label = details.labelImage
48
+ invoice = details.receiptImage
49
+ label_type = ctx.get("label_type", "PDF")
50
+
51
+ return models.ShipmentDetails(
52
+ carrier_id=settings.carrier_id,
53
+ carrier_name=settings.carrier_name,
54
+ tracking_number=details.labelMetadata.trackingNumber,
55
+ shipment_identifier=details.labelMetadata.trackingNumber,
56
+ label_type=label_type,
57
+ docs=models.Documents(label=label, invoice=invoice),
58
+ meta=dict(
59
+ SKU=details.labelMetadata.SKU,
60
+ postage=details.labelMetadata.postage,
61
+ routingInformation=details.labelMetadata.routingInformation,
62
+ labelBrokerID=details.labelMetadata.labelBrokerID,
63
+ ),
64
+ )
65
+
66
+
67
+ def shipment_request(
68
+ payload: models.ShipmentRequest,
69
+ settings: provider_utils.Settings,
70
+ ) -> lib.Serializable:
71
+ shipper = lib.to_address(payload.shipper)
72
+ recipient = lib.to_address(payload.recipient)
73
+
74
+ if (
75
+ shipper.country_code is not None
76
+ and shipper.country_code != units.Country.US.name
77
+ ):
78
+ raise errors.OriginNotServicedError(shipper.country_code)
79
+
80
+ if (
81
+ recipient.country_code is not None
82
+ and recipient.country_code != units.Country.US.name
83
+ ):
84
+ raise errors.DestinationNotServicedError(recipient.country_code)
85
+
86
+ return_address = lib.to_address(payload.return_address)
87
+ mail_class = lib.identity(
88
+ provider_units.ShippingService.to_mail_class(payload.service).value
89
+ or payload.service
90
+ )
91
+ options = lib.to_shipping_options(
92
+ payload.options,
93
+ initializer=provider_units.shipping_options_initializer,
94
+ )
95
+ packages = lib.to_packages(
96
+ payload.parcels,
97
+ options=options,
98
+ package_option_type=provider_units.ShippingOption,
99
+ shipping_options_initializer=provider_units.shipping_options_initializer,
100
+ )
101
+ pickup_location = lib.to_address(options.hold_for_pickup_address.state)
102
+ label_type = provider_units.LabelType.map(payload.label_type).value or "PDF"
103
+
104
+ package_options = lambda package: lib.identity(
105
+ package.options
106
+ if mail_class not in provider_units.INCOMPATIBLE_SERVICES
107
+ else {}
108
+ )
109
+
110
+ # map data to convert karrio model to usps specific type
111
+ request = [
112
+ usps.LabelRequestType(
113
+ imageInfo=usps.ImageInfoType(
114
+ imageType=label_type,
115
+ labelType="4X6LABEL",
116
+ # shipInfo=None,
117
+ receiptOption="NONE",
118
+ suppressPostage=None,
119
+ suppressMailDate=None,
120
+ returnLabel=None,
121
+ ),
122
+ toAddress=usps.AddressType(
123
+ streetAddress=recipient.address_line1,
124
+ secondaryAddress=recipient.address_line2,
125
+ city=recipient.city,
126
+ state=recipient.state_code,
127
+ ZIPCode=lib.to_zip5(recipient.postal_code) or "",
128
+ ZIPPlus4=lib.to_zip4(recipient.postal_code) or "",
129
+ urbanization=None,
130
+ firstName=recipient.first_name,
131
+ lastName=recipient.last_name,
132
+ firm=recipient.company_name,
133
+ phone=provider_utils.parse_phone_number(recipient.phone_number),
134
+ email=recipient.email,
135
+ ignoreBadAddress=True,
136
+ platformUserId=None,
137
+ parcelLockerDelivery=None,
138
+ holdForPickup=package.options.usps_hold_for_pickup.state,
139
+ facilityId=package.options.usps_facility_id.state,
140
+ ),
141
+ fromAddress=usps.AddressType(
142
+ streetAddress=shipper.address_line1,
143
+ secondaryAddress=shipper.address_line2,
144
+ city=shipper.city,
145
+ state=shipper.state_code,
146
+ ZIPCode=lib.to_zip5(shipper.postal_code) or "",
147
+ ZIPPlus4=lib.to_zip4(shipper.postal_code) or "",
148
+ urbanization=None,
149
+ firstName=shipper.first_name,
150
+ lastName=shipper.last_name,
151
+ firm=shipper.company_name,
152
+ phone=provider_utils.parse_phone_number(shipper.phone_number),
153
+ email=shipper.email,
154
+ ignoreBadAddress=True,
155
+ platformUserId=None,
156
+ parcelLockerDelivery=None,
157
+ holdForPickup=None,
158
+ facilityId=None,
159
+ ),
160
+ senderAddress=usps.AddressType(
161
+ streetAddress=shipper.address_line1,
162
+ secondaryAddress=shipper.address_line2r,
163
+ city=shipper.city,
164
+ state=shipper.state_code,
165
+ ZIPCode=lib.to_zip5(shipper.postal_code) or "",
166
+ ZIPPlus4=lib.to_zip4(shipper.postal_code) or "",
167
+ urbanization=None,
168
+ firstName=shipper.first_name,
169
+ lastName=shipper.last_name,
170
+ firm=shipper.company_name,
171
+ phone=provider_utils.parse_phone_number(shipper.phone_number),
172
+ email=shipper.email,
173
+ ignoreBadAddress=True,
174
+ platformUserId=None,
175
+ parcelLockerDelivery=None,
176
+ holdForPickup=None,
177
+ facilityId=None,
178
+ ),
179
+ returnAddress=lib.identity(
180
+ usps.AddressType(
181
+ streetAddress=return_address.address_line1,
182
+ secondaryAddress=return_address.address_line2r,
183
+ city=return_address.city,
184
+ state=return_address.state_code,
185
+ ZIPCode=lib.to_zip5(return_address.postal_code) or "",
186
+ ZIPPlus4=lib.to_zip4(return_address.postal_code) or "",
187
+ urbanization=None,
188
+ firstName=return_address.first_name,
189
+ lastName=return_address.last_name,
190
+ firm=return_address.company_name,
191
+ phone=provider_utils.parse_phone_number(return_address.phone_number),
192
+ email=return_address.email,
193
+ ignoreBadAddress=True,
194
+ platformUserId=None,
195
+ parcelLockerDelivery=None,
196
+ holdForPickup=None,
197
+ facilityId=None,
198
+ )
199
+ if payload.return_address is not None
200
+ else None
201
+ ),
202
+ packageDescription=usps.PackageDescriptionType(
203
+ weightUOM="lb",
204
+ weight=package.weight.LB,
205
+ dimensionsUOM="in",
206
+ length=package.length.IN,
207
+ height=package.height.IN,
208
+ width=package.width.IN,
209
+ girth=package.girth.value,
210
+ mailClass=mail_class,
211
+ rateIndicator=package.options.usps_rate_indicator.state or "DR",
212
+ processingCategory=lib.identity(
213
+ package.options.usps_processing_category.state or "NON_MACHINABLE"
214
+ ),
215
+ destinationEntryFacilityType=lib.identity(
216
+ package.options.usps_destination_facility_type.state or "NONE"
217
+ ),
218
+ destinationEntryFacilityAddress=lib.identity(
219
+ usps.DestinationEntryFacilityAddressType(
220
+ streetAddress=pickup_location.address_line1,
221
+ secondaryAddress=pickup_location.address_line2r,
222
+ city=pickup_location.city,
223
+ state=pickup_location.state_code,
224
+ ZIPCode=lib.to_zip5(pickup_location.postal_code) or "",
225
+ ZIPPlus4=lib.to_zip4(pickup_location.postal_code) or "",
226
+ urbanization=None,
227
+ )
228
+ if package.options.hold_for_pickup_address.state is not None
229
+ else None
230
+ ),
231
+ packageOptions=lib.identity(
232
+ usps.PackageOptionsType(
233
+ packageValue=lib.identity(
234
+ package.total_value
235
+ or package.options.declared_value.state
236
+ or 1.0
237
+ ),
238
+ nonDeliveryOption=None,
239
+ redirectAddress=None,
240
+ contentType=None,
241
+ generateGXEvent=None,
242
+ containers=[],
243
+ ancillaryServiceEndorsements=None,
244
+ originalPackage=None,
245
+ )
246
+ if (package.total_value or package.options.declared_value.state)
247
+ else None
248
+ ),
249
+ customerReference=[
250
+ usps.CustomerReferenceType(
251
+ referenceNumber=reference,
252
+ printReferenceNumber=True,
253
+ )
254
+ for reference in [payload.reference]
255
+ if reference is not None
256
+ ],
257
+ extraServices=lib.identity(
258
+ package.options.usps_extra_services.state
259
+ or [
260
+ lib.to_int(_.code)
261
+ for __, _ in package_options(package).items()
262
+ if _.name not in provider_units.CUSTOM_OPTIONS
263
+ ]
264
+ ),
265
+ mailingDate=lib.fdate(
266
+ package.options.shipment_date.state or time.strftime("%Y-%m-%d")
267
+ ),
268
+ carrierRelease=package.options.usps_carrier_release.state,
269
+ physicalSignatureRequired=package.options.usps_physical_signature_required.state,
270
+ inductionZIPCode=lib.identity(
271
+ return_address.postal_code or shipper.postal_code
272
+ ),
273
+ ),
274
+ customsForm=None,
275
+ )
276
+ for package in packages
277
+ ]
278
+
279
+ return lib.Serializable(request, lib.to_dict, dict(label_type=label_type))