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.
- karrio/mappers/usps/__init__.py +3 -0
- karrio/mappers/usps/mapper.py +94 -0
- karrio/mappers/usps/proxy.py +155 -0
- karrio/mappers/usps/settings.py +26 -0
- karrio/plugins/usps/__init__.py +24 -0
- karrio/providers/usps/__init__.py +26 -0
- karrio/providers/usps/error.py +56 -0
- karrio/providers/usps/manifest.py +100 -0
- karrio/providers/usps/pickup/__init__.py +4 -0
- karrio/providers/usps/pickup/cancel.py +40 -0
- karrio/providers/usps/pickup/create.py +102 -0
- karrio/providers/usps/pickup/update.py +109 -0
- karrio/providers/usps/rate.py +204 -0
- karrio/providers/usps/shipment/__init__.py +9 -0
- karrio/providers/usps/shipment/cancel.py +53 -0
- karrio/providers/usps/shipment/create.py +279 -0
- karrio/providers/usps/tracking.py +112 -0
- karrio/providers/usps/units.py +303 -0
- karrio/providers/usps/utils.py +320 -0
- karrio/schemas/usps/__init__.py +0 -0
- karrio/schemas/usps/error_response.py +31 -0
- karrio/schemas/usps/label_request.py +142 -0
- karrio/schemas/usps/label_response.py +84 -0
- karrio/schemas/usps/pickup_request.py +49 -0
- karrio/schemas/usps/pickup_response.py +58 -0
- karrio/schemas/usps/pickup_update_request.py +55 -0
- karrio/schemas/usps/pickup_update_response.py +58 -0
- karrio/schemas/usps/rate_request.py +38 -0
- karrio/schemas/usps/rate_response.py +89 -0
- karrio/schemas/usps/scan_form_request.py +36 -0
- karrio/schemas/usps/scan_form_response.py +46 -0
- karrio/schemas/usps/tracking_response.py +113 -0
- karrio_usps-2025.5rc1.dist-info/METADATA +45 -0
- karrio_usps-2025.5rc1.dist-info/RECORD +37 -0
- karrio_usps-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_usps-2025.5rc1.dist-info/entry_points.txt +2 -0
- karrio_usps-2025.5rc1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,320 @@
|
|
1
|
+
"""USPS connection settings."""
|
2
|
+
|
3
|
+
import re
|
4
|
+
import typing
|
5
|
+
import datetime
|
6
|
+
import karrio.lib as lib
|
7
|
+
import karrio.core as core
|
8
|
+
import karrio.core.errors as errors
|
9
|
+
|
10
|
+
AccountType = lib.units.create_enum(
|
11
|
+
"AccountType",
|
12
|
+
["EPS", "PERMIT", "METER"],
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
class Settings(core.Settings):
|
17
|
+
"""USPS connection settings."""
|
18
|
+
|
19
|
+
# Add carrier specific api connection properties here
|
20
|
+
client_id: str
|
21
|
+
client_secret: str
|
22
|
+
account_number: str = None
|
23
|
+
account_type: AccountType = "EPS" # type: ignore
|
24
|
+
manifest_MID: str = None
|
25
|
+
CRID: str = None
|
26
|
+
MID: str = None
|
27
|
+
|
28
|
+
@property
|
29
|
+
def carrier_name(self):
|
30
|
+
return "usps"
|
31
|
+
|
32
|
+
@property
|
33
|
+
def server_url(self):
|
34
|
+
return "https://api-cat.usps.com" if self.test_mode else "https://apis.usps.com"
|
35
|
+
|
36
|
+
@property
|
37
|
+
def tracking_url(self):
|
38
|
+
return "https://tools.usps.com/go/TrackConfirmAction?tLabels={}"
|
39
|
+
|
40
|
+
@property
|
41
|
+
def connection_config(self) -> lib.units.Options:
|
42
|
+
return lib.to_connection_config(
|
43
|
+
self.config or {},
|
44
|
+
option_type=ConnectionConfig,
|
45
|
+
)
|
46
|
+
|
47
|
+
@property
|
48
|
+
def access_token(self):
|
49
|
+
"""Retrieve the access_token using the client_id|client_secret pair
|
50
|
+
or collect it from the cache if an unexpired access_token exist.
|
51
|
+
"""
|
52
|
+
cache_key = f"access|{self.carrier_name}|{self.client_id}|{self.client_secret}"
|
53
|
+
now = datetime.datetime.now() + datetime.timedelta(minutes=30)
|
54
|
+
|
55
|
+
auth = self.connection_cache.get(cache_key) or {}
|
56
|
+
token = auth.get("access_token")
|
57
|
+
expiry = lib.to_date(auth.get("expiry"), current_format="%Y-%m-%d %H:%M:%S")
|
58
|
+
|
59
|
+
if token is not None and expiry is not None and expiry > now:
|
60
|
+
return token
|
61
|
+
|
62
|
+
self.connection_cache.set(cache_key, lambda: oauth2_login(self))
|
63
|
+
new_auth = self.connection_cache.get(cache_key)
|
64
|
+
|
65
|
+
return new_auth["access_token"]
|
66
|
+
|
67
|
+
@property
|
68
|
+
def payment_token(self):
|
69
|
+
"""Retrieve the paymentAuthorizationToken using the client_id|client_secret pair
|
70
|
+
or collect it from the cache if an unexpired paymentAuthorizationToken exist.
|
71
|
+
"""
|
72
|
+
cache_key = f"payment|{self.carrier_name}|{self.client_id}|{self.client_secret}"
|
73
|
+
now = datetime.datetime.now() + datetime.timedelta(minutes=45)
|
74
|
+
|
75
|
+
auth = self.connection_cache.get(cache_key) or {}
|
76
|
+
token = auth.get("paymentAuthorizationToken")
|
77
|
+
expiry = lib.to_date(auth.get("expiry"), current_format="%Y-%m-%d %H:%M:%S")
|
78
|
+
|
79
|
+
if token is not None and expiry is not None and expiry > now:
|
80
|
+
return token
|
81
|
+
|
82
|
+
self.connection_cache.set(cache_key, lambda: payment_auth(self))
|
83
|
+
new_auth = self.connection_cache.get(cache_key)
|
84
|
+
|
85
|
+
return new_auth["paymentAuthorizationToken"]
|
86
|
+
|
87
|
+
|
88
|
+
class ConnectionConfig(lib.Enum):
|
89
|
+
permit_ZIP = lib.OptionEnum("permit_ZIP")
|
90
|
+
permit_number = lib.OptionEnum("permit_number")
|
91
|
+
shipping_options = lib.OptionEnum("shipping_options", list)
|
92
|
+
shipping_services = lib.OptionEnum("shipping_services", list)
|
93
|
+
|
94
|
+
|
95
|
+
def oauth2_login(settings: Settings):
|
96
|
+
import karrio.providers.usps.error as error
|
97
|
+
|
98
|
+
API_SCOPES = [
|
99
|
+
"addresses",
|
100
|
+
"international-prices",
|
101
|
+
"subscriptions",
|
102
|
+
"payments",
|
103
|
+
"pickup",
|
104
|
+
"tracking",
|
105
|
+
"labels",
|
106
|
+
"scan-forms",
|
107
|
+
"companies",
|
108
|
+
"service-delivery-standards",
|
109
|
+
"locations",
|
110
|
+
"international-labels",
|
111
|
+
"prices",
|
112
|
+
"shipments",
|
113
|
+
]
|
114
|
+
result = lib.request(
|
115
|
+
url=f"{settings.server_url}/oauth2/v3/token",
|
116
|
+
method="POST",
|
117
|
+
headers={"content-Type": "application/x-www-form-urlencoded"},
|
118
|
+
data=lib.to_query_string(
|
119
|
+
dict(
|
120
|
+
grant_type="client_credentials",
|
121
|
+
client_id=settings.client_id,
|
122
|
+
client_secret=settings.client_secret,
|
123
|
+
scope=" ".join(API_SCOPES),
|
124
|
+
)
|
125
|
+
),
|
126
|
+
)
|
127
|
+
|
128
|
+
response = lib.to_dict(result)
|
129
|
+
messages = error.parse_error_response(response, settings)
|
130
|
+
|
131
|
+
if any(messages):
|
132
|
+
raise errors.ParsedMessagesError(messages)
|
133
|
+
|
134
|
+
expiry = datetime.datetime.now() + datetime.timedelta(
|
135
|
+
seconds=float(response.get("expires_in", 0))
|
136
|
+
)
|
137
|
+
|
138
|
+
return {**response, "expiry": lib.fdatetime(expiry)}
|
139
|
+
|
140
|
+
|
141
|
+
def payment_auth(settings: Settings):
|
142
|
+
import karrio.providers.usps.error as error
|
143
|
+
|
144
|
+
result = lib.request(
|
145
|
+
url=f"{settings.server_url}/payments/v3/payment-authorization",
|
146
|
+
method="POST",
|
147
|
+
headers={
|
148
|
+
"content-Type": "application/json",
|
149
|
+
"Authorization": f"Bearer {settings.access_token}",
|
150
|
+
},
|
151
|
+
data=lib.to_json(
|
152
|
+
{
|
153
|
+
"roles": [
|
154
|
+
{
|
155
|
+
"roleName": "LABEL_OWNER",
|
156
|
+
"CRID": settings.CRID,
|
157
|
+
"MID": settings.MID,
|
158
|
+
"accountType": settings.account_type or "EPS",
|
159
|
+
"accountNumber": settings.account_number,
|
160
|
+
"manifestMID": settings.manifest_MID,
|
161
|
+
# "permitNumber": settings.connection_config.permit_number.state,
|
162
|
+
# "permitZIP": settings.connection_config.permit_ZIP.state,
|
163
|
+
},
|
164
|
+
{
|
165
|
+
"roleName": "PAYER",
|
166
|
+
"CRID": settings.CRID,
|
167
|
+
"MID": settings.MID,
|
168
|
+
"accountType": settings.account_type or "EPS",
|
169
|
+
"accountNumber": settings.account_number,
|
170
|
+
# "permitNumber": settings.connection_config.permit_number.state,
|
171
|
+
# "permitZIP": settings.connection_config.permit_zip.state,
|
172
|
+
},
|
173
|
+
]
|
174
|
+
}
|
175
|
+
),
|
176
|
+
)
|
177
|
+
|
178
|
+
response = lib.to_dict(result)
|
179
|
+
messages = error.parse_error_response(response, settings)
|
180
|
+
|
181
|
+
if any(messages):
|
182
|
+
raise errors.ParsedMessagesError(messages)
|
183
|
+
|
184
|
+
expiry = datetime.datetime.now() + datetime.timedelta(minutes=50)
|
185
|
+
|
186
|
+
return {**response, "expiry": lib.fdatetime(expiry)}
|
187
|
+
|
188
|
+
|
189
|
+
def normalize_multipart_response(response: str) -> str:
|
190
|
+
"""Normalize a multipart response string to have consistent formatting"""
|
191
|
+
# Find boundary
|
192
|
+
boundary_match = re.search(r"--[a-zA-Z0-9\+/=_-]+", response)
|
193
|
+
if not boundary_match:
|
194
|
+
return response
|
195
|
+
|
196
|
+
boundary = boundary_match.group(0)
|
197
|
+
|
198
|
+
# Split into parts using boundary
|
199
|
+
parts = response.split(boundary)
|
200
|
+
|
201
|
+
# Format each part
|
202
|
+
formatted_parts = []
|
203
|
+
for part in parts:
|
204
|
+
if (
|
205
|
+
not part.strip() or part.strip() == "--"
|
206
|
+
): # Skip empty parts and end boundary
|
207
|
+
continue
|
208
|
+
|
209
|
+
# Remove excess whitespace and normalize line endings
|
210
|
+
part = part.strip().replace("\r\n", "\n").replace("\n\n\n", "\n\n")
|
211
|
+
|
212
|
+
# Extract headers and content
|
213
|
+
if "Content-Type" in part:
|
214
|
+
# Split headers and content
|
215
|
+
headers = []
|
216
|
+
content = ""
|
217
|
+
|
218
|
+
# Process line by line
|
219
|
+
lines = part.split("\n")
|
220
|
+
in_headers = True
|
221
|
+
|
222
|
+
for line in lines:
|
223
|
+
if in_headers:
|
224
|
+
if line.startswith("Content-"):
|
225
|
+
headers.append(line)
|
226
|
+
elif not line.strip():
|
227
|
+
in_headers = False
|
228
|
+
continue
|
229
|
+
content += line + "\n"
|
230
|
+
|
231
|
+
# Reconstruct part with proper formatting
|
232
|
+
formatted_part = "\n".join(headers) + "\n\n" + content.strip()
|
233
|
+
formatted_parts.append(formatted_part)
|
234
|
+
|
235
|
+
# Reconstruct full response
|
236
|
+
formatted_response = f"\n{boundary}\n".join([""] + formatted_parts + ["--"])
|
237
|
+
|
238
|
+
return formatted_response
|
239
|
+
|
240
|
+
|
241
|
+
def parse_response(response) -> dict:
|
242
|
+
json_data = lib.failsafe(lambda: lib.to_dict(response))
|
243
|
+
|
244
|
+
if json_data:
|
245
|
+
return json_data
|
246
|
+
|
247
|
+
# Normalize response format first
|
248
|
+
normalized_response = normalize_multipart_response(response)
|
249
|
+
|
250
|
+
# Extract boundary dynamically
|
251
|
+
boundary_match = re.search(r"--[a-zA-Z0-9\-]+", normalized_response)
|
252
|
+
if not boundary_match:
|
253
|
+
return dict(
|
254
|
+
error=dict(
|
255
|
+
code="SHIPPING_SDK_ERROR",
|
256
|
+
message="Failed to parse multipart response",
|
257
|
+
)
|
258
|
+
)
|
259
|
+
|
260
|
+
boundary = boundary_match.group(0).strip()
|
261
|
+
|
262
|
+
# Handle multipart form-data
|
263
|
+
parts = normalized_response.split(boundary)
|
264
|
+
data = {}
|
265
|
+
|
266
|
+
for part in parts:
|
267
|
+
if not part.strip() or part.strip() == "--":
|
268
|
+
continue # Skip empty parts and the final boundary marker
|
269
|
+
|
270
|
+
part_data = {}
|
271
|
+
headers_content, content = (
|
272
|
+
part.split("\n\n", 1) if "\n\n" in part else (part, "")
|
273
|
+
)
|
274
|
+
headers: typing.List[str] = headers_content.strip().split("\n")
|
275
|
+
|
276
|
+
# Extract Content-Disposition and Content-Type
|
277
|
+
for header in headers:
|
278
|
+
if "Content-Type" in header:
|
279
|
+
part_data["content_type"] = header.split(":")[1].strip()
|
280
|
+
elif "Content-Disposition" in header:
|
281
|
+
disposition = header.split(":")[1].strip()
|
282
|
+
if "filename=" in disposition:
|
283
|
+
filename = re.search(r'filename="([^"]+)"', disposition)
|
284
|
+
if filename:
|
285
|
+
part_data["filename"] = filename.group(1)
|
286
|
+
if "name=" in disposition:
|
287
|
+
name = re.search(r' name="([^"]+)"', disposition)
|
288
|
+
if name:
|
289
|
+
part_data["name"] = name.group(1)
|
290
|
+
|
291
|
+
# Parse content based on type
|
292
|
+
if part_data.get("content_type") == "application/json":
|
293
|
+
data[part_data["name"]] = {**lib.to_dict(content.strip())}
|
294
|
+
elif part_data.get("filename"):
|
295
|
+
data[part_data["name"]] = content.strip() # type: ignore
|
296
|
+
|
297
|
+
return data
|
298
|
+
|
299
|
+
|
300
|
+
def parse_error_response(response) -> dict:
|
301
|
+
# Check if the response is JSON
|
302
|
+
content = lib.failsafe(lambda: response.read())
|
303
|
+
json_data = lib.failsafe(lambda: lib.to_dict(content))
|
304
|
+
|
305
|
+
if json_data:
|
306
|
+
return json_data
|
307
|
+
|
308
|
+
# the response is plain text
|
309
|
+
return dict(
|
310
|
+
error=dict(
|
311
|
+
code=response.code,
|
312
|
+
message=response.strip(),
|
313
|
+
)
|
314
|
+
)
|
315
|
+
|
316
|
+
def parse_phone_number(number: str) -> typing.Optional[str]:
|
317
|
+
if number is None:
|
318
|
+
return None
|
319
|
+
|
320
|
+
return number.replace(" ", "").replace("-", "").replace("+", "")[-10:]
|
File without changes
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class SourceType:
|
8
|
+
parameter: typing.Optional[str] = None
|
9
|
+
example: typing.Optional[str] = None
|
10
|
+
|
11
|
+
|
12
|
+
@attr.s(auto_attribs=True)
|
13
|
+
class ErrorElementType:
|
14
|
+
status: typing.Optional[str] = None
|
15
|
+
code: typing.Optional[str] = None
|
16
|
+
title: typing.Optional[str] = None
|
17
|
+
detail: typing.Optional[str] = None
|
18
|
+
source: typing.Optional[SourceType] = jstruct.JStruct[SourceType]
|
19
|
+
|
20
|
+
|
21
|
+
@attr.s(auto_attribs=True)
|
22
|
+
class ErrorResponseErrorType:
|
23
|
+
code: typing.Optional[str] = None
|
24
|
+
message: typing.Optional[str] = None
|
25
|
+
errors: typing.Optional[typing.List[ErrorElementType]] = jstruct.JList[ErrorElementType]
|
26
|
+
|
27
|
+
|
28
|
+
@attr.s(auto_attribs=True)
|
29
|
+
class ErrorResponseType:
|
30
|
+
apiVersion: typing.Optional[str] = None
|
31
|
+
error: typing.Optional[ErrorResponseErrorType] = jstruct.JStruct[ErrorResponseErrorType]
|
@@ -0,0 +1,142 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class ContentType:
|
8
|
+
itemDescription: typing.Optional[str] = None
|
9
|
+
itemQuantity: typing.Optional[int] = None
|
10
|
+
itemValue: typing.Optional[int] = None
|
11
|
+
itemTotalValue: typing.Optional[int] = None
|
12
|
+
weightUOM: typing.Optional[str] = None
|
13
|
+
itemWeight: typing.Optional[float] = None
|
14
|
+
itemTotalWeight: typing.Optional[float] = None
|
15
|
+
HSTariffNumber: typing.Optional[str] = None
|
16
|
+
countryofOrigin: typing.Optional[str] = None
|
17
|
+
itemCategory: typing.Optional[str] = None
|
18
|
+
itemSubcategory: typing.Optional[str] = None
|
19
|
+
|
20
|
+
|
21
|
+
@attr.s(auto_attribs=True)
|
22
|
+
class CustomsFormType:
|
23
|
+
contentComments: typing.Optional[str] = None
|
24
|
+
restrictionType: typing.Optional[str] = None
|
25
|
+
restrictionComments: typing.Optional[str] = None
|
26
|
+
AESITN: typing.Optional[str] = None
|
27
|
+
invoiceNumber: typing.Optional[str] = None
|
28
|
+
licenseNumber: typing.Optional[str] = None
|
29
|
+
certificateNumber: typing.Optional[str] = None
|
30
|
+
customsContentType: typing.Optional[str] = None
|
31
|
+
importersReference: typing.Optional[str] = None
|
32
|
+
importersContact: typing.Optional[str] = None
|
33
|
+
exportersReference: typing.Optional[str] = None
|
34
|
+
exportersContact: typing.Optional[str] = None
|
35
|
+
contents: typing.Optional[typing.List[ContentType]] = jstruct.JList[ContentType]
|
36
|
+
|
37
|
+
|
38
|
+
@attr.s(auto_attribs=True)
|
39
|
+
class AddressType:
|
40
|
+
streetAddress: typing.Optional[str] = None
|
41
|
+
secondaryAddress: typing.Optional[str] = None
|
42
|
+
city: typing.Optional[str] = None
|
43
|
+
state: typing.Optional[str] = None
|
44
|
+
ZIPCode: typing.Optional[str] = None
|
45
|
+
ZIPPlus4: typing.Optional[str] = None
|
46
|
+
urbanization: typing.Optional[str] = None
|
47
|
+
firstName: typing.Optional[str] = None
|
48
|
+
lastName: typing.Optional[str] = None
|
49
|
+
firm: typing.Optional[str] = None
|
50
|
+
phone: typing.Optional[str] = None
|
51
|
+
email: typing.Optional[str] = None
|
52
|
+
ignoreBadAddress: typing.Optional[bool] = None
|
53
|
+
platformUserId: typing.Optional[str] = None
|
54
|
+
parcelLockerDelivery: typing.Optional[bool] = None
|
55
|
+
holdForPickup: typing.Optional[bool] = None
|
56
|
+
facilityId: typing.Optional[str] = None
|
57
|
+
|
58
|
+
|
59
|
+
@attr.s(auto_attribs=True)
|
60
|
+
class ImageInfoType:
|
61
|
+
imageType: typing.Optional[str] = None
|
62
|
+
labelType: typing.Optional[str] = None
|
63
|
+
shipInfo: typing.Optional[bool] = None
|
64
|
+
receiptOption: typing.Optional[str] = None
|
65
|
+
suppressPostage: typing.Optional[bool] = None
|
66
|
+
suppressMailDate: typing.Optional[bool] = None
|
67
|
+
returnLabel: typing.Optional[bool] = None
|
68
|
+
|
69
|
+
|
70
|
+
@attr.s(auto_attribs=True)
|
71
|
+
class CustomerReferenceType:
|
72
|
+
referenceNumber: typing.Optional[str] = None
|
73
|
+
printReferenceNumber: typing.Optional[bool] = None
|
74
|
+
|
75
|
+
|
76
|
+
@attr.s(auto_attribs=True)
|
77
|
+
class DestinationEntryFacilityAddressType:
|
78
|
+
streetAddress: typing.Optional[str] = None
|
79
|
+
secondaryAddress: typing.Optional[str] = None
|
80
|
+
city: typing.Optional[str] = None
|
81
|
+
state: typing.Optional[str] = None
|
82
|
+
ZIPCode: typing.Optional[str] = None
|
83
|
+
ZIPPlus4: typing.Optional[str] = None
|
84
|
+
urbanization: typing.Optional[str] = None
|
85
|
+
|
86
|
+
|
87
|
+
@attr.s(auto_attribs=True)
|
88
|
+
class ContainerType:
|
89
|
+
containerID: typing.Optional[str] = None
|
90
|
+
sortType: typing.Optional[str] = None
|
91
|
+
|
92
|
+
|
93
|
+
@attr.s(auto_attribs=True)
|
94
|
+
class OriginalPackageType:
|
95
|
+
originalTrackingNumber: typing.Optional[str] = None
|
96
|
+
originalConstructCode: typing.Optional[str] = None
|
97
|
+
|
98
|
+
|
99
|
+
@attr.s(auto_attribs=True)
|
100
|
+
class PackageOptionsType:
|
101
|
+
packageValue: typing.Optional[int] = None
|
102
|
+
nonDeliveryOption: typing.Optional[str] = None
|
103
|
+
redirectAddress: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
104
|
+
contentType: typing.Optional[str] = None
|
105
|
+
generateGXEvent: typing.Optional[bool] = None
|
106
|
+
containers: typing.Optional[typing.List[ContainerType]] = jstruct.JList[ContainerType]
|
107
|
+
ancillaryServiceEndorsements: typing.Optional[str] = None
|
108
|
+
originalPackage: typing.Optional[OriginalPackageType] = jstruct.JStruct[OriginalPackageType]
|
109
|
+
|
110
|
+
|
111
|
+
@attr.s(auto_attribs=True)
|
112
|
+
class PackageDescriptionType:
|
113
|
+
weightUOM: typing.Optional[str] = None
|
114
|
+
weight: typing.Optional[int] = None
|
115
|
+
dimensionsUOM: typing.Optional[str] = None
|
116
|
+
length: typing.Optional[int] = None
|
117
|
+
height: typing.Optional[int] = None
|
118
|
+
width: typing.Optional[int] = None
|
119
|
+
girth: typing.Optional[int] = None
|
120
|
+
mailClass: typing.Optional[str] = None
|
121
|
+
rateIndicator: typing.Optional[str] = None
|
122
|
+
processingCategory: typing.Optional[str] = None
|
123
|
+
destinationEntryFacilityType: typing.Optional[str] = None
|
124
|
+
destinationEntryFacilityAddress: typing.Optional[DestinationEntryFacilityAddressType] = jstruct.JStruct[DestinationEntryFacilityAddressType]
|
125
|
+
packageOptions: typing.Optional[PackageOptionsType] = jstruct.JStruct[PackageOptionsType]
|
126
|
+
customerReference: typing.Optional[typing.List[CustomerReferenceType]] = jstruct.JList[CustomerReferenceType]
|
127
|
+
extraServices: typing.Optional[typing.List[int]] = None
|
128
|
+
mailingDate: typing.Optional[str] = None
|
129
|
+
carrierRelease: typing.Optional[bool] = None
|
130
|
+
physicalSignatureRequired: typing.Optional[bool] = None
|
131
|
+
inductionZIPCode: typing.Optional[str] = None
|
132
|
+
|
133
|
+
|
134
|
+
@attr.s(auto_attribs=True)
|
135
|
+
class LabelRequestType:
|
136
|
+
imageInfo: typing.Optional[ImageInfoType] = jstruct.JStruct[ImageInfoType]
|
137
|
+
toAddress: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
138
|
+
fromAddress: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
139
|
+
senderAddress: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
140
|
+
returnAddress: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
141
|
+
packageDescription: typing.Optional[PackageDescriptionType] = jstruct.JStruct[PackageDescriptionType]
|
142
|
+
customsForm: typing.Optional[CustomsFormType] = jstruct.JStruct[CustomsFormType]
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class CommitmentType:
|
8
|
+
name: typing.Optional[str] = None
|
9
|
+
scheduleDeliveryDate: typing.Optional[str] = None
|
10
|
+
|
11
|
+
|
12
|
+
@attr.s(auto_attribs=True)
|
13
|
+
class ExtraServiceType:
|
14
|
+
name: typing.Optional[str] = None
|
15
|
+
SKU: typing.Optional[str] = None
|
16
|
+
price: typing.Optional[int] = None
|
17
|
+
|
18
|
+
|
19
|
+
@attr.s(auto_attribs=True)
|
20
|
+
class InductionTypeObjectType:
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
@attr.s(auto_attribs=True)
|
25
|
+
class LabelAddressType:
|
26
|
+
streetAddress: typing.Optional[str] = None
|
27
|
+
streetAddressAbbreviation: typing.Optional[str] = None
|
28
|
+
secondaryAddress: typing.Optional[str] = None
|
29
|
+
cityAbbreviation: typing.Optional[str] = None
|
30
|
+
city: typing.Optional[str] = None
|
31
|
+
state: typing.Optional[str] = None
|
32
|
+
ZIPCode: typing.Optional[str] = None
|
33
|
+
ZIPPlus4: typing.Optional[str] = None
|
34
|
+
urbanization: typing.Optional[str] = None
|
35
|
+
firstName: typing.Optional[str] = None
|
36
|
+
lastName: typing.Optional[str] = None
|
37
|
+
firm: typing.Optional[str] = None
|
38
|
+
phone: typing.Optional[str] = None
|
39
|
+
email: typing.Optional[str] = None
|
40
|
+
ignoreBadAddress: typing.Optional[bool] = None
|
41
|
+
|
42
|
+
|
43
|
+
@attr.s(auto_attribs=True)
|
44
|
+
class LinkType:
|
45
|
+
rel: typing.Optional[typing.List[str]] = None
|
46
|
+
title: typing.Optional[str] = None
|
47
|
+
href: typing.Optional[str] = None
|
48
|
+
method: typing.Optional[str] = None
|
49
|
+
submissionMediaType: typing.Optional[str] = None
|
50
|
+
targetMediaType: typing.Optional[str] = None
|
51
|
+
|
52
|
+
|
53
|
+
@attr.s(auto_attribs=True)
|
54
|
+
class LabelMetadataType:
|
55
|
+
labelAddress: typing.Optional[LabelAddressType] = jstruct.JStruct[LabelAddressType]
|
56
|
+
routingInformation: typing.Optional[str] = None
|
57
|
+
trackingNumber: typing.Optional[str] = None
|
58
|
+
constructCode: typing.Optional[str] = None
|
59
|
+
SKU: typing.Optional[str] = None
|
60
|
+
postage: typing.Optional[int] = None
|
61
|
+
extraServices: typing.Optional[typing.List[ExtraServiceType]] = jstruct.JList[ExtraServiceType]
|
62
|
+
zone: typing.Optional[str] = None
|
63
|
+
commitment: typing.Optional[CommitmentType] = jstruct.JStruct[CommitmentType]
|
64
|
+
weightUOM: typing.Optional[str] = None
|
65
|
+
weight: typing.Optional[int] = None
|
66
|
+
dimensionalWeight: typing.Optional[int] = None
|
67
|
+
fees: typing.Optional[typing.List[ExtraServiceType]] = jstruct.JList[ExtraServiceType]
|
68
|
+
permitHolderName: typing.Optional[str] = None
|
69
|
+
inductionType: typing.Optional[InductionTypeObjectType] = jstruct.JStruct[InductionTypeObjectType]
|
70
|
+
labelBrokerID: typing.Optional[str] = None
|
71
|
+
links: typing.Optional[typing.List[LinkType]] = jstruct.JList[LinkType]
|
72
|
+
bannerText: typing.Optional[str] = None
|
73
|
+
retailDistributionCode: typing.Optional[str] = None
|
74
|
+
serviceTypeCode: typing.Optional[int] = None
|
75
|
+
|
76
|
+
|
77
|
+
@attr.s(auto_attribs=True)
|
78
|
+
class LabelResponseType:
|
79
|
+
labelMetadata: typing.Optional[LabelMetadataType] = jstruct.JStruct[LabelMetadataType]
|
80
|
+
returnLabelMetadata: typing.Optional[LabelMetadataType] = jstruct.JStruct[LabelMetadataType]
|
81
|
+
labelImage: typing.Optional[str] = None
|
82
|
+
receiptImage: typing.Optional[str] = None
|
83
|
+
returnLabelImage: typing.Optional[str] = None
|
84
|
+
returnReceiptImage: typing.Optional[str] = None
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class PackageType:
|
8
|
+
packageType: typing.Optional[str] = None
|
9
|
+
packageCount: typing.Optional[int] = None
|
10
|
+
|
11
|
+
|
12
|
+
@attr.s(auto_attribs=True)
|
13
|
+
class AddressType:
|
14
|
+
streetAddress: typing.Optional[str] = None
|
15
|
+
secondaryAddress: typing.Optional[str] = None
|
16
|
+
city: typing.Optional[str] = None
|
17
|
+
state: typing.Optional[str] = None
|
18
|
+
ZIPCode: typing.Optional[str] = None
|
19
|
+
ZIPPlus4: typing.Optional[str] = None
|
20
|
+
urbanization: typing.Optional[str] = None
|
21
|
+
|
22
|
+
|
23
|
+
@attr.s(auto_attribs=True)
|
24
|
+
class ContactType:
|
25
|
+
email: typing.Optional[str] = None
|
26
|
+
|
27
|
+
|
28
|
+
@attr.s(auto_attribs=True)
|
29
|
+
class PickupAddressType:
|
30
|
+
firstName: typing.Optional[str] = None
|
31
|
+
lastName: typing.Optional[str] = None
|
32
|
+
firm: typing.Optional[str] = None
|
33
|
+
address: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
34
|
+
contact: typing.Optional[typing.List[ContactType]] = jstruct.JList[ContactType]
|
35
|
+
|
36
|
+
|
37
|
+
@attr.s(auto_attribs=True)
|
38
|
+
class PickupLocationType:
|
39
|
+
packageLocation: typing.Optional[str] = None
|
40
|
+
specialInstructions: typing.Optional[str] = None
|
41
|
+
|
42
|
+
|
43
|
+
@attr.s(auto_attribs=True)
|
44
|
+
class PickupRequestType:
|
45
|
+
pickupDate: typing.Optional[str] = None
|
46
|
+
pickupAddress: typing.Optional[PickupAddressType] = jstruct.JStruct[PickupAddressType]
|
47
|
+
packages: typing.Optional[typing.List[PackageType]] = jstruct.JList[PackageType]
|
48
|
+
estimatedWeight: typing.Optional[int] = None
|
49
|
+
pickupLocation: typing.Optional[PickupLocationType] = jstruct.JStruct[PickupLocationType]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class PackageType:
|
8
|
+
packageType: typing.Optional[str] = None
|
9
|
+
packageCount: typing.Optional[int] = None
|
10
|
+
|
11
|
+
|
12
|
+
@attr.s(auto_attribs=True)
|
13
|
+
class AddressType:
|
14
|
+
streetAddress: typing.Optional[str] = None
|
15
|
+
streetAddressAbbreviation: typing.Optional[str] = None
|
16
|
+
secondaryAddress: typing.Optional[str] = None
|
17
|
+
cityAbbreviation: typing.Optional[str] = None
|
18
|
+
city: typing.Optional[str] = None
|
19
|
+
state: typing.Optional[str] = None
|
20
|
+
ZIPCode: typing.Optional[str] = None
|
21
|
+
ZIPPlus4: typing.Optional[str] = None
|
22
|
+
urbanization: typing.Optional[str] = None
|
23
|
+
|
24
|
+
|
25
|
+
@attr.s(auto_attribs=True)
|
26
|
+
class ContactType:
|
27
|
+
email: typing.Optional[str] = None
|
28
|
+
|
29
|
+
|
30
|
+
@attr.s(auto_attribs=True)
|
31
|
+
class PickupAddressType:
|
32
|
+
firstName: typing.Optional[str] = None
|
33
|
+
lastName: typing.Optional[str] = None
|
34
|
+
firm: typing.Optional[str] = None
|
35
|
+
address: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
36
|
+
contact: typing.Optional[typing.List[ContactType]] = jstruct.JList[ContactType]
|
37
|
+
|
38
|
+
|
39
|
+
@attr.s(auto_attribs=True)
|
40
|
+
class PickupLocationType:
|
41
|
+
packageLocation: typing.Optional[str] = None
|
42
|
+
specialInstructions: typing.Optional[str] = None
|
43
|
+
|
44
|
+
|
45
|
+
@attr.s(auto_attribs=True)
|
46
|
+
class CarrierPickupRequestType:
|
47
|
+
pickupDate: typing.Optional[str] = None
|
48
|
+
pickupAddress: typing.Optional[PickupAddressType] = jstruct.JStruct[PickupAddressType]
|
49
|
+
packages: typing.Optional[typing.List[PackageType]] = jstruct.JList[PackageType]
|
50
|
+
estimatedWeight: typing.Optional[int] = None
|
51
|
+
pickupLocation: typing.Optional[PickupLocationType] = jstruct.JStruct[PickupLocationType]
|
52
|
+
|
53
|
+
|
54
|
+
@attr.s(auto_attribs=True)
|
55
|
+
class PickupResponseType:
|
56
|
+
confirmationNumber: typing.Optional[str] = None
|
57
|
+
pickupDate: typing.Optional[str] = None
|
58
|
+
carrierPickupRequest: typing.Optional[CarrierPickupRequestType] = jstruct.JStruct[CarrierPickupRequestType]
|