karrio-cli 2025.5rc3__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 (68) hide show
  1. karrio_cli/__init__.py +0 -0
  2. karrio_cli/__main__.py +105 -0
  3. karrio_cli/ai/README.md +335 -0
  4. karrio_cli/ai/__init__.py +0 -0
  5. karrio_cli/ai/commands.py +102 -0
  6. karrio_cli/ai/karrio_ai/__init__.py +1 -0
  7. karrio_cli/ai/karrio_ai/agent.py +972 -0
  8. karrio_cli/ai/karrio_ai/architecture/INTEGRATION_AGENT_PROMPT.md +497 -0
  9. karrio_cli/ai/karrio_ai/architecture/MAPPING_AGENT_PROMPT.md +355 -0
  10. karrio_cli/ai/karrio_ai/architecture/REAL_WORLD_TESTING.md +305 -0
  11. karrio_cli/ai/karrio_ai/architecture/SCHEMA_AGENT_PROMPT.md +183 -0
  12. karrio_cli/ai/karrio_ai/architecture/TESTING_AGENT_PROMPT.md +448 -0
  13. karrio_cli/ai/karrio_ai/architecture/TESTING_GUIDE.md +271 -0
  14. karrio_cli/ai/karrio_ai/enhanced_tools.py +943 -0
  15. karrio_cli/ai/karrio_ai/rag_system.py +503 -0
  16. karrio_cli/ai/karrio_ai/tests/test_agent.py +350 -0
  17. karrio_cli/ai/karrio_ai/tests/test_real_integration.py +360 -0
  18. karrio_cli/ai/karrio_ai/tests/test_real_world_scenarios.py +513 -0
  19. karrio_cli/commands/__init__.py +0 -0
  20. karrio_cli/commands/codegen.py +336 -0
  21. karrio_cli/commands/login.py +139 -0
  22. karrio_cli/commands/plugins.py +168 -0
  23. karrio_cli/commands/sdk.py +870 -0
  24. karrio_cli/common/queries.py +101 -0
  25. karrio_cli/common/utils.py +368 -0
  26. karrio_cli/resources/__init__.py +0 -0
  27. karrio_cli/resources/carriers.py +91 -0
  28. karrio_cli/resources/connections.py +207 -0
  29. karrio_cli/resources/events.py +151 -0
  30. karrio_cli/resources/logs.py +151 -0
  31. karrio_cli/resources/orders.py +144 -0
  32. karrio_cli/resources/shipments.py +210 -0
  33. karrio_cli/resources/trackers.py +287 -0
  34. karrio_cli/templates/__init__.py +9 -0
  35. karrio_cli/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  36. karrio_cli/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  37. karrio_cli/templates/__pycache__/address.cpython-311.pyc +0 -0
  38. karrio_cli/templates/__pycache__/address.cpython-312.pyc +0 -0
  39. karrio_cli/templates/__pycache__/docs.cpython-311.pyc +0 -0
  40. karrio_cli/templates/__pycache__/docs.cpython-312.pyc +0 -0
  41. karrio_cli/templates/__pycache__/documents.cpython-311.pyc +0 -0
  42. karrio_cli/templates/__pycache__/documents.cpython-312.pyc +0 -0
  43. karrio_cli/templates/__pycache__/manifest.cpython-311.pyc +0 -0
  44. karrio_cli/templates/__pycache__/manifest.cpython-312.pyc +0 -0
  45. karrio_cli/templates/__pycache__/pickup.cpython-311.pyc +0 -0
  46. karrio_cli/templates/__pycache__/pickup.cpython-312.pyc +0 -0
  47. karrio_cli/templates/__pycache__/rates.cpython-311.pyc +0 -0
  48. karrio_cli/templates/__pycache__/rates.cpython-312.pyc +0 -0
  49. karrio_cli/templates/__pycache__/sdk.cpython-311.pyc +0 -0
  50. karrio_cli/templates/__pycache__/sdk.cpython-312.pyc +0 -0
  51. karrio_cli/templates/__pycache__/shipments.cpython-311.pyc +0 -0
  52. karrio_cli/templates/__pycache__/shipments.cpython-312.pyc +0 -0
  53. karrio_cli/templates/__pycache__/tracking.cpython-311.pyc +0 -0
  54. karrio_cli/templates/__pycache__/tracking.cpython-312.pyc +0 -0
  55. karrio_cli/templates/address.py +308 -0
  56. karrio_cli/templates/docs.py +150 -0
  57. karrio_cli/templates/documents.py +428 -0
  58. karrio_cli/templates/manifest.py +396 -0
  59. karrio_cli/templates/pickup.py +839 -0
  60. karrio_cli/templates/rates.py +638 -0
  61. karrio_cli/templates/sdk.py +947 -0
  62. karrio_cli/templates/shipments.py +892 -0
  63. karrio_cli/templates/tracking.py +437 -0
  64. karrio_cli-2025.5rc3.dist-info/METADATA +165 -0
  65. karrio_cli-2025.5rc3.dist-info/RECORD +68 -0
  66. karrio_cli-2025.5rc3.dist-info/WHEEL +5 -0
  67. karrio_cli-2025.5rc3.dist-info/entry_points.txt +2 -0
  68. karrio_cli-2025.5rc3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,638 @@
