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,3 @@
1
+ from karrio.mappers.usps.mapper import Mapper
2
+ from karrio.mappers.usps.proxy import Proxy
3
+ from karrio.mappers.usps.settings import Settings
@@ -0,0 +1,94 @@
1
+ """Karrio USPS client mapper."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.api.mapper as mapper
6
+ import karrio.core.models as models
7
+ import karrio.providers.usps as provider
8
+ import karrio.mappers.usps.settings as provider_settings
9
+
10
+
11
+ class Mapper(mapper.Mapper):
12
+ settings: provider_settings.Settings
13
+
14
+ def create_rate_request(
15
+ self, payload: models.RateRequest
16
+ ) -> lib.Serializable:
17
+ return provider.rate_request(payload, self.settings)
18
+
19
+ def create_tracking_request(
20
+ self, payload: models.TrackingRequest
21
+ ) -> lib.Serializable:
22
+ return provider.tracking_request(payload, self.settings)
23
+
24
+ def create_shipment_request(
25
+ self, payload: models.ShipmentRequest
26
+ ) -> lib.Serializable:
27
+ return provider.shipment_request(payload, self.settings)
28
+
29
+ def create_pickup_request(
30
+ self, payload: models.PickupRequest
31
+ ) -> lib.Serializable:
32
+ return provider.pickup_request(payload, self.settings)
33
+
34
+ def create_pickup_update_request(
35
+ self, payload: models.PickupUpdateRequest
36
+ ) -> lib.Serializable:
37
+ return provider.pickup_update_request(payload, self.settings)
38
+
39
+ def create_cancel_pickup_request(
40
+ self, payload: models.PickupCancelRequest
41
+ ) -> lib.Serializable:
42
+ return provider.pickup_cancel_request(payload, self.settings)
43
+
44
+ def create_cancel_shipment_request(
45
+ self, payload: models.ShipmentCancelRequest
46
+ ) -> lib.Serializable[str]:
47
+ return provider.shipment_cancel_request(payload, self.settings)
48
+
49
+ def create_manifest_request(
50
+ self, payload: models.ManifestRequest
51
+ ) -> lib.Serializable:
52
+ return provider.manifest_request(payload, self.settings)
53
+
54
+
55
+ def parse_cancel_pickup_response(
56
+ self, response: lib.Deserializable[str]
57
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
58
+ return provider.parse_pickup_cancel_response(response, self.settings)
59
+
60
+ def parse_cancel_shipment_response(
61
+ self, response: lib.Deserializable[str]
62
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
63
+ return provider.parse_shipment_cancel_response(response, self.settings)
64
+
65
+ def parse_pickup_response(
66
+ self, response: lib.Deserializable[str]
67
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
68
+ return provider.parse_pickup_response(response, self.settings)
69
+
70
+ def parse_pickup_update_response(
71
+ self, response: lib.Deserializable[str]
72
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
73
+ return provider.parse_pickup_update_response(response, self.settings)
74
+
75
+ def parse_rate_response(
76
+ self, response: lib.Deserializable[str]
77
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
78
+ return provider.parse_rate_response(response, self.settings)
79
+
80
+ def parse_shipment_response(
81
+ self, response: lib.Deserializable[str]
82
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
83
+ return provider.parse_shipment_response(response, self.settings)
84
+
85
+ def parse_tracking_response(
86
+ self, response: lib.Deserializable[str]
87
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
88
+ return provider.parse_tracking_response(response, self.settings)
89
+
90
+ def parse_manifest_response(
91
+ self, response: lib.Deserializable[str]
92
+ ) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
93
+ return provider.parse_manifest_response(response, self.settings)
94
+
@@ -0,0 +1,155 @@
1
+ """Karrio USPS client proxy."""
2
+
3
+ import karrio.lib as lib
4
+ import karrio.api.proxy as proxy
5
+ import karrio.providers.usps.utils as provider_utils
6
+ import karrio.mappers.usps.settings as provider_settings
7
+
8
+
9
+ class Proxy(proxy.Proxy):
10
+ settings: provider_settings.Settings
11
+
12
+ def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
13
+ response = lib.run_asynchronously(
14
+ lambda _: lib.request(
15
+ url=f"{self.settings.server_url}/shipments/v3/options/search",
16
+ data=lib.to_json(_),
17
+ trace=self.trace_as("json"),
18
+ method="POST",
19
+ headers={
20
+ "Content-Type": "application/json",
21
+ "Authorization": f"Bearer {self.settings.access_token}",
22
+ },
23
+ ),
24
+ request.serialize(),
25
+ )
26
+
27
+ return lib.Deserializable(
28
+ response,
29
+ lambda _: [lib.to_dict(_) for _ in _],
30
+ request.ctx,
31
+ )
32
+
33
+ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
34
+ response = lib.run_asynchronously(
35
+ lambda _: lib.request(
36
+ url=f"{self.settings.server_url}/labels/v3/label",
37
+ data=lib.to_json(_),
38
+ trace=self.trace_as("json"),
39
+ method="POST",
40
+ headers={
41
+ "Content-Type": "application/json",
42
+ "Authorization": f"Bearer {self.settings.access_token}",
43
+ "X-Payment-Authorization-Token": f"{self.settings.payment_token}",
44
+ },
45
+ on_error=provider_utils.parse_error_response,
46
+ ),
47
+ request.serialize(),
48
+ )
49
+
50
+ return lib.Deserializable(
51
+ response,
52
+ lambda _: [provider_utils.parse_response(_) for _ in _],
53
+ request.ctx,
54
+ )
55
+
56
+ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
57
+ response = lib.run_asynchronously(
58
+ lambda _: (
59
+ _["trackingNumber"],
60
+ lib.request(
61
+ url=f"{self.settings.server_url}/labels/v3/label/{_['trackingNumber']}",
62
+ trace=self.trace_as("json"),
63
+ method="DELETE",
64
+ headers={
65
+ "Content-Type": "application/json",
66
+ "Authorization": f"Bearer {self.settings.access_token}",
67
+ "X-Payment-Authorization-Token": f"{self.settings.payment_token}",
68
+ },
69
+ ),
70
+ ),
71
+ request.serialize(),
72
+ )
73
+
74
+ return lib.Deserializable(
75
+ response,
76
+ lambda __: [(_[0], lib.to_dict(_[1])) for _ in __],
77
+ )
78
+
79
+ def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
80
+ response = lib.run_asynchronously(
81
+ lambda trackingNumber: (
82
+ trackingNumber,
83
+ lib.request(
84
+ url=f"{self.settings.server_url}/tracking/v3/tracking/{trackingNumber}?expand=DETAIL",
85
+ trace=self.trace_as("json"),
86
+ method="GET",
87
+ headers={
88
+ "Content-Type": "application/json",
89
+ "Authorization": f"Bearer {self.settings.access_token}",
90
+ },
91
+ ),
92
+ ),
93
+ request.serialize(),
94
+ )
95
+
96
+ return lib.Deserializable(
97
+ response,
98
+ lambda __: [(_[0], lib.to_dict(_[1])) for _ in __],
99
+ )
100
+
101
+ def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
102
+ response = lib.request(
103
+ url=f"{self.settings.server_url}/pickup/v3/carrier-pickup",
104
+ data=lib.to_json(request.serialize()),
105
+ trace=self.trace_as("json"),
106
+ method="POST",
107
+ headers={
108
+ "Content-Type": "application/json",
109
+ "Authorization": f"Bearer {self.settings.access_token}",
110
+ },
111
+ )
112
+
113
+ return lib.Deserializable(response, lib.to_dict)
114
+
115
+ def modify_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
116
+ response = lib.request(
117
+ url=f"{self.settings.server_url}/pickup/v3/carrier-pickup/{request.ctx['confirmationNumber']}",
118
+ data=lib.to_json(request.serialize()),
119
+ trace=self.trace_as("json"),
120
+ method="PUT",
121
+ headers={
122
+ "Content-Type": "application/json",
123
+ "Authorization": f"Bearer {self.settings.access_token}",
124
+ },
125
+ )
126
+
127
+ return lib.Deserializable(response, lib.to_dict)
128
+
129
+ def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
130
+ response = lib.request(
131
+ url=f"{self.settings.server_url}/pickup/v3/carrier-pickup/{request.serialize()['confirmationNumber']}",
132
+ trace=self.trace_as("json"),
133
+ method="DELETE",
134
+ headers={
135
+ "Content-Type": "application/json",
136
+ "Authorization": f"Bearer {self.settings.access_token}",
137
+ },
138
+ on_ok=lambda _: '{"ok": true}',
139
+ )
140
+
141
+ return lib.Deserializable(response, lib.to_dict)
142
+
143
+ def create_manifest(self, request: lib.Serializable) -> lib.Deserializable[str]:
144
+ response = lib.request(
145
+ url=f"{self.settings.server_url}/scan-forms/v3/scan-form",
146
+ data=lib.to_json(request.serialize()),
147
+ trace=self.trace_as("json"),
148
+ method="POST",
149
+ headers={
150
+ "Content-Type": "application/json",
151
+ "Authorization": f"Bearer {self.settings.access_token}",
152
+ },
153
+ )
154
+
155
+ return lib.Deserializable(response, lib.to_dict)
@@ -0,0 +1,26 @@
1
+ """Karrio USPS client settings."""
2
+
3
+ import attr
4
+ import karrio.providers.usps.utils as provider_utils
5
+
6
+
7
+ @attr.s(auto_attribs=True)
8
+ class Settings(provider_utils.Settings):
9
+ """USPS connection settings."""
10
+
11
+ # Add carrier specific API connection properties here
12
+ client_id: str
13
+ client_secret: str
14
+ account_number: str = None
15
+ account_type: provider_utils.AccountType = "EPS" # type: ignore
16
+ manifest_MID: str = None
17
+ CRID: str = None
18
+ MID: str = None
19
+
20
+ # generic properties
21
+ id: str = None
22
+ test_mode: bool = False
23
+ carrier_id: str = "usps"
24
+ account_country_code: str = "US"
25
+ metadata: dict = {}
26
+ config: dict = {}
@@ -0,0 +1,24 @@
1
+ import karrio.core.metadata as metadata
2
+ import karrio.mappers.usps as mappers
3
+ import karrio.providers.usps.units as units
4
+ import karrio.providers.usps.utils as utils
5
+
6
+
7
+ METADATA = metadata.PluginMetadata(
8
+ status="production-ready",
9
+ id="usps",
10
+ label="USPS",
11
+ # Integrations
12
+ Mapper=mappers.Mapper,
13
+ Proxy=mappers.Proxy,
14
+ Settings=mappers.Settings,
15
+ # Data Units
16
+ is_hub=False,
17
+ options=units.ShippingOption,
18
+ services=units.ShippingService,
19
+ connection_configs=utils.ConnectionConfig,
20
+ # New fields
21
+ website="https://www.usps.com",
22
+ documentation="https://www.usps.com/business/web-tools-apis",
23
+ description="The United States Postal Service is an independent agency of the executive branch of the United States federal government responsible for providing postal service in the United States.",
24
+ )
@@ -0,0 +1,26 @@
1
+ """Karrio USPS provider imports."""
2
+
3
+ from karrio.providers.usps.utils import Settings
4
+ from karrio.providers.usps.rate import parse_rate_response, rate_request
5
+ from karrio.providers.usps.shipment import (
6
+ parse_shipment_cancel_response,
7
+ parse_shipment_response,
8
+ shipment_cancel_request,
9
+ shipment_request,
10
+ )
11
+ from karrio.providers.usps.pickup import (
12
+ parse_pickup_cancel_response,
13
+ parse_pickup_update_response,
14
+ parse_pickup_response,
15
+ pickup_update_request,
16
+ pickup_cancel_request,
17
+ pickup_request,
18
+ )
19
+ from karrio.providers.usps.tracking import (
20
+ parse_tracking_response,
21
+ tracking_request,
22
+ )
23
+ from karrio.providers.usps.manifest import (
24
+ parse_manifest_response,
25
+ manifest_request,
26
+ )
@@ -0,0 +1,56 @@
1
+ """Karrio USPS error parser."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.core.models as models
6
+ import karrio.providers.usps.utils as provider_utils
7
+
8
+
9
+ def parse_error_response(
10
+ response: typing.Union[dict, typing.List[dict]],
11
+ settings: provider_utils.Settings,
12
+ **kwargs,
13
+ ) -> typing.List[models.Message]:
14
+ responses = response if isinstance(response, list) else [response]
15
+ errors = [
16
+ *(
17
+ [response]
18
+ if isinstance(response, dict)
19
+ and isinstance(response.get("error"), dict)
20
+ and not any(response.get("error").get("errors") or [])
21
+ else []
22
+ ),
23
+ *(
24
+ [response]
25
+ if isinstance(response, dict) and isinstance(response.get("error"), str)
26
+ else []
27
+ ),
28
+ *(
29
+ response["error"]["errors"]
30
+ if isinstance(response, dict)
31
+ and isinstance(response.get("error"), dict)
32
+ and response.get("error").get("errors")
33
+ else []
34
+ ),
35
+ ]
36
+
37
+ return [
38
+ models.Message(
39
+ carrier_id=settings.carrier_id,
40
+ carrier_name=settings.carrier_name,
41
+ code=lib.identity(error.get("code") or error.get("error")),
42
+ message=lib.identity(
43
+ error.get("message")
44
+ or error.get("detail")
45
+ or error.get("error_description", "")
46
+ ),
47
+ details=lib.to_dict(
48
+ {
49
+ **kwargs,
50
+ "source": error.get("source"),
51
+ "error_uri": error.get("error_uri"),
52
+ }
53
+ ),
54
+ )
55
+ for error in errors
56
+ ]
@@ -0,0 +1,100 @@
1
+ """Karrio USPS manifest API implementation."""
2
+
3
+ import karrio.schemas.usps.scan_form_request as usps
4
+ import karrio.schemas.usps.scan_form_response as manifest
5
+
6
+ import time
7
+ import typing
8
+ import karrio.lib as lib
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_manifest_response(
16
+ _response: lib.Deserializable[dict],
17
+ settings: provider_utils.Settings,
18
+ ) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
19
+ response = _response.deserialize()
20
+
21
+ messages = error.parse_error_response(response, settings)
22
+ details = lib.identity(
23
+ _extract_details(response, settings)
24
+ if response.get("SCANFormImage") is not None
25
+ else None
26
+ )
27
+
28
+ return details, messages
29
+
30
+
31
+ def _extract_details(
32
+ data: dict,
33
+ settings: provider_utils.Settings,
34
+ ) -> models.ManifestDetails:
35
+ details = lib.to_object(manifest.ScanFormResponseType, data)
36
+ image = data.get("SCANFormImage")
37
+
38
+ return models.ManifestDetails(
39
+ carrier_id=settings.carrier_id,
40
+ carrier_name=settings.carrier_id,
41
+ doc=models.ManifestDocument(manifest=image),
42
+ meta=dict(
43
+ manifestNumber=details.SCANFormMetadata.manifestNumber,
44
+ trackingNumbers=details.SCANFormMetadata.trackingNumbers,
45
+ ),
46
+ )
47
+
48
+
49
+ def manifest_request(
50
+ payload: models.ManifestRequest,
51
+ settings: provider_utils.Settings,
52
+ ) -> lib.Serializable:
53
+ address = lib.to_address(payload.address)
54
+ options = lib.units.Options(
55
+ payload.options,
56
+ option_type=lib.units.create_enum(
57
+ "ManifestOptions",
58
+ # fmt: off
59
+ {
60
+ "shipment_date": lib.OptionEnum("shipment_date"),
61
+ "usps_ignore_bad_address": lib.OptionEnum("ignoreBadAddress", bool),
62
+ "usps_overwrite_mailing_date": lib.OptionEnum("overwriteMailingDate", bool),
63
+ "usps_destination_entry_facility_type": lib.OptionEnum("destinationEntryFacilityType", str),
64
+ },
65
+ # fmt: on
66
+ ),
67
+ )
68
+
69
+ # map data to convert karrio model to usps specific type
70
+ request = usps.ScanFormRequestType(
71
+ form="5630",
72
+ imageType="PDF",
73
+ labelType="8.5x11LABEL",
74
+ mailingDate=lib.fdate(options.shipment_date.state or time.strftime("%Y-%m-%d")),
75
+ overwriteMailingDate=options.usps_overwrite_mailing_date.state or False,
76
+ entryFacilityZIPCode=address.postal_code,
77
+ destinationEntryFacilityType=lib.identity(
78
+ options.usps_destination_entry_facility_type.state or "NONE"
79
+ ),
80
+ shipment=usps.ShipmentType(
81
+ trackingNumbers=payload.shipment_identifiers,
82
+ ),
83
+ fromAddress=usps.FromAddressType(
84
+ ignoreBadAddress=options.usps_ignore_bad_address.state or False,
85
+ streetAddress=address.address_line1,
86
+ secondaryAddress=address.address_line2,
87
+ city=address.city,
88
+ state=address.state,
89
+ ZIPCode=lib.to_zip5(address.postal_code) or "",
90
+ ZIPPlus4=lib.to_zip4(address.postal_code) or "",
91
+ urbanization=None,
92
+ firstName=lib.identity(
93
+ lib.failsafe(lambda: (address.person_name or "").split(" ")[0]) or ""
94
+ ),
95
+ lastName=lib.failsafe(lambda: (address.person_name or "").split(" ")[1]),
96
+ firm=address.company_name,
97
+ ),
98
+ )
99
+
100
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,4 @@
1
+
2
+ from karrio.providers.usps.pickup.create import parse_pickup_response, pickup_request
3
+ from karrio.providers.usps.pickup.update import parse_pickup_update_response, pickup_update_request
4
+ from karrio.providers.usps.pickup.cancel import parse_pickup_cancel_response, pickup_cancel_request
@@ -0,0 +1,40 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+ import karrio.core.units as units
4
+ import karrio.core.models as models
5
+ import karrio.providers.usps.error as error
6
+ import karrio.providers.usps.utils as provider_utils
7
+ import karrio.providers.usps.units as provider_units
8
+
9
+
10
+ def parse_pickup_cancel_response(
11
+ _response: lib.Deserializable[dict],
12
+ settings: provider_utils.Settings,
13
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
14
+ response = _response.deserialize()
15
+ messages = error.parse_error_response(response, settings)
16
+ success = response.get("ok") == True
17
+
18
+ confirmation = (
19
+ models.ConfirmationDetails(
20
+ carrier_id=settings.carrier_id,
21
+ carrier_name=settings.carrier_name,
22
+ operation="Cancel Pickup",
23
+ success=success,
24
+ )
25
+ if success
26
+ else None
27
+ )
28
+
29
+ return confirmation, messages
30
+
31
+
32
+ def pickup_cancel_request(
33
+ payload: models.PickupCancelRequest,
34
+ settings: provider_utils.Settings,
35
+ ) -> lib.Serializable:
36
+
37
+ # map data to convert karrio model to usps specific type
38
+ request = dict(confirmationNumber=payload.confirmation_number)
39
+
40
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,102 @@
1
+ """Karrio USPS schedule pickup implementation."""
2
+
3
+ import karrio.schemas.usps.pickup_request as usps
4
+ import karrio.schemas.usps.pickup_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_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.PickupResponseType, 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_request(
46
+ payload: models.PickupRequest,
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.PickupRequestType(
65
+ pickupDate=lib.fdate(payload.pickup_date),
66
+ pickupAddress=usps.PickupAddressType(
67
+ firstName=address.person_name,
68
+ lastName=None,
69
+ firm=address.company_name,
70
+ address=usps.AddressType(
71
+ streetAddress=address.address_line1,
72
+ secondaryAddress=address.address_line2,
73
+ city=address.city,
74
+ state=address.state,
75
+ ZIPCode=lib.to_zip5(address.postal_code),
76
+ ZIPPlus4=lib.to_zip4(address.postal_code) or "",
77
+ urbanization=None,
78
+ ),
79
+ contact=[
80
+ usps.ContactType(email=address.email)
81
+ for _ in [address.email]
82
+ if _ is not None
83
+ ],
84
+ ),
85
+ packages=[
86
+ usps.PackageType(
87
+ packageType=options.usps_package_type.state or "OTHER",
88
+ packageCount=len(packages),
89
+ )
90
+ ],
91
+ estimatedWeight=packages.weight.LB,
92
+ pickupLocation=lib.identity(
93
+ usps.PickupLocationType(
94
+ packageLocation=payload.package_location,
95
+ specialInstructions=payload.instruction,
96
+ )
97
+ if any([payload.package_location, payload.instruction])
98
+ else None
99
+ ),
100
+ )
101
+
102
+ return lib.Serializable(request, lib.to_dict)