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,839 @@
1
+ from jinja2 import Template
2
+
3
+ PROVIDER_PICKUP_CREATE_TEMPLATE = Template(
4
+ '''"""Karrio {{name}} pickup API implementation."""
5
+
6
+ import typing
7
+ import karrio.lib as lib
8
+ import karrio.core.models as models
9
+ import karrio.providers.{{id}}.error as error
10
+ import karrio.providers.{{id}}.utils as provider_utils
11
+
12
+
13
+ def parse_pickup_response(
14
+ _response: lib.Deserializable[{% if is_xml_api %}lib.Element{% else %}dict{% endif %}],
15
+ settings: provider_utils.Settings,
16
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
17
+ """
18
+ Parse pickup response from carrier API
19
+
20
+ _response: The carrier response to deserialize
21
+ settings: The carrier connection settings
22
+
23
+ Returns a tuple with (PickupDetails, List[Message])
24
+ """
25
+ response = _response.deserialize()
26
+ messages = error.parse_error_response(response, settings)
27
+
28
+ # Extract pickup details
29
+ pickup = _extract_details(response, settings)
30
+
31
+ return pickup, messages
32
+
33
+
34
+ def _extract_details(
35
+ response: {% if is_xml_api %}lib.Element{% else %}dict{% endif %},
36
+ settings: provider_utils.Settings,
37
+ ) -> models.PickupDetails:
38
+ """
39
+ Extract pickup details from carrier response data
40
+
41
+ data: The carrier-specific pickup response data
42
+ settings: The carrier connection settings
43
+
44
+ Returns a PickupDetails object with the pickup information
45
+ """
46
+ {% if is_xml_api %}
47
+ # Example implementation for XML response:
48
+ # Extract pickup details from the XML response
49
+ # confirmation_number = lib.find_element("confirmation-number", response, first=True).text
50
+ # pickup_date = lib.find_element("pickup-date", response, first=True).text
51
+ # ready_time = lib.find_element("ready-time", response, first=True).text
52
+ # closing_time = lib.find_element("closing-time", response, first=True).text
53
+
54
+ # For development, return sample data
55
+ confirmation_number = "PICKUP123"
56
+ pickup_date = lib.today_str()
57
+ ready_time = "09:00"
58
+ closing_time = "17:00"
59
+ {% else %}
60
+ # Example implementation for JSON response:
61
+ # Extract pickup details from the JSON response
62
+ # confirmation_number = response.get("confirmationNumber")
63
+ # pickup_date = response.get("pickupDate")
64
+ # ready_time = response.get("readyTime")
65
+ # closing_time = response.get("closingTime")
66
+
67
+ # For development, return sample data
68
+ confirmation_number = "PICKUP123"
69
+ pickup_date = lib.today_str()
70
+ ready_time = "09:00"
71
+ closing_time = "17:00"
72
+ {% endif %}
73
+
74
+ return models.PickupDetails(
75
+ carrier_id=settings.carrier_id,
76
+ carrier_name=settings.carrier_name,
77
+ confirmation_number=confirmation_number,
78
+ pickup_date=lib.fdate(pickup_date),
79
+ ready_time=ready_time,
80
+ closing_time=closing_time,
81
+ )
82
+
83
+
84
+ def pickup_request(
85
+ payload: models.PickupRequest,
86
+ settings: provider_utils.Settings,
87
+ ) -> lib.Serializable:
88
+ """
89
+ Create a pickup request for the carrier API
90
+
91
+ payload: The standardized PickupRequest from karrio
92
+ settings: The carrier connection settings
93
+
94
+ Returns a Serializable object that can be sent to the carrier API
95
+ """
96
+ # Extract pickup details
97
+ address = lib.to_address(payload.address)
98
+ pickup_date = payload.pickup_date or lib.today_str()
99
+ ready_time = payload.ready_time or "09:00"
100
+ closing_time = payload.closing_time or "17:00"
101
+
102
+ {% if is_xml_api %}
103
+ # Example implementation for XML request:
104
+ request = f"""<?xml version="1.0"?>
105
+ <pickup-request>
106
+ <pickup-date>{pickup_date}</pickup-date>
107
+ <ready-time>{ready_time}</ready-time>
108
+ <closing-time>{closing_time}</closing-time>
109
+ <address>
110
+ <address-line1>{address.address_line1}</address-line1>
111
+ <city>{address.city}</city>
112
+ <postal-code>{address.postal_code}</postal-code>
113
+ <country-code>{address.country_code}</country-code>
114
+ <state-code>{address.state_code}</state-code>
115
+ <person-name>{address.person_name}</person-name>
116
+ <company-name>{address.company_name}</company-name>
117
+ <phone-number>{address.phone_number}</phone-number>
118
+ <email>{address.email}</email>
119
+ </address>
120
+ </pickup-request>"""
121
+
122
+ return lib.Serializable(request, lambda r: r)
123
+ {% else %}
124
+ # Example implementation for JSON request:
125
+ request = {
126
+ "pickupDate": pickup_date,
127
+ "readyTime": ready_time,
128
+ "closingTime": closing_time,
129
+ "address": {
130
+ "addressLine1": address.address_line1,
131
+ "city": address.city,
132
+ "postalCode": address.postal_code,
133
+ "countryCode": address.country_code,
134
+ "stateCode": address.state_code,
135
+ "personName": address.person_name,
136
+ "companyName": address.company_name,
137
+ "phoneNumber": address.phone_number,
138
+ "email": address.email,
139
+ }
140
+ }
141
+
142
+ return lib.Serializable(request, lib.to_dict)
143
+ {% endif %}
144
+ '''
145
+ )
146
+
147
+ PROVIDER_PICKUP_UPDATE_TEMPLATE = Template('''"""Karrio {{name}} pickup update API implementation."""
148
+
149
+ import typing
150
+ import karrio.lib as lib
151
+ import karrio.core.models as models
152
+ import karrio.providers.{{id}}.error as error
153
+ import karrio.providers.{{id}}.utils as provider_utils
154
+
155
+
156
+ def parse_pickup_update_response(
157
+ _response: lib.Deserializable[{% if is_xml_api %}lib.Element{% else %}dict{% endif %}],
158
+ settings: provider_utils.Settings,
159
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
160
+ """
161
+ Parse pickup update response from carrier API
162
+
163
+ _response: The carrier response to deserialize
164
+ settings: The carrier connection settings
165
+
166
+ Returns a tuple with (PickupDetails, List[Message])
167
+ """
168
+ response = _response.deserialize()
169
+ messages = error.parse_error_response(response, settings)
170
+
171
+ # Extract updated pickup details
172
+ pickup = _extract_details(response, settings)
173
+
174
+ return pickup, messages
175
+
176
+
177
+ def _extract_details(
178
+ response: {% if is_xml_api %}lib.Element{% else %}dict{% endif %},
179
+ settings: provider_utils.Settings,
180
+ ) -> models.PickupDetails:
181
+ """
182
+ Extract pickup details from carrier response data
183
+
184
+ data: The carrier-specific pickup response data
185
+ settings: The carrier connection settings
186
+
187
+ Returns a PickupDetails object with the pickup information
188
+ """
189
+ {% if is_xml_api %}
190
+ # Example implementation for XML response:
191
+ # Extract pickup details from the XML response
192
+ # confirmation_number = lib.find_element("confirmation-number", response, first=True).text
193
+ # pickup_date = lib.find_element("pickup-date", response, first=True).text
194
+ # ready_time = lib.find_element("ready-time", response, first=True).text
195
+ # closing_time = lib.find_element("closing-time", response, first=True).text
196
+
197
+ # For development, return sample data
198
+ confirmation_number = "PICKUP123"
199
+ pickup_date = lib.today_str()
200
+ ready_time = "10:00"
201
+ closing_time = "18:00"
202
+ {% else %}
203
+ # Example implementation for JSON response:
204
+ # Extract pickup details from the JSON response
205
+ # confirmation_number = response.get("confirmationNumber")
206
+ # pickup_date = response.get("pickupDate")
207
+ # ready_time = response.get("readyTime")
208
+ # closing_time = response.get("closingTime")
209
+
210
+ # For development, return sample data
211
+ confirmation_number = "PICKUP123"
212
+ pickup_date = lib.today_str()
213
+ ready_time = "10:00"
214
+ closing_time = "18:00"
215
+ {% endif %}
216
+
217
+ return models.PickupDetails(
218
+ carrier_id=settings.carrier_id,
219
+ carrier_name=settings.carrier_name,
220
+ confirmation_number=confirmation_number,
221
+ pickup_date=lib.fdate(pickup_date),
222
+ ready_time=ready_time,
223
+ closing_time=closing_time,
224
+ )
225
+
226
+
227
+ def pickup_update_request(
228
+ payload: models.PickupUpdateRequest,
229
+ settings: provider_utils.Settings,
230
+ ) -> lib.Serializable:
231
+ """
232
+ Create a pickup update request for the carrier API
233
+
234
+ payload: The standardized PickupUpdateRequest from karrio
235
+ settings: The carrier connection settings
236
+
237
+ Returns a Serializable object that can be sent to the carrier API
238
+ """
239
+ # Extract pickup update details
240
+ confirmation_number = payload.confirmation_number
241
+ address = lib.to_address(payload.address)
242
+ pickup_date = payload.pickup_date or lib.today_str()
243
+ ready_time = payload.ready_time or "10:00"
244
+ closing_time = payload.closing_time or "18:00"
245
+
246
+ {% if is_xml_api %}
247
+ # Example implementation for XML request:
248
+ request = f"""<?xml version="1.0"?>
249
+ <pickup-update-request>
250
+ <confirmation-number>{confirmation_number}</confirmation-number>
251
+ <pickup-date>{pickup_date}</pickup-date>
252
+ <ready-time>{ready_time}</ready-time>
253
+ <closing-time>{closing_time}</closing-time>
254
+ <address>
255
+ <address-line1>{address.address_line1}</address-line1>
256
+ <city>{address.city}</city>
257
+ <postal-code>{address.postal_code}</postal-code>
258
+ <country-code>{address.country_code}</country-code>
259
+ <state-code>{address.state_code}</state-code>
260
+ <person-name>{address.person_name}</person-name>
261
+ <company-name>{address.company_name}</company-name>
262
+ <phone-number>{address.phone_number}</phone-number>
263
+ <email>{address.email}</email>
264
+ </address>
265
+ </pickup-update-request>"""
266
+
267
+ return lib.Serializable(request, lambda r: r)
268
+ {% else %}
269
+ # Example implementation for JSON request:
270
+ request = {
271
+ "confirmationNumber": confirmation_number,
272
+ "pickupDate": pickup_date,
273
+ "readyTime": ready_time,
274
+ "closingTime": closing_time,
275
+ "address": {
276
+ "addressLine1": address.address_line1,
277
+ "city": address.city,
278
+ "postalCode": address.postal_code,
279
+ "countryCode": address.country_code,
280
+ "stateCode": address.state_code,
281
+ "personName": address.person_name,
282
+ "companyName": address.company_name,
283
+ "phoneNumber": address.phone_number,
284
+ "email": address.email,
285
+ }
286
+ }
287
+
288
+ return lib.Serializable(request, lib.to_dict)
289
+ {% endif %}
290
+ '''
291
+ )
292
+
293
+ PROVIDER_PICKUP_CANCEL_TEMPLATE = Template('''"""Karrio {{name}} pickup cancellation API implementation."""
294
+
295
+ import typing
296
+ import karrio.lib as lib
297
+ import karrio.core.models as models
298
+ import karrio.providers.{{id}}.error as error
299
+ import karrio.providers.{{id}}.utils as provider_utils
300
+
301
+
302
+ def parse_pickup_cancel_response(
303
+ _response: lib.Deserializable[{% if is_xml_api %}lib.Element{% else %}dict{% endif %}],
304
+ settings: provider_utils.Settings,
305
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
306
+ """Parse pickup cancellation response from carrier API"""
307
+ response = _response.deserialize()
308
+ messages = error.parse_error_response(response, settings)
309
+
310
+ # Check if cancellation was successful
311
+ success = _extract_cancellation_status(response)
312
+ confirmation = (
313
+ models.ConfirmationDetails(
314
+ carrier_id=settings.carrier_id,
315
+ carrier_name=settings.carrier_name,
316
+ success=success,
317
+ operation="Cancel Pickup",
318
+ ) if success else None
319
+ )
320
+
321
+ return confirmation, messages
322
+
323
+
324
+ def _extract_cancellation_status(
325
+ response: {% if is_xml_api %}lib.Element{% else %}dict{% endif %}
326
+ ) -> bool:
327
+ """Extract cancellation success status from carrier response"""
328
+ {% if is_xml_api %}
329
+ # Example implementation for XML response:
330
+ # status_node = lib.find_element("status", response, first=True)
331
+ # return status_node is not None and status_node.text.lower() == "cancelled"
332
+
333
+ # For development, always return success
334
+ return True
335
+ {% else %}
336
+ # Example implementation for JSON response:
337
+ # return response.get("status", "").lower() == "cancelled"
338
+
339
+ # For development, always return success
340
+ return True
341
+ {% endif %}
342
+
343
+
344
+ def pickup_cancel_request(
345
+ payload: models.PickupCancelRequest,
346
+ settings: provider_utils.Settings,
347
+ ) -> lib.Serializable:
348
+ """Create pickup cancellation request for carrier API"""
349
+ # Extract cancellation details
350
+ confirmation_number = payload.confirmation_number
351
+
352
+ {% if is_xml_api %}
353
+ # Example implementation for XML request:
354
+ request = f"""<?xml version="1.0"?>
355
+ <pickup-cancel-request>
356
+ <confirmation-number>{confirmation_number}</confirmation-number>
357
+ </pickup-cancel-request>"""
358
+
359
+ return lib.Serializable(request, lambda r: r)
360
+ {% else %}
361
+ # Example implementation for JSON request:
362
+ request = {
363
+ "confirmationNumber": confirmation_number
364
+ }
365
+
366
+ return lib.Serializable(request, lib.to_dict)
367
+ {% endif %}
368
+ '''
369
+ )
370
+
371
+ PROVIDER_PICKUP_IMPORTS_TEMPLATE = Template(
372
+ '''"""Karrio {{name}} pickup API imports."""
373
+
374
+ from karrio.providers.{{id}}.pickup.create import (
375
+ parse_pickup_response,
376
+ pickup_request,
377
+ )
378
+ from karrio.providers.{{id}}.pickup.update import (
379
+ parse_pickup_update_response,
380
+ pickup_update_request,
381
+ )
382
+ from karrio.providers.{{id}}.pickup.cancel import (
383
+ parse_pickup_cancel_response,
384
+ pickup_cancel_request,
385
+ )
386
+ '''
387
+ )
388
+
389
+
390
+ # XML schema templates for pickup operations
391
+ XML_SCHEMA_PICKUP_CREATE_REQUEST_TEMPLATE = Template("""<?xml version="1.0"?>
392
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/pickup-request" xmlns="http://{{id}}.com/ws/pickup-request" elementFormDefault="qualified">
393
+ <xsd:element name="pickup-request">
394
+ <xsd:complexType>
395
+ <xsd:all>
396
+ <xsd:element name="account-number" type="xsd:string" minOccurs="0" />
397
+ <xsd:element name="pickup-date" type="xsd:date" />
398
+ <xsd:element name="ready-time" type="xsd:string" />
399
+ <xsd:element name="closing-time" type="xsd:string" />
400
+ <xsd:element name="instruction" type="xsd:string" minOccurs="0" />
401
+ <xsd:element name="address">
402
+ <xsd:complexType>
403
+ <xsd:all>
404
+ <xsd:element name="company-name" type="xsd:string" minOccurs="0" />
405
+ <xsd:element name="person-name" type="xsd:string" />
406
+ <xsd:element name="street" type="xsd:string" />
407
+ <xsd:element name="city" type="xsd:string" />
408
+ <xsd:element name="state" type="xsd:string" minOccurs="0" />
409
+ <xsd:element name="postal-code" type="xsd:string" />
410
+ <xsd:element name="country" type="xsd:string" />
411
+ <xsd:element name="phone" type="xsd:string" minOccurs="0" />
412
+ <xsd:element name="email" type="xsd:string" minOccurs="0" />
413
+ </xsd:all>
414
+ </xsd:complexType>
415
+ </xsd:element>
416
+ <xsd:element name="parcel-count" type="xsd:integer" minOccurs="0" />
417
+ <xsd:element name="weight" type="xsd:decimal" minOccurs="0" />
418
+ <xsd:element name="weight-unit" type="xsd:string" minOccurs="0" />
419
+ </xsd:all>
420
+ </xsd:complexType>
421
+ </xsd:element>
422
+ </xsd:schema>
423
+ """
424
+ )
425
+
426
+ XML_SCHEMA_PICKUP_CREATE_RESPONSE_TEMPLATE = Template("""<?xml version="1.0"?>
427
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/pickup-response" xmlns="http://{{id}}.com/ws/pickup-response" elementFormDefault="qualified">
428
+ <xsd:element name="pickup-response">
429
+ <xsd:complexType>
430
+ <xsd:all>
431
+ <xsd:element name="confirmation-number" type="xsd:string" />
432
+ <xsd:element name="pickup-date" type="xsd:date" />
433
+ <xsd:element name="ready-time" type="xsd:string" minOccurs="0" />
434
+ <xsd:element name="closing-time" type="xsd:string" minOccurs="0" />
435
+ <xsd:element name="status" type="xsd:string" minOccurs="0" />
436
+ <xsd:element name="request-id" type="xsd:string" minOccurs="0" />
437
+ </xsd:all>
438
+ </xsd:complexType>
439
+ </xsd:element>
440
+ </xsd:schema>
441
+ """
442
+ )
443
+
444
+ XML_SCHEMA_PICKUP_UPDATE_REQUEST_TEMPLATE = Template("""<?xml version="1.0"?>
445
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/pickup-update-request" xmlns="http://{{id}}.com/ws/pickup-update-request" elementFormDefault="qualified">
446
+ <xsd:element name="pickup-update-request">
447
+ <xsd:complexType>
448
+ <xsd:all>
449
+ <xsd:element name="confirmation-number" type="xsd:string" />
450
+ <xsd:element name="account-number" type="xsd:string" minOccurs="0" />
451
+ <xsd:element name="pickup-date" type="xsd:date" />
452
+ <xsd:element name="ready-time" type="xsd:string" />
453
+ <xsd:element name="closing-time" type="xsd:string" />
454
+ <xsd:element name="instruction" type="xsd:string" minOccurs="0" />
455
+ <xsd:element name="address">
456
+ <xsd:complexType>
457
+ <xsd:all>
458
+ <xsd:element name="company-name" type="xsd:string" minOccurs="0" />
459
+ <xsd:element name="person-name" type="xsd:string" />
460
+ <xsd:element name="street" type="xsd:string" />
461
+ <xsd:element name="city" type="xsd:string" />
462
+ <xsd:element name="state" type="xsd:string" minOccurs="0" />
463
+ <xsd:element name="postal-code" type="xsd:string" />
464
+ <xsd:element name="country" type="xsd:string" />
465
+ <xsd:element name="phone" type="xsd:string" minOccurs="0" />
466
+ <xsd:element name="email" type="xsd:string" minOccurs="0" />
467
+ </xsd:all>
468
+ </xsd:complexType>
469
+ </xsd:element>
470
+ <xsd:element name="parcel-count" type="xsd:integer" minOccurs="0" />
471
+ <xsd:element name="weight" type="xsd:decimal" minOccurs="0" />
472
+ <xsd:element name="weight-unit" type="xsd:string" minOccurs="0" />
473
+ </xsd:all>
474
+ </xsd:complexType>
475
+ </xsd:element>
476
+ </xsd:schema>
477
+ """
478
+ )
479
+
480
+ XML_SCHEMA_PICKUP_UPDATE_RESPONSE_TEMPLATE = Template("""<?xml version="1.0"?>
481
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/pickup-update-response" xmlns="http://{{id}}.com/ws/pickup-update-response" elementFormDefault="qualified">
482
+ <xsd:element name="pickup-update-response">
483
+ <xsd:complexType>
484
+ <xsd:all>
485
+ <xsd:element name="confirmation-number" type="xsd:string" />
486
+ <xsd:element name="pickup-date" type="xsd:date" />
487
+ <xsd:element name="ready-time" type="xsd:string" minOccurs="0" />
488
+ <xsd:element name="closing-time" type="xsd:string" minOccurs="0" />
489
+ <xsd:element name="status" type="xsd:string" minOccurs="0" />
490
+ <xsd:element name="request-id" type="xsd:string" minOccurs="0" />
491
+ </xsd:all>
492
+ </xsd:complexType>
493
+ </xsd:element>
494
+ </xsd:schema>
495
+ """
496
+ )
497
+
498
+ XML_SCHEMA_PICKUP_CANCEL_REQUEST_TEMPLATE = Template("""<?xml version="1.0"?>
499
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/pickup-cancel-request" xmlns="http://{{id}}.com/ws/pickup-cancel-request" elementFormDefault="qualified">
500
+ <xsd:element name="pickup-cancel-request">
501
+ <xsd:complexType>
502
+ <xsd:all>
503
+ <xsd:element name="confirmation-number" type="xsd:string" />
504
+ <xsd:element name="pickup-date" type="xsd:date" minOccurs="0" />
505
+ <xsd:element name="reason" type="xsd:string" minOccurs="0" />
506
+ <xsd:element name="account-number" type="xsd:string" minOccurs="0" />
507
+ </xsd:all>
508
+ </xsd:complexType>
509
+ </xsd:element>
510
+ </xsd:schema>
511
+ """
512
+ )
513
+
514
+ XML_SCHEMA_PICKUP_CANCEL_RESPONSE_TEMPLATE = Template("""<?xml version="1.0"?>
515
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/pickup-cancel-response" xmlns="http://{{id}}.com/ws/pickup-cancel-response" elementFormDefault="qualified">
516
+ <xsd:element name="pickup-cancel-response">
517
+ <xsd:complexType>
518
+ <xsd:all>
519
+ <xsd:element name="status" type="xsd:string" />
520
+ <xsd:element name="confirmation-number" type="xsd:string" minOccurs="0" />
521
+ <xsd:element name="status-message" type="xsd:string" minOccurs="0" />
522
+ </xsd:all>
523
+ </xsd:complexType>
524
+ </xsd:element>
525
+ </xsd:schema>
526
+ """
527
+ )
528
+
529
+ # JSON schema templates for pickup operations
530
+ JSON_SCHEMA_PICKUP_CREATE_REQUEST_TEMPLATE = Template(
531
+ """{
532
+ "pickupRequest": {
533
+ "accountNumber": "123456",
534
+ "pickupDate": "2023-06-01",
535
+ "readyTime": "09:00",
536
+ "closingTime": "17:00",
537
+ "instruction": "Please knock loudly",
538
+ "address": {
539
+ "companyName": "ACME Corp",
540
+ "personName": "John Doe",
541
+ "street": "123 Main St",
542
+ "city": "Anytown",
543
+ "state": "CA",
544
+ "postalCode": "12345",
545
+ "country": "US",
546
+ "phone": "555-123-4567",
547
+ "email": "john@example.com"
548
+ },
549
+ "parcelCount": 3,
550
+ "weight": 10.5,
551
+ "weightUnit": "KG"
552
+ }
553
+ }
554
+ """
555
+ )
556
+
557
+ JSON_SCHEMA_PICKUP_CREATE_RESPONSE_TEMPLATE = Template(
558
+ """{
559
+ "pickupResponse": {
560
+ "confirmationNumber": "PICKUP123456",
561
+ "pickupDate": "2023-06-01",
562
+ "readyTime": "09:00",
563
+ "closingTime": "17:00",
564
+ "status": "scheduled",
565
+ "requestId": "REQ123456"
566
+ }
567
+ }
568
+ """
569
+ )
570
+
571
+ JSON_SCHEMA_PICKUP_UPDATE_REQUEST_TEMPLATE = Template(
572
+ """{
573
+ "pickupUpdateRequest": {
574
+ "confirmationNumber": "PICKUP123456",
575
+ "accountNumber": "123456",
576
+ "pickupDate": "2023-06-01",
577
+ "readyTime": "10:00",
578
+ "closingTime": "18:00",
579
+ "instruction": "Please knock loudly and call before arrival",
580
+ "address": {
581
+ "companyName": "ACME Corp",
582
+ "personName": "John Doe",
583
+ "street": "123 Main St",
584
+ "city": "Anytown",
585
+ "state": "CA",
586
+ "postalCode": "12345",
587
+ "country": "US",
588
+ "phone": "555-123-4567",
589
+ "email": "john@example.com"
590
+ },
591
+ "parcelCount": 5,
592
+ "weight": 15.5,
593
+ "weightUnit": "KG"
594
+ }
595
+ }
596
+ """
597
+ )
598
+
599
+ JSON_SCHEMA_PICKUP_UPDATE_RESPONSE_TEMPLATE = Template(
600
+ """{
601
+ "pickupUpdateResponse": {
602
+ "confirmationNumber": "PICKUP123456",
603
+ "pickupDate": "2023-06-01",
604
+ "readyTime": "10:00",
605
+ "closingTime": "18:00",
606
+ "status": "modified",
607
+ "requestId": "REQ123456"
608
+ }
609
+ }
610
+ """
611
+ )
612
+
613
+ JSON_SCHEMA_PICKUP_CANCEL_REQUEST_TEMPLATE = Template(
614
+ """{
615
+ "pickupCancelRequest": {
616
+ "confirmationNumber": "PICKUP123456",
617
+ "accountNumber": "123456",
618
+ "pickupDate": "2023-06-01",
619
+ "reason": "No longer needed"
620
+ }
621
+ }
622
+ """
623
+ )
624
+
625
+ JSON_SCHEMA_PICKUP_CANCEL_RESPONSE_TEMPLATE = Template(
626
+ """{
627
+ "pickupCancelResponse": {
628
+ "status": "cancelled",
629
+ "confirmationNumber": "PICKUP123456",
630
+ "statusMessage": "Pickup successfully cancelled"
631
+ }
632
+ }
633
+ """
634
+ )
635
+
636
+
637
+ TEST_PICKUP_TEMPLATE = Template('''"""{{name}} carrier pickup tests."""
638
+
639
+ import unittest
640
+ import karrio.sdk as karrio
641
+ import karrio.lib as lib
642
+ import karrio.core.models as models
643
+ from unittest.mock import patch
644
+ from .fixture import gateway
645
+
646
+
647
+ class TestPickup(unittest.TestCase):
648
+ def setUp(self):
649
+ self.maxDiff = None
650
+ self.PickupRequest = models.PickupRequest(**PickupPayload)
651
+
652
+ def test_create_pickup_request(self):
653
+ request = gateway.mapper.create_pickup_request(self.PickupRequest)
654
+ self.assertEqual(lib.to_dict(request.serialize()), PickupRequest)
655
+
656
+ def test_schedule_pickup(self):
657
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
658
+ mock.return_value = {% if is_xml_api %}"<r></r>"{% else %}"{}"{% endif %}
659
+ karrio.Pickup.schedule(self.PickupRequest).from_(gateway)
660
+ self.assertEqual(
661
+ mock.call_args[1]["url"],
662
+ f"{gateway.settings.server_url}/pickups"
663
+ )
664
+
665
+ def test_update_pickup(self):
666
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
667
+ mock.return_value = {% if is_xml_api %}"<r></r>"{% else %}"{}"{% endif %}
668
+ karrio.Pickup.update(self.PickupRequest).from_(gateway)
669
+ self.assertEqual(
670
+ mock.call_args[1]["url"],
671
+ f"{gateway.settings.server_url}/pickups/123/update"
672
+ )
673
+
674
+ def test_cancel_pickup(self):
675
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
676
+ mock.return_value = {% if is_xml_api %}"<r></r>"{% else %}"{}"{% endif %}
677
+ karrio.Pickup.cancel(self.PickupRequest).from_(gateway)
678
+ self.assertEqual(
679
+ mock.call_args[1]["url"],
680
+ f"{gateway.settings.server_url}/pickups/123/cancel"
681
+ )
682
+
683
+ def test_parse_pickup_response(self):
684
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
685
+ mock.return_value = PickupResponse
686
+ parsed_response = (
687
+ karrio.Pickup.schedule(self.PickupRequest)
688
+ .from_(gateway)
689
+ .parse()
690
+ )
691
+ self.assertListEqual(lib.to_dict(parsed_response), ParsedPickupResponse)
692
+
693
+ def test_parse_error_response(self):
694
+ with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock:
695
+ mock.return_value = ErrorResponse
696
+ parsed_response = (
697
+ karrio.Pickup.schedule(self.PickupRequest)
698
+ .from_(gateway)
699
+ .parse()
700
+ )
701
+ self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse)
702
+
703
+
704
+ if __name__ == "__main__":
705
+ unittest.main()
706
+
707
+
708
+ PickupPayload = {
709
+ "address": {
710
+ "address_line1": "123 Test Street",
711
+ "city": "Test City",
712
+ "postal_code": "12345",
713
+ "country_code": "US",
714
+ "state_code": "CA",
715
+ "person_name": "Test Person",
716
+ "company_name": "Test Company",
717
+ "phone_number": "1234567890",
718
+ "email": "test@example.com"
719
+ },
720
+ "pickup_date": "2024-01-01",
721
+ "ready_time": "09:00",
722
+ "closing_time": "17:00",
723
+ "confirmation_number": "123"
724
+ }
725
+
726
+ PickupRequest = {% if is_xml_api %}{
727
+ "address": {
728
+ "address_line1": "123 Test Street",
729
+ "city": "Test City",
730
+ "postal_code": "12345",
731
+ "country_code": "US",
732
+ "state_code": "CA",
733
+ "person_name": "Test Person",
734
+ "company_name": "Test Company",
735
+ "phone_number": "1234567890",
736
+ "email": "test@example.com"
737
+ },
738
+ "pickup_date": "2024-01-01",
739
+ "ready_time": "09:00",
740
+ "closing_time": "17:00"
741
+ }{% else %}{
742
+ "address": {
743
+ "addressLine1": "123 Test Street",
744
+ "city": "Test City",
745
+ "postalCode": "12345",
746
+ "countryCode": "US",
747
+ "stateCode": "CA",
748
+ "personName": "Test Person",
749
+ "companyName": "Test Company",
750
+ "phoneNumber": "1234567890",
751
+ "email": "test@example.com"
752
+ },
753
+ "pickupDate": "2024-01-01",
754
+ "readyTime": "09:00",
755
+ "closingTime": "17:00"
756
+ }{% endif %}
757
+
758
+ PickupResponse = {% if is_xml_api %}"""<?xml version="1.0"?>
759
+ <pickup-response>
760
+ <confirmation-number>PICKUP123</confirmation-number>
761
+ <pickup-date>2024-01-01</pickup-date>
762
+ <ready-time>09:00</ready-time>
763
+ <closing-time>17:00</closing-time>
764
+ <status>scheduled</status>
765
+ </pickup-response>"""{% else %}"""{
766
+ "confirmationNumber": "PICKUP123",
767
+ "pickupDate": "2024-01-01",
768
+ "readyTime": "09:00",
769
+ "closingTime": "17:00",
770
+ "status": "scheduled"
771
+ }"""{% endif %}
772
+
773
+ PickupUpdateResponse = {% if is_xml_api %}"""<?xml version="1.0"?>
774
+ <pickup-update-response>
775
+ <confirmation-number>PICKUP123</confirmation-number>
776
+ <pickup-date>2024-01-02</pickup-date>
777
+ <ready-time>10:00</ready-time>
778
+ <closing-time>18:00</closing-time>
779
+ <status>updated</status>
780
+ </pickup-update-response>"""{% else %}"""{
781
+ "confirmationNumber": "PICKUP123",
782
+ "pickupDate": "2024-01-02",
783
+ "readyTime": "10:00",
784
+ "closingTime": "18:00",
785
+ "status": "updated"
786
+ }"""{% endif %}
787
+
788
+ PickupCancelResponse = {% if is_xml_api %}"""<?xml version="1.0"?>
789
+ <pickup-cancel-response>
790
+ <success>true</success>
791
+ <message>Pickup successfully cancelled</message>
792
+ </pickup-cancel-response>"""{% else %}"""{
793
+ "success": true,
794
+ "message": "Pickup successfully cancelled"
795
+ }"""{% endif %}
796
+
797
+ ErrorResponse = {% if is_xml_api %}"""<?xml version="1.0"?>
798
+ <error-response>
799
+ <e>
800
+ <code>pickup_error</code>
801
+ <message>Unable to schedule pickup</message>
802
+ <details>Invalid pickup date provided</details>
803
+ </e>
804
+ </error-response>"""{% else %}"""{
805
+ "error": {
806
+ "code": "pickup_error",
807
+ "message": "Unable to schedule pickup",
808
+ "details": "Invalid pickup date provided"
809
+ }
810
+ }"""{% endif %}
811
+
812
+ ParsedPickupResponse = [
813
+ {
814
+ "carrier_id": "{{id}}",
815
+ "carrier_name": "{{id}}",
816
+ "confirmation_number": "PICKUP123",
817
+ "pickup_date": "2024-01-01",
818
+ "ready_time": "09:00",
819
+ "closing_time": "17:00",
820
+ },
821
+ []
822
+ ]
823
+
824
+ ParsedErrorResponse = [
825
+ None,
826
+ [
827
+ {
828
+ "carrier_id": "{{id}}",
829
+ "carrier_name": "{{id}}",
830
+ "code": "pickup_error",
831
+ "message": "Unable to schedule pickup",
832
+ "details": {
833
+ "details": "Invalid pickup date provided"
834
+ }
835
+ }
836
+ ]
837
+ ]
838
+ '''
839
+ )