1
+ from jinja2 import Template
2
+
3
+ PROVIDER_RATE_TEMPLATE = Template('''"""Karrio {{name}} rate API implementation."""
4
+
5
+ # IMPLEMENTATION INSTRUCTIONS:
6
+ # 1. Uncomment the imports when the schema types are generated
7
+ # 2. Import the specific request and response types you need
8
+ # 3. Create a request instance with the appropriate request type
9
+ # 4. Extract data from the response to populate the RateDetails
10
+ #
11
+ # NOTE: JSON schema types are generated with "Type" suffix (e.g., RateRequestType),
12
+ # while XML schema types don't have this suffix (e.g., RateRequest).
13
+
14
+ import karrio.schemas.{{id}}.rate_request as {{id}}_req
15
+ import karrio.schemas.{{id}}.rate_response as {{id}}_res
16
+
17
+ import typing
18
+ import karrio.lib as lib
19
+ import karrio.core.units as units
20
+ import karrio.core.models as models
21
+ import karrio.providers.{{id}}.error as error
22
+ import karrio.providers.{{id}}.utils as provider_utils
23
+ import karrio.providers.{{id}}.units as provider_units
24
+
25
+
26
+ def parse_rate_response(
27
+ _response: lib.Deserializable[{% if is_xml_api %}lib.Element{% else %}dict{% endif %}],
28
+ settings: provider_utils.Settings,
29
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
30
+ response = _response.deserialize()
31
+
32
+ messages = error.parse_error_response(response, settings)
33
+
34
+ # Extract rate objects from the response - adjust based on carrier API structure
35
+ {% if is_xml_api %}
36
+ # For XML APIs, find the path to rate elements
37
+ rate_elements = response.xpath(".//rate") if hasattr(response, 'xpath') else []
38
+ rates = [_extract_details(rate, settings) for rate in rate_elements]
39
+ {% else %}
40
+ # For JSON APIs, find the path to rate objects
41
+ rate_objects = response.get("rates", []) if hasattr(response, 'get') else []
42
+ rates = [_extract_details(rate, settings) for rate in rate_objects]
43
+ {% endif %}
44
+
45
+ return rates, messages
46
+
47
+
48
+ def _extract_details(
49
+ data: {% if is_xml_api %}lib.Element{% else %}dict{% endif %},
50
+ settings: provider_utils.Settings,
51
+ ) -> models.RateDetails:
52
+ """
53
+ Extract rate details from carrier response data
54
+
55
+ data: The carrier-specific rate data structure
56
+ settings: The carrier connection settings
57
+
58
+ Returns a RateDetails object with extracted rate information
59
+ """
60
+ # Convert the carrier data to a proper object for easy attribute access
61
+ {% if is_xml_api %}
62
+ # For XML APIs, convert Element to proper response object
63
+ rate = lib.to_object({{id}}_res.Rate, data)
64
+
65
+ # Now access data through the object attributes
66
+ service = rate.service_code
67
+ service_name = rate.service_name
68
+ total = float(rate.total_charge) if hasattr(rate, 'total_charge') and rate.total_charge else 0.0
69
+ currency = rate.currency or "USD"
70
+ transit_days = int(rate.transit_days) if hasattr(rate, 'transit_days') and rate.transit_days else 0
71
+ {% else %}
72
+ # For JSON APIs, convert dict to proper response object
73
+ rate = lib.to_object({{id}}_res.RateResponseType, data)
74
+
75
+ # Now access data through the object attributes
76
+ service = rate.serviceCode if hasattr(rate, 'serviceCode') else ""
77
+ service_name = rate.serviceName if hasattr(rate, 'serviceName') else ""
78
+ total = float(rate.totalCharge) if hasattr(rate, 'totalCharge') and rate.totalCharge else 0.0
79
+ currency = rate.currency if hasattr(rate, 'currency') else "USD"
80
+ transit_days = int(rate.transitDays) if hasattr(rate, 'transitDays') and rate.transitDays else 0
81
+ {% endif %}
82
+
83
+ return models.RateDetails(
84
+ carrier_id=settings.carrier_id,
85
+ carrier_name=settings.carrier_name,
86
+ service=service,
87
+ total_charge=lib.to_money(total, currency),
88
+ currency=currency,
89
+ transit_days=transit_days,
90
+ meta=dict(
91
+ service_name=service_name,
92
+ # Add any other useful metadata from the carrier response
93
+ ),
94
+ )
95
+
96
+
97
+ def rate_request(
98
+ payload: models.RateRequest,
99
+ settings: provider_utils.Settings,
100
+ ) -> lib.Serializable:
101
+ """
102
+ Create a rate request for the carrier API
103
+
104
+ payload: The standardized RateRequest from karrio
105
+ settings: The carrier connection settings
106
+
107
+ Returns a Serializable object that can be sent to the carrier API
108
+ """
109
+ # Convert karrio models to carrier-specific format
110
+ shipper = lib.to_address(payload.shipper)
111
+ recipient = lib.to_address(payload.recipient)
112
+ packages = lib.to_packages(payload.parcels)
113
+ services = lib.to_services(payload.services, provider_units.ShippingService)
114
+ options = lib.to_shipping_options(
115
+ payload.options,
116
+ package_options=packages.options,
117
+ initializer=provider_units.shipping_options_initializer,
118
+ )
119
+
120
+ # Create the carrier-specific request object
121
+ {% if is_xml_api %}
122
+ # For XML API request
123
+ request = {{id}}_req.RateRequest(
124
+ # Map shipper details
125
+ shipper={{id}}_req.Address(
126
+ address_line1=shipper.address_line1,
127
+ city=shipper.city,
128
+ postal_code=shipper.postal_code,
129
+ country_code=shipper.country_code,
130
+ state_code=shipper.state_code,
131
+ person_name=shipper.person_name,
132
+ company_name=shipper.company_name,
133
+ phone_number=shipper.phone_number,
134
+ email=shipper.email,
135
+ ),
136
+ # Map recipient details
137
+ recipient={{id}}_req.Address(
138
+ address_line1=recipient.address_line1,
139
+ city=recipient.city,
140
+ postal_code=recipient.postal_code,
141
+ country_code=recipient.country_code,
142
+ state_code=recipient.state_code,
143
+ person_name=recipient.person_name,
144
+ company_name=recipient.company_name,
145
+ phone_number=recipient.phone_number,
146
+ email=recipient.email,
147
+ ),
148
+ # Map package details
149
+ packages=[
150
+ {{id}}_req.Package(
151
+ weight=package.weight.value,
152
+ weight_unit=provider_units.WeightUnit[package.weight.unit].value,
153
+ length=package.length.value if package.length else None,
154
+ width=package.width.value if package.width else None,
155
+ height=package.height.value if package.height else None,
156
+ dimension_unit=provider_units.DimensionUnit[package.dimension_unit].value if package.dimension_unit else None,
157
+ packaging_type=provider_units.PackagingType[package.packaging_type or 'your_packaging'].value,
158
+ )
159
+ for package in packages
160
+ ],
161
+ # Map service codes if specified
162
+ services=[s.value_or_key for s in services] if services else None,
163
+ # Add customer number and other account details
164
+ customer_number=settings.customer_number,
165
+ # Add any other required fields for this carrier's API
166
+ )
167
+ {% else %}
168
+ # For JSON API request
169
+ request = {{id}}_req.RateRequestType(
170
+ # Map shipper details
171
+ shipper={
172
+ "addressLine1": shipper.address_line1,
173
+ "city": shipper.city,
174
+ "postalCode": shipper.postal_code,
175
+ "countryCode": shipper.country_code,
176
+ "stateCode": shipper.state_code,
177
+ "personName": shipper.person_name,
178
+ "companyName": shipper.company_name,
179
+ "phoneNumber": shipper.phone_number,
180
+ "email": shipper.email,
181
+ },
182
+ # Map recipient details
183
+ recipient={
184
+ "addressLine1": recipient.address_line1,
185
+ "city": recipient.city,
186
+ "postalCode": recipient.postal_code,
187
+ "countryCode": recipient.country_code,
188
+ "stateCode": recipient.state_code,
189
+ "personName": recipient.person_name,
190
+ "companyName": recipient.company_name,
191
+ "phoneNumber": recipient.phone_number,
192
+ "email": recipient.email,
193
+ },
194
+ # Map package details
195
+ packages=[
196
+ {
197
+ "weight": package.weight.value,
198
+ "weightUnit": provider_units.WeightUnit[package.weight.unit].value,
199
+ "length": package.length.value if package.length else None,
200
+ "width": package.width.value if package.width else None,
201
+ "height": package.height.value if package.height else None,
202
+ "dimensionUnit": provider_units.DimensionUnit[package.dimension_unit].value if package.dimension_unit else None,
203
+ "packagingType": provider_units.PackagingType[package.packaging_type or 'your_packaging'].value,
204
+ }
205
+ for package in packages
206
+ ],
207
+ # Add service code
208
+ serviceCode=service,
209
+ # Add account information
210
+ customerNumber=settings.customer_number,
211
+ # Add label details
212
+ labelFormat=payload.label_type or "PDF",
213
+ # Add any other required fields for the carrier API
214
+ )
215
+ {% endif %}
216
+
217
+ return lib.Serializable(request, {% if is_xml_api %}lib.to_xml{% else %}lib.to_dict{% endif %})
218
+
219
+ '''
220
+ )
221
+
222
+
223
+ TEST_RATE_TEMPLATE = Template('''"""{{name}} carrier rate tests."""
224
+
225
+ import unittest
226
+ from unittest.mock import patch, ANY
227
+ from .fixture import gateway
228
+ import logging
229
+ import karrio.sdk as karrio
230
+ import karrio.lib as lib
231
+ import karrio.core.models as models
232
+
233
+ logger = logging.getLogger(__name__)
234
+
235
+
236
+ class Test{{compact_name}}Rating(unittest.TestCase):
237
+ def setUp(self):
238
+ self.maxDiff = None
239
+ self.RateRequest = models.RateRequest(**RatePayload)
240
+
241
+ def test_create_rate_request(self):
242
+ request = gateway.mapper.create_rate_request(self.RateRequest)
243
+ self.assertEqual(lib.to_dict(request.serialize()), RateRequest)
244
+
245
+ def test_get_rates(self):
246
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
247
+ mock.return_value = {% if is_xml_api %}"<r></r>"{% else %}"{}"{% endif %}
248
+ karrio.Rating.fetch(self.RateRequest).from_(gateway)
249
+ self.assertEqual(
250
+ mock.call_args[1]["url"],
251
+ f"{gateway.settings.server_url}/rates"
252
+ )
253
+
254
+ def test_parse_rate_response(self):
255
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
256
+ mock.return_value = RateResponse
257
+ parsed_response = (
258
+ karrio.Rating.fetch(self.RateRequest)
259
+ .from_(gateway)
260
+ .parse()
261
+ )
262
+ self.assertListEqual(lib.to_dict(parsed_response), ParsedRateResponse)
263
+
264
+ def test_parse_error_response(self):
265
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
266
+ mock.return_value = ErrorResponse
267
+ parsed_response = (
268
+ karrio.Rating.fetch(self.RateRequest)
269
+ .from_(gateway)
270
+ .parse()
271
+ )
272
+ self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse)
273
+
274
+
275
+ if __name__ == "__main__":
276
+ unittest.main()
277
+
278
+
279
+ RatePayload = {
280
+ "shipper": {
281
+ "address_line1": "123 Test Street",
282
+ "city": "Test City",
283
+ "postal_code": "12345",
284
+ "country_code": "US",
285
+ "state_code": "CA",
286
+ "person_name": "Test Person",
287
+ "company_name": "Test Company",
288
+ "phone_number": "1234567890",
289
+ "email": "test@example.com"
290
+ },
291
+ "recipient": {
292
+ "address_line1": "123 Test Street",
293
+ "city": "Test City",
294
+ "postal_code": "12345",
295
+ "country_code": "US",
296
+ "state_code": "CA",
297
+ "person_name": "Test Person",
298
+ "company_name": "Test Company",
299
+ "phone_number": "1234567890",
300
+ "email": "test@example.com"
301
+ },
302
+ "parcels": [{
303
+ "weight": 10.0,
304
+ "width": 10.0,
305
+ "height": 10.0,
306
+ "length": 10.0,
307
+ "weight_unit": "KG",
308
+ "dimension_unit": "CM",
309
+ "packaging_type": "BOX"
310
+ }]
311
+ }
312
+
313
+ RateRequest = {% if is_xml_api %}{
314
+ "shipper": {
315
+ "address_line1": "123 Test Street",
316
+ "city": "Test City",
317
+ "postal_code": "12345",
318
+ "country_code": "US",
319
+ "state_code": "CA",
320
+ "person_name": "Test Person",
321
+ "company_name": "Test Company",
322
+ "phone_number": "1234567890",
323
+ "email": "test@example.com"
324
+ },
325
+ "recipient": {
326
+ "address_line1": "123 Test Street",
327
+ "city": "Test City",
328
+ "postal_code": "12345",
329
+ "country_code": "US",
330
+ "state_code": "CA",
331
+ "person_name": "Test Person",
332
+ "company_name": "Test Company",
333
+ "phone_number": "1234567890",
334
+ "email": "test@example.com"
335
+ },
336
+ "packages": [
337
+ {
338
+ "weight": 10.0,
339
+ "weight_unit": "KG",
340
+ "length": 10.0,
341
+ "width": 10.0,
342
+ "height": 10.0,
343
+ "dimension_unit": "CM",
344
+ "packaging_type": "BOX"
345
+ }
346
+ ]
347
+ }{% else %}{
348
+ "shipper": {
349
+ "addressLine1": "123 Test Street",
350
+ "city": "Test City",
351
+ "postalCode": "12345",
352
+ "countryCode": "US",
353
+ "stateCode": "CA",
354
+ "personName": "Test Person",
355
+ "companyName": "Test Company",
356
+ "phoneNumber": "1234567890",
357
+ "email": "test@example.com"
358
+ },
359
+ "recipient": {
360
+ "addressLine1": "123 Test Street",
361
+ "city": "Test City",
362
+ "postalCode": "12345",
363
+ "countryCode": "US",
364
+ "stateCode": "CA",
365
+ "personName": "Test Person",
366
+ "companyName": "Test Company",
367
+ "phoneNumber": "1234567890",
368
+ "email": "test@example.com"
369
+ },
370
+ "packages": [
371
+ {
372
+ "weight": 10.0,
373
+ "weightUnit": "KG",
374
+ "length": 10.0,
375
+ "width": 10.0,
376
+ "height": 10.0,
377
+ "dimensionUnit": "CM",
378
+ "packagingType": "BOX"
379
+ }
380
+ ]
381
+ }{% endif %}
382
+
383
+ RateResponse = {% if is_xml_api %}"""<?xml version="1.0"?>
384
+ <rate-response>
385
+ <rate>
386
+ <service-code>express</service-code>
387
+ <service-name>Express Service</service-name>
388
+ <total-charge>25.99</total-charge>
389
+ <currency>USD</currency>
390
+ <transit-days>2</transit-days>
391
+ </rate>
392
+ <rate>
393
+ <service-code>ground</service-code>
394
+ <service-name>Ground Service</service-name>
395
+ <total-charge>12.99</total-charge>
396
+ <currency>USD</currency>
397
+ <transit-days>5</transit-days>
398
+ </rate>
399
+ </rate-response>"""{% else %}"""{
400
+ "rates": [
401
+ {
402
+ "serviceCode": "express",
403
+ "serviceName": "Express Service",
404
+ "totalCharge": 25.99,
405
+ "currency": "USD",
406
+ "transitDays": 2
407
+ },
408
+ {
409
+ "serviceCode": "ground",
410
+ "serviceName": "Ground Service",
411
+ "totalCharge": 12.99,
412
+ "currency": "USD",
413
+ "transitDays": 5
414
+ }
415
+ ]
416
+ }"""{% endif %}
417
+
418
+ ErrorResponse = {% if is_xml_api %}"""<?xml version="1.0"?>
419
+ <error-response>
420
+ <e>
421
+ <code>rate_error</code>
422
+ <message>Unable to get rates</message>
423
+ <details>Invalid address provided</details>
424
+ <e>
425
+ </error-response>"""{% else %}"""{
426
+ "error": {
427
+ "code": "rate_error",
428
+ "message": "Unable to get rates",
429
+ "details": "Invalid address provided"
430
+ }
431
+ }"""{% endif %}
432
+
433
+ ParsedRateResponse = [
434
+ [
435
+ {
436
+ "carrier_id": "{{id}}",
437
+ "carrier_name": "{{id}}",
438
+ "service": "express",
439
+ "currency": "USD",
440
+ "total_charge": 25.99,
441
+ "transit_days": 2,
442
+ "meta": {
443
+ "service_name": "Express Service"
444
+ }
445
+ },
446
+ {
447
+ "carrier_id": "{{id}}",
448
+ "carrier_name": "{{id}}",
449
+ "service": "ground",
450
+ "currency": "USD",
451
+ "total_charge": 12.99,
452
+ "transit_days": 5,
453
+ "meta": {
454
+ "service_name": "Ground Service"
455
+ }
456
+ }
457
+ ],
458
+ []
459
+ ]
460
+
461
+ ParsedErrorResponse = [
462
+ [],
463
+ [
464
+ {
465
+ "carrier_id": "{{id}}",
466
+ "carrier_name": "{{id}}",
467
+ "code": "rate_error",
468
+ "message": "Unable to get rates",
469
+ "details": {
470
+ "details": "Invalid address provided"
471
+ }
472
+ }
473
+ ]
474
+ ]
475
+ '''
476
+ )
477
+
478
+ XML_SCHEMA_RATE_REQUEST_TEMPLATE = Template("""<?xml version="1.0"?>
479
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/rate" xmlns="http://{{id}}.com/ws/rate" elementFormDefault="qualified">
480
+ <xsd:element name="rate-request">
481
+ <xsd:complexType>
482
+ <xsd:all>
483
+ <xsd:element name="shipper">
484
+ <xsd:complexType>
485
+ <xsd:all>
486
+ <xsd:element name="address-line1" type="xsd:string" />
487
+ <xsd:element name="city" type="xsd:string" />
488
+ <xsd:element name="postal-code" type="xsd:string" />
489
+ <xsd:element name="country-code" type="xsd:string" />
490
+ <xsd:element name="state-code" type="xsd:string" minOccurs="0" />
491
+ <xsd:element name="person-name" type="xsd:string" minOccurs="0" />
492
+ <xsd:element name="company-name" type="xsd:string" minOccurs="0" />
493
+ <xsd:element name="phone-number" type="xsd:string" minOccurs="0" />
494
+ <xsd:element name="email" type="xsd:string" minOccurs="0" />
495
+ </xsd:all>
496
+ </xsd:complexType>
497
+ </xsd:element>
498
+ <xsd:element name="recipient">
499
+ <xsd:complexType>
500
+ <xsd:all>
501
+ <xsd:element name="address-line1" type="xsd:string" />
502
+ <xsd:element name="city" type="xsd:string" />
503
+ <xsd:element name="postal-code" type="xsd:string" />
504
+ <xsd:element name="country-code" type="xsd:string" />
505
+ <xsd:element name="state-code" type="xsd:string" minOccurs="0" />
506
+ <xsd:element name="person-name" type="xsd:string" minOccurs="0" />
507
+ <xsd:element name="company-name" type="xsd:string" minOccurs="0" />
508
+ <xsd:element name="phone-number" type="xsd:string" minOccurs="0" />
509
+ <xsd:element name="email" type="xsd:string" minOccurs="0" />
510
+ </xsd:all>
511
+ </xsd:complexType>
512
+ </xsd:element>
513
+ <xsd:element name="packages">
514
+ <xsd:complexType>
515
+ <xsd:sequence>
516
+ <xsd:element name="package" maxOccurs="unbounded">
517
+ <xsd:complexType>
518
+ <xsd:all>
519
+ <xsd:element name="weight" type="xsd:decimal" />
520
+ <xsd:element name="weight-unit" type="xsd:string" />
521
+ <xsd:element name="length" type="xsd:decimal" minOccurs="0" />
522
+ <xsd:element name="width" type="xsd:decimal" minOccurs="0" />
523
+ <xsd:element name="height" type="xsd:decimal" minOccurs="0" />
524
+ <xsd:element name="dimension-unit" type="xsd:string" minOccurs="0" />
525
+ <xsd:element name="packaging-type" type="xsd:string" minOccurs="0" />
526
+ </xsd:all>
527
+ </xsd:complexType>
528
+ </xsd:element>
529
+ </xsd:sequence>
530
+ </xsd:complexType>
531
+ </xsd:element>
532
+ <xsd:element name="services" minOccurs="0">
533
+ <xsd:complexType>
534
+ <xsd:sequence>
535
+ <xsd:element name="service" type="xsd:string" maxOccurs="unbounded" />
536
+ </xsd:sequence>
537
+ </xsd:complexType>
538
+ </xsd:element>
539
+ <xsd:element name="options" type="xsd:string" minOccurs="0" />
540
+ </xsd:all>
541
+ </xsd:complexType>
542
+ </xsd:element>
543
+ </xsd:schema>
544
+ """
545
+ )
546
+
547
+ XML_SCHEMA_RATE_RESPONSE_TEMPLATE = Template("""<?xml version="1.0"?>
548
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/rate" xmlns="http://{{id}}.com/ws/rate" elementFormDefault="qualified">
549
+ <xsd:element name="rate-response">
550
+ <xsd:complexType>
551
+ <xsd:sequence>
552
+ <xsd:element name="rate" maxOccurs="unbounded">
553
+ <xsd:complexType>
554
+ <xsd:all>
555
+ <xsd:element name="service-code" type="xsd:string" />
556
+ <xsd:element name="service-name" type="xsd:string" minOccurs="0" />
557
+ <xsd:element name="total-charge" type="xsd:decimal" />
558
+ <xsd:element name="currency" type="xsd:string" minOccurs="0" />
559
+ <xsd:element name="transit-days" type="xsd:integer" minOccurs="0" />
560
+ </xsd:all>
561
+ </xsd:complexType>
562
+ </xsd:element>
563
+ </xsd:sequence>
564
+ </xsd:complexType>
565
+ </xsd:element>
566
+ </xsd:schema>
567
+ """
568
+ )
569
+
570
+ JSON_SCHEMA_RATE_REQUEST_TEMPLATE = Template(
571
+ """{
572
+ "rateRequest": {
573
+ "shipper": {
574
+ "addressLine1": "123 Main St",
575
+ "city": "Anytown",
576
+ "postalCode": "12345",
577
+ "countryCode": "US",
578
+ "stateCode": "CA",
579
+ "personName": "John Doe",
580
+ "companyName": "ACME Corp",
581
+ "phoneNumber": "555-123-4567",
582
+ "email": "john@example.com"
583
+ },
584
+ "recipient": {
585
+ "addressLine1": "456 Oak St",
586
+ "city": "Somewhere",
587
+ "postalCode": "67890",
588
+ "countryCode": "US",
589
+ "stateCode": "NY",
590
+ "personName": "Jane Smith",
591
+ "companyName": "XYZ Inc",
592
+ "phoneNumber": "555-987-6543",
593
+ "email": "jane@example.com"
594
+ },
595
+ "packages": [
596
+ {
597
+ "weight": 10.5,
598
+ "weightUnit": "KG",
599
+ "length": 20.0,
600
+ "width": 15.0,
601
+ "height": 10.0,
602
+ "dimensionUnit": "CM",
603
+ "packagingType": "BOX"
604
+ }
605
+ ],
606
+ "services": ["EXPRESS", "GROUND"],
607
+ "options": {
608
+ "insurance": true,
609
+ "signature_required": false
610
+ }
611
+ }
612
+ }
613
+ """
614
+ )
615
+
616
+ JSON_SCHEMA_RATE_RESPONSE_TEMPLATE = Template(
617
+ """{
618
+ "rateResponse": {
619
+ "rates": [
620
+ {
621
+ "serviceCode": "EXPRESS",
622
+ "serviceName": "Express Shipping",
623
+ "totalCharge": 25.99,
624
+ "currency": "USD",
625
+ "transitDays": 2
626
+ },
627
+ {
628
+ "serviceCode": "GROUND",
629
+ "serviceName": "Ground Shipping",
630
+ "totalCharge": 12.99,
631
+ "currency": "USD",
632
+ "transitDays": 5
633
+ }
634
+ ]
635
+ }
636
+ }
637
+ """
638
+ )