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,947 @@
1
+ """CARRIER EXTENSION TEMPLATES SECTION"""
2
+ from jinja2 import Template
3
+
4
+ README_TEMPLATE = Template("""# karrio.{{id}}
5
+
6
+ This package is a {{name}} extension of the [karrio](https://pypi.org/project/karrio) multi carrier shipping SDK.
7
+
8
+ ## Requirements
9
+
10
+ `Python 3.11+`
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pip install karrio.{{id}}
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```python
21
+ import karrio.sdk as karrio
22
+ from karrio.mappers.{{id}}.settings import Settings
23
+
24
+
25
+ # Initialize a carrier gateway
26
+ {{id}} = karrio.gateway["{{id}}"].create(
27
+ Settings(
28
+ ...
29
+ )
30
+ )
31
+ ```
32
+
33
+ Check the [Karrio Mutli-carrier SDK docs](https://docs.karrio.io) for Shipping API requests
34
+
35
+ """
36
+ )
37
+
38
+ PYPROJECT_TEMPLATE = Template('''[build-system]
39
+ requires = ["setuptools>=61.0"]
40
+ build-backend = "setuptools.build_meta"
41
+
42
+ [project]
43
+ name = "karrio_{{id}}"
44
+ version = "{{version}}"
45
+ description = "Karrio - {{name}} Shipping Extension"
46
+ readme = "README.md"
47
+ requires-python = ">=3.11"
48
+ license = "Apache-2.0"
49
+ authors = [
50
+ {name = "karrio", email = "hello@karrio.io"}
51
+ ]
52
+ classifiers = [
53
+ "Intended Audience :: Developers",
54
+ "Operating System :: OS Independent",
55
+ "Programming Language :: Python :: 3",
56
+ ]
57
+ dependencies = [
58
+ "karrio",
59
+ ]
60
+
61
+ [project.urls]
62
+ Homepage = "https://github.com/karrioapi/karrio"
63
+
64
+ [project.entry-points."karrio.plugins"]
65
+ {{id}} = "karrio.plugins.{{id}}"
66
+
67
+ [tool.setuptools]
68
+ zip-safe = false
69
+ include-package-data = true
70
+
71
+ [tool.setuptools.package-dir]
72
+ "" = "."
73
+
74
+ [tool.setuptools.packages.find]
75
+ exclude = ["tests.*", "tests"]
76
+ namespaces = true
77
+ ''')
78
+
79
+ SETUP_TEMPLATE = Template('''
80
+ """Warning: This setup.py is only there for git install until poetry support git subdirectory"""
81
+ from setuptools import setup, find_namespace_packages
82
+
83
+ with open("README.md", "r") as fh:
84
+ long_description = fh.read()
85
+
86
+ setup(
87
+ name="karrio.{{id}}",
88
+ version="{{version}}",
89
+ description="Karrio - {{name}} Shipping Extension",
90
+ long_description=long_description,
91
+ long_description_content_type="text/markdown",
92
+ url="https://github.com/karrioapi/karrio",
93
+ author="karrio",
94
+ author_email="hello@karrio.io",
95
+ license="Apache-2.0",
96
+ packages=find_namespace_packages(exclude=["tests.*", "tests"]),
97
+ install_requires=["karrio"],
98
+ classifiers=[
99
+ "Intended Audience :: Developers",
100
+ "Operating System :: OS Independent",
101
+ "Programming Language :: Python :: 3",
102
+ ],
103
+ zip_safe=False,
104
+ include_package_data=True,
105
+ )
106
+
107
+ '''
108
+ )
109
+
110
+ XML_GENERATE_TEMPLATE = Template("""SCHEMAS=./schemas
111
+ LIB_MODULES=./karrio/schemas/{{id}}
112
+ find "${LIB_MODULES}" -name "*.py" -exec rm -r {} \\;
113
+ touch "${LIB_MODULES}/__init__.py"
114
+
115
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/error.py" $SCHEMAS/error_response.xsd{% if "address" in features %}
116
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/address_validation_request.py" $SCHEMAS/address_validation_request.xsd
117
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/address_validation_response.py" $SCHEMAS/address_validation_response.xsd{% endif %}{% if "rating" in features %}
118
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/rate_request.py" $SCHEMAS/rate_request.xsd
119
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/rate_response.py" $SCHEMAS/rate_response.xsd{% endif %}{% if "pickup" in features %}
120
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/pickup_create_request.py" $SCHEMAS/pickup_create_request.xsd
121
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/pickup_create_response.py" $SCHEMAS/pickup_create_response.xsd
122
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/pickup_update_request.py" $SCHEMAS/pickup_update_request.xsd
123
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/pickup_update_response.py" $SCHEMAS/pickup_update_response.xsd
124
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/pickup_cancel_request.py" $SCHEMAS/pickup_cancel_request.xsd
125
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/pickup_cancel_response.py" $SCHEMAS/pickup_cancel_response.xsd{% endif %}{% if "manifest" in features %}
126
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/manifest_request.py" $SCHEMAS/manifest_request.xsd
127
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/manifest_response.py" $SCHEMAS/manifest_response.xsd{% endif %}{% if "shipping" in features %}
128
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/shipment_request.py" $SCHEMAS/shipment_request.xsd
129
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/shipment_response.py" $SCHEMAS/shipment_response.xsd{% endif %}{% if "tracking" in features %}
130
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/tracking_request.py" $SCHEMAS/tracking_request.xsd
131
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/tracking_response.py" $SCHEMAS/tracking_response.xsd{% endif %}{% if "document" in features %}
132
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/document_upload_request.py" $SCHEMAS/document_upload_request.xsd
133
+ generateDS --no-namespace-defs -o "${LIB_MODULES}/document_upload_response.py" $SCHEMAS/document_upload_response.xsd{% endif %}
134
+ """
135
+ )
136
+
137
+ JSON_GENERATE_TEMPLATE = Template("""SCHEMAS=./schemas
138
+ LIB_MODULES=./karrio/schemas/{{id}}
139
+ find "${LIB_MODULES}" -name "*.py" -exec rm -r {} \\;
140
+ touch "${LIB_MODULES}/__init__.py"
141
+
142
+ kcli codegen generate "${SCHEMAS}/error_response.json" "${LIB_MODULES}/error_response.py"{% if "address" in features %}
143
+ kcli codegen generate "${SCHEMAS}/address_validation_request.json" "${LIB_MODULES}/address_validation_request.py"
144
+ kcli codegen generate "${SCHEMAS}/address_validation_response.json" "${LIB_MODULES}/address_validation_response.py"{% endif %}{% if "rating" in features %}
145
+ kcli codegen generate "${SCHEMAS}/rate_request.json" "${LIB_MODULES}/rate_request.py"
146
+ kcli codegen generate "${SCHEMAS}/rate_response.json" "${LIB_MODULES}/rate_response.py"{% endif %}{% if "pickup" in features %}
147
+ kcli codegen generate "${SCHEMAS}/pickup_create_request.json" "${LIB_MODULES}/pickup_create_request.py"
148
+ kcli codegen generate "${SCHEMAS}/pickup_create_response.json" "${LIB_MODULES}/pickup_create_response.py"
149
+ kcli codegen generate "${SCHEMAS}/pickup_update_request.json" "${LIB_MODULES}/pickup_update_request.py"
150
+ kcli codegen generate "${SCHEMAS}/pickup_update_response.json" "${LIB_MODULES}/pickup_update_response.py"
151
+ kcli codegen generate "${SCHEMAS}/pickup_cancel_request.json" "${LIB_MODULES}/pickup_cancel_request.py"
152
+ kcli codegen generate "${SCHEMAS}/pickup_cancel_response.json" "${LIB_MODULES}/pickup_cancel_response.py"{% endif %}{% if "manifest" in features %}
153
+ kcli codegen generate "${SCHEMAS}/manifest_request.json" "${LIB_MODULES}/manifest_request.py"
154
+ kcli codegen generate "${SCHEMAS}/manifest_response.json" "${LIB_MODULES}/manifest_response.py"{% endif %}{% if "shipping" in features %}
155
+ kcli codegen generate "${SCHEMAS}/shipment_request.json" "${LIB_MODULES}/shipment_request.py"
156
+ kcli codegen generate "${SCHEMAS}/shipment_response.json" "${LIB_MODULES}/shipment_response.py"{% endif %}{% if "tracking" in features %}
157
+ kcli codegen generate "${SCHEMAS}/tracking_request.json" "${LIB_MODULES}/tracking_request.py"
158
+ kcli codegen generate "${SCHEMAS}/tracking_response.json" "${LIB_MODULES}/tracking_response.py"{% endif %}{% if "document" in features %}
159
+ kcli codegen generate "${SCHEMAS}/document_upload_request.json" "${LIB_MODULES}/document_upload_request.py"
160
+ kcli codegen generate "${SCHEMAS}/document_upload_response.json" "${LIB_MODULES}/document_upload_response.py"{% endif %}
161
+
162
+
163
+ """
164
+ )
165
+
166
+ MAPPER_IMPORTS_TEMPLATE = Template(
167
+ """from karrio.mappers.{{id}}.mapper import Mapper
168
+ from karrio.mappers.{{id}}.proxy import Proxy
169
+ from karrio.mappers.{{id}}.settings import Settings
170
+ """
171
+ )
172
+
173
+ MAPPER_METADATA_TEMPLATE = Template(
174
+ """from karrio.core.metadata import Metadata
175
+
176
+ from karrio.mappers.{{id}}.mapper import Mapper
177
+ from karrio.mappers.{{id}}.proxy import Proxy
178
+ from karrio.mappers.{{id}}.settings import Settings
179
+ import karrio.providers.{{id}}.units as units
180
+ import karrio.providers.{{id}}.utils as utils
181
+
182
+
183
+ METADATA = Metadata(
184
+ id="{{id}}",
185
+ label="{{name}}",
186
+ # Integrations
187
+ Mapper=Mapper,
188
+ Proxy=Proxy,
189
+ Settings=Settings,
190
+ # Data Units
191
+ is_hub=False,
192
+ # options=units.ShippingOption,
193
+ # services=units.ShippingService,
194
+ connection_configs=utils.ConnectionConfig,
195
+ )
196
+
197
+ """
198
+ )
199
+
200
+ PLUGIN_METADATA_TEMPLATE = Template(
201
+ """from karrio.core.metadata import PluginMetadata
202
+
203
+ from karrio.mappers.{{id}}.mapper import Mapper
204
+ from karrio.mappers.{{id}}.proxy import Proxy
205
+ from karrio.mappers.{{id}}.settings import Settings
206
+ import karrio.providers.{{id}}.units as units
207
+ import karrio.providers.{{id}}.utils as utils
208
+
209
+
210
+ # This METADATA object is used by Karrio to discover and register this plugin
211
+ # when loaded through Python entrypoints or local plugin directories.
212
+ # The entrypoint is defined in pyproject.toml under [project.entry-points."karrio.plugins"]
213
+ METADATA = PluginMetadata(
214
+ id="{{id}}",
215
+ label="{{name}}",
216
+ description="{{name}} shipping integration for Karrio",
217
+ # Integrations
218
+ Mapper=Mapper,
219
+ Proxy=Proxy,
220
+ Settings=Settings,
221
+ # Data Units
222
+ is_hub=False,
223
+ # options=units.ShippingOption,
224
+ # services=units.ShippingService,
225
+ connection_configs=utils.ConnectionConfig,
226
+ # Extra info
227
+ website="",
228
+ documentation="",
229
+ )
230
+
231
+ """
232
+ )
233
+
234
+ MAPPER_TEMPLATE = Template('''"""Karrio {{name}} client mapper."""
235
+
236
+ import typing
237
+ import karrio.lib as lib
238
+ import karrio.api.mapper as mapper
239
+ import karrio.core.models as models
240
+ import karrio.providers.{{id}} as provider
241
+ import karrio.mappers.{{id}}.settings as provider_settings
242
+
243
+
244
+ class Mapper(mapper.Mapper):
245
+ settings: provider_settings.Settings{% if "rating" in features %}
246
+
247
+ def create_rate_request(
248
+ self, payload: models.RateRequest
249
+ ) -> lib.Serializable:
250
+ return provider.rate_request(payload, self.settings)
251
+ {% endif %}{% if "tracking" in features %}
252
+ def create_tracking_request(
253
+ self, payload: models.TrackingRequest
254
+ ) -> lib.Serializable:
255
+ return provider.tracking_request(payload, self.settings)
256
+ {% endif %}{% if "shipping" in features %}
257
+ def create_shipment_request(
258
+ self, payload: models.ShipmentRequest
259
+ ) -> lib.Serializable:
260
+ return provider.shipment_request(payload, self.settings)
261
+ {% endif %}{% if "pickup" in features %}
262
+ def create_pickup_request(
263
+ self, payload: models.PickupRequest
264
+ ) -> lib.Serializable:
265
+ return provider.pickup_request(payload, self.settings)
266
+ {% endif %}{% if "pickup" in features %}
267
+ def create_pickup_update_request(
268
+ self, payload: models.PickupUpdateRequest
269
+ ) -> lib.Serializable:
270
+ return provider.pickup_update_request(payload, self.settings)
271
+ {% endif %}{% if "pickup" in features %}
272
+ def create_cancel_pickup_request(
273
+ self, payload: models.PickupCancelRequest
274
+ ) -> lib.Serializable:
275
+ return provider.pickup_cancel_request(payload, self.settings)
276
+ {% endif %}{% if "shipping" in features %}
277
+ def create_cancel_shipment_request(
278
+ self, payload: models.ShipmentCancelRequest
279
+ ) -> lib.Serializable[str]:
280
+ return provider.shipment_cancel_request(payload, self.settings)
281
+ {% endif %}{% if "document" in features %}
282
+ def create_document_upload_request(
283
+ self, payload: models.DocumentUploadRequest
284
+ ) -> lib.Serializable[str]:
285
+ return provider.document_upload_request(payload, self.settings)
286
+ {% endif %}{% if "address" in features %}
287
+ def create_address_validation_request(
288
+ self, payload: models.AddressValidationRequest
289
+ ) -> lib.Serializable:
290
+ return provider.address_validation_request(payload, self.settings)
291
+ {% endif %}{% if "manifest" in features %}
292
+ def create_manifest_request(
293
+ self, payload: models.ManifestRequest
294
+ ) -> lib.Serializable:
295
+ return provider.manifest_request(payload, self.settings)
296
+ {% endif %}
297
+ {% if "pickup" in features %}
298
+ def parse_cancel_pickup_response(
299
+ self, response: lib.Deserializable[str]
300
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
301
+ return provider.parse_pickup_cancel_response(response, self.settings)
302
+ {% endif %}{% if "shipping" in features %}
303
+ def parse_cancel_shipment_response(
304
+ self, response: lib.Deserializable[str]
305
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
306
+ return provider.parse_shipment_cancel_response(response, self.settings)
307
+ {% endif %}{% if "pickup" in features %}
308
+ def parse_pickup_response(
309
+ self, response: lib.Deserializable[str]
310
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
311
+ return provider.parse_pickup_response(response, self.settings)
312
+ {% endif %}{% if "pickup" in features %}
313
+ def parse_pickup_update_response(
314
+ self, response: lib.Deserializable[str]
315
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
316
+ return provider.parse_pickup_update_response(response, self.settings)
317
+ {% endif %}{% if "rating" in features %}
318
+ def parse_rate_response(
319
+ self, response: lib.Deserializable[str]
320
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
321
+ return provider.parse_rate_response(response, self.settings)
322
+ {% endif %}{% if "shipping" in features %}
323
+ def parse_shipment_response(
324
+ self, response: lib.Deserializable[str]
325
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
326
+ return provider.parse_shipment_response(response, self.settings)
327
+ {% endif %}{% if "tracking" in features %}
328
+ def parse_tracking_response(
329
+ self, response: lib.Deserializable[str]
330
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
331
+ return provider.parse_tracking_response(response, self.settings)
332
+ {% endif %}{% if "document" in features %}
333
+ def parse_document_upload_response(
334
+ self, response: lib.Deserializable[str]
335
+ ) -> typing.Tuple[models.DocumentUploadDetails, typing.List[models.Message]]:
336
+ return provider.parse_document_upload_response(response, self.settings)
337
+ {% endif %}{% if "manifest" in features %}
338
+ def parse_manifest_response(
339
+ self, response: lib.Deserializable[str]
340
+ ) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
341
+ return provider.parse_manifest_response(response, self.settings)
342
+ {% endif %}{% if "address" in features %}
343
+ def parse_address_validation_response(
344
+ self, response: lib.Deserializable[str]
345
+ ) -> typing.Tuple[typing.List[models.AddressValidationDetails], typing.List[models.Message]]:
346
+ return provider.parse_address_validation_response(response, self.settings)
347
+ {% endif %}
348
+
349
+ '''
350
+ )
351
+
352
+ MAPPER_PROXY_TEMPLATE = Template('''"""Karrio {{name}} client proxy."""
353
+
354
+ import karrio.lib as lib
355
+ import karrio.api.proxy as proxy
356
+ import karrio.mappers.{{id}}.settings as provider_settings
357
+
358
+ # IMPLEMENTATION INSTRUCTIONS:
359
+ # 1. Import the schema types specific to your carrier API
360
+ # 2. Uncomment and adapt the request examples below to work with your carrier API
361
+ # 3. Replace the stub responses with actual API calls once you've tested with the example data
362
+ # 4. Update URLs, headers, and authentication methods as required by your carrier API
363
+
364
+
365
+ class Proxy(proxy.Proxy):
366
+ settings: provider_settings.Settings{% if "rating" in features %}
367
+
368
+ def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
369
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
370
+ # ---------------------------------------------------------
371
+ # Example implementation:
372
+ # response = lib.request(
373
+ # url=f"{self.settings.server_url}/rates",
374
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
375
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
376
+ # method="POST",
377
+ # headers={
378
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
379
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
380
+ # },
381
+ # )
382
+
383
+ # DEVELOPMENT ONLY: Remove this stub response and uncomment the API call above when implementing the real carrier API
384
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
385
+
386
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
387
+ {% endif %}{% if "shipping" in features %}
388
+ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
389
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
390
+ # ---------------------------------------------------------
391
+ # Example implementation:
392
+ # response = lib.request(
393
+ # url=f"{self.settings.server_url}/shipments",
394
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
395
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
396
+ # method="POST",
397
+ # headers={
398
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
399
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
400
+ # },
401
+ # )
402
+
403
+ # DEVELOPMENT ONLY: Remove this stub response and uncomment the API call above when implementing the real carrier API
404
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
405
+
406
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
407
+ {% endif %}{% if "shipping" in features %}
408
+ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
409
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
410
+ # ---------------------------------------------------------
411
+ # Example implementation:
412
+ # response = lib.request(
413
+ # url=f"{self.settings.server_url}/shipments/cancel",
414
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
415
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
416
+ # method="POST",
417
+ # headers={
418
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
419
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
420
+ # },
421
+ # )
422
+
423
+ # DEVELOPMENT ONLY: Remove this stub response and uncomment the API call above when implementing the real carrier API
424
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
425
+
426
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
427
+ {% endif %}{% if "tracking" in features %}
428
+ def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
429
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
430
+ # ---------------------------------------------------------
431
+ # Example implementation:
432
+ # response = lib.request(
433
+ # url=f"{self.settings.server_url}/tracking",
434
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
435
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
436
+ # method="POST",
437
+ # headers={
438
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
439
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
440
+ # },
441
+ # )
442
+
443
+ # DEVELOPMENT ONLY: Remove this stub response and uncomment the API call above when implementing the real carrier API
444
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
445
+
446
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
447
+ {% endif %}{% if "pickup" in features %}
448
+ def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
449
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
450
+ # ---------------------------------------------------------
451
+ # Example implementation:
452
+ # response = lib.request(
453
+ # url=f"{self.settings.server_url}/pickups",
454
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
455
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
456
+ # method="POST",
457
+ # headers={
458
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
459
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
460
+ # },
461
+ # )
462
+
463
+ # DEVELOPMENT ONLY: Remove this stub response and uncomment the API call above when implementing the real carrier API
464
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
465
+
466
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
467
+ {% endif %}{% if "pickup" in features %}
468
+ def modify_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
469
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
470
+ # ---------------------------------------------------------
471
+ # Example implementation:
472
+ # response = lib.request(
473
+ # url=f"{self.settings.server_url}/pickups/modify",
474
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
475
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
476
+ # method="POST",
477
+ # headers={
478
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
479
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
480
+ # },
481
+ # )
482
+
483
+ # DEVELOPMENT ONLY: Remove this stub response and uncomment the API call above when implementing the real carrier API
484
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
485
+
486
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
487
+ {% endif %}{% if "pickup" in features %}
488
+ def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
489
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
490
+ # ---------------------------------------------------------
491
+ # Example implementation:
492
+ # response = lib.request(
493
+ # url=f"{self.settings.server_url}/pickups/cancel",
494
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
495
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
496
+ # method="POST",
497
+ # headers={
498
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
499
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
500
+ # },
501
+ # )
502
+
503
+ # During development, use stub response from schema examples
504
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
505
+
506
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
507
+ {% endif %}{% if "address" in features %}
508
+ def validate_address(self, request: lib.Serializable) -> lib.Deserializable[str]:
509
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
510
+ # ---------------------------------------------------------
511
+ # Example implementation:
512
+ # response = lib.request(
513
+ # url=f"{self.settings.server_url}/address/validate",
514
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
515
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
516
+ # method="POST",
517
+ # headers={
518
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
519
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
520
+ # },
521
+ # )
522
+
523
+ # During development, use stub response from schema examples
524
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
525
+
526
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
527
+ {% endif %}{% if "document" in features %}
528
+ def upload_document(self, request: lib.Serializable) -> lib.Deserializable[str]:
529
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
530
+ # ---------------------------------------------------------
531
+ # Example implementation:
532
+ # response = lib.request(
533
+ # url=f"{self.settings.server_url}/documents",
534
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
535
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
536
+ # method="POST",
537
+ # headers={
538
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
539
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
540
+ # },
541
+ # )
542
+
543
+ # During development, use stub response from schema examples
544
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
545
+
546
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
547
+ {% endif %}{% if "manifest" in features %}
548
+ def create_manifest(self, request: lib.Serializable) -> lib.Deserializable[str]:
549
+ # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION
550
+ # ---------------------------------------------------------
551
+ # Example implementation:
552
+ # response = lib.request(
553
+ # url=f"{self.settings.server_url}/manifests",
554
+ # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %},
555
+ # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}),
556
+ # method="POST",
557
+ # headers={
558
+ # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %},
559
+ # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %}
560
+ # },
561
+ # )
562
+
563
+ # During development, use stub response from schema examples
564
+ {% if is_xml_api %}response = '<r></r>'{% else %}response = lib.to_json({}){% endif %}
565
+
566
+ return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %})
567
+ {% endif %}
568
+ '''
569
+ )
570
+
571
+ MAPPER_SETTINGS_TEMPLATE = Template('''"""Karrio {{name}} client settings."""
572
+
573
+ import attr
574
+ import karrio.providers.{{id}}.utils as provider_utils
575
+
576
+
577
+ @attr.s(auto_attribs=True)
578
+ class Settings(provider_utils.Settings):
579
+ """{{name}} connection settings."""
580
+
581
+ # Add carrier specific API connection properties here
582
+ {% if is_xml_api %}username: str
583
+ password: str
584
+ account_number: str = None{% else %}api_key: str
585
+ account_number: str = None{% endif %}
586
+
587
+ # generic properties
588
+ id: str = None
589
+ test_mode: bool = False
590
+ carrier_id: str = "{{id}}"
591
+ account_country_code: str = None
592
+ metadata: dict = {}
593
+ config: dict = {}
594
+
595
+ '''
596
+ )
597
+
598
+ PROVIDER_IMPORTS_TEMPLATE = Template(
599
+ '''"""Karrio {{name}} provider imports."""
600
+ from karrio.providers.{{id}}.utils import Settings{% if "rating" in features %}
601
+ from karrio.providers.{{id}}.rate import (
602
+ parse_rate_response,
603
+ rate_request,
604
+ ){% endif %}{% if "shipping" in features %}
605
+ from karrio.providers.{{id}}.shipment import (
606
+ parse_shipment_cancel_response,
607
+ parse_shipment_response,
608
+ shipment_cancel_request,
609
+ shipment_request,
610
+ ){% endif %}{% if "pickup" in features %}
611
+ from karrio.providers.{{id}}.pickup import (
612
+ parse_pickup_cancel_response,
613
+ parse_pickup_update_response,
614
+ parse_pickup_response,
615
+ pickup_update_request,
616
+ pickup_cancel_request,
617
+ pickup_request,
618
+ ){% endif %}{% if "tracking" in features %}
619
+ from karrio.providers.{{id}}.tracking import (
620
+ parse_tracking_response,
621
+ tracking_request,
622
+ ){% endif %}{% if "address" in features %}
623
+ from karrio.providers.{{id}}.address import (
624
+ parse_address_validation_response,
625
+ address_validation_request,
626
+ ){% endif %}{% if "document" in features %}
627
+ from karrio.providers.{{id}}.document import (
628
+ parse_document_upload_response,
629
+ document_upload_request,
630
+ ){% endif %}{% if "manifest" in features %}
631
+ from karrio.providers.{{id}}.manifest import (
632
+ parse_manifest_response,
633
+ manifest_request,
634
+ )
635
+ {% endif %}
636
+ '''
637
+ )
638
+
639
+ PROVIDER_ERROR_TEMPLATE = Template('''"""Karrio {{name}} error parser."""
640
+
641
+ import typing
642
+ import karrio.lib as lib
643
+ import karrio.core.models as models
644
+ import karrio.providers.{{id}}.utils as provider_utils
645
+
646
+
647
+ def parse_error_response(
648
+ response: {% if is_xml_api %}lib.Element{% else %}dict{% endif %},
649
+ settings: provider_utils.Settings,
650
+ **kwargs,
651
+ ) -> typing.List[models.Message]:
652
+ errors: list = [] # compute the carrier error object list
653
+
654
+ return [
655
+ models.Message(
656
+ carrier_id=settings.carrier_id,
657
+ carrier_name=settings.carrier_name,
658
+ code="",
659
+ message="",
660
+ details={**kwargs},
661
+ )
662
+ for error in errors
663
+ ]
664
+
665
+ '''
666
+ )
667
+
668
+ PROVIDER_UNITS_TEMPLATE = Template('''
669
+ import karrio.lib as lib
670
+ import karrio.core.units as units
671
+
672
+
673
+ class PackagingType(lib.StrEnum):
674
+ """ Carrier specific packaging type """
675
+ PACKAGE = "PACKAGE"
676
+
677
+ """ Unified Packaging type mapping """
678
+ envelope = PACKAGE
679
+ pak = PACKAGE
680
+ tube = PACKAGE
681
+ pallet = PACKAGE
682
+ small_box = PACKAGE
683
+ medium_box = PACKAGE
684
+ your_packaging = PACKAGE
685
+
686
+
687
+ class ShippingService(lib.StrEnum):
688
+ """ Carrier specific services """
689
+ {{id}}_standard_service = "{{name}} Standard Service"
690
+
691
+
692
+ class ShippingOption(lib.Enum):
693
+ """ Carrier specific options """
694
+ # {{id}}_option = lib.OptionEnum("code")
695
+
696
+ """ Unified Option type mapping """
697
+ # insurance = {{id}}_coverage # maps unified karrio option to carrier specific
698
+
699
+ pass
700
+
701
+
702
+ def shipping_options_initializer(
703
+ options: dict,
704
+ package_options: units.ShippingOptions = None,
705
+ ) -> units.ShippingOptions:
706
+ """
707
+ Apply default values to the given options.
708
+ """
709
+
710
+ if package_options is not None:
711
+ options.update(package_options.content)
712
+
713
+ def items_filter(key: str) -> bool:
714
+ return key in ShippingOption # type: ignore
715
+
716
+ return units.ShippingOptions(options, ShippingOption, items_filter=items_filter)
717
+
718
+
719
+ class TrackingStatus(lib.Enum):
720
+ on_hold = ["on_hold"]
721
+ delivered = ["delivered"]
722
+ in_transit = ["in_transit"]
723
+ delivery_failed = ["delivery_failed"]
724
+ delivery_delayed = ["delivery_delayed"]
725
+ out_for_delivery = ["out_for_delivery"]
726
+ ready_for_pickup = ["ready_for_pickup"]
727
+
728
+ '''
729
+ )
730
+
731
+ PROVIDER_UTILS_TEMPLATE = Template('''
732
+ import base64
733
+ import datetime
734
+ import karrio.lib as lib
735
+ import karrio.core as core
736
+ import karrio.core.errors as errors
737
+
738
+
739
+ class Settings(core.Settings):
740
+ """{{name}} connection settings."""
741
+
742
+ # Add carrier specific api connection properties here
743
+ {% if is_xml_api %}username: str
744
+ password: str
745
+ account_number: str = None{% else %}api_key: str
746
+ account_number: str = None{% endif %}
747
+
748
+ @property
749
+ def carrier_name(self):
750
+ return "{{id}}"
751
+
752
+ @property
753
+ def server_url(self):
754
+ return (
755
+ "https://carrier.api"
756
+ if self.test_mode
757
+ else "https://sandbox.carrier.api"
758
+ )
759
+
760
+ # """uncomment the following code block to expose a carrier tracking url."""
761
+ # @property
762
+ # def tracking_url(self):
763
+ # return "https://www.carrier.com/tracking?tracking-id={}"
764
+
765
+ {% if is_xml_api %}@property
766
+ def authorization(self):
767
+ pair = "%s:%s" % (self.username, self.password)
768
+ return base64.b64encode(pair.encode("utf-8")).decode("ascii"){% endif %}
769
+
770
+ @property
771
+ def connection_config(self) -> lib.units.Options:
772
+ return lib.to_connection_config(
773
+ self.config or {},
774
+ option_type=ConnectionConfig,
775
+ )
776
+
777
+ # """uncomment the following code block to implement the oauth login."""
778
+ # @property
779
+ # def access_token(self):
780
+ # """Retrieve the access_token using the client_id|client_secret pair
781
+ # or collect it from the cache if an unexpired access_token exist.
782
+ # """
783
+ # cache_key = f"{self.carrier_name}|{self.client_id}|{self.client_secret}"
784
+ # now = datetime.datetime.now() + datetime.timedelta(minutes=30)
785
+
786
+ # auth = self.connection_cache.get(cache_key) or {}
787
+ # token = auth.get("access_token")
788
+ # expiry = lib.to_date(auth.get("expiry"), current_format="%Y-%m-%d %H:%M:%S")
789
+
790
+ # if token is not None and expiry is not None and expiry > now:
791
+ # return token
792
+
793
+ # self.connection_cache.set(cache_key, lambda: login(self))
794
+ # new_auth = self.connection_cache.get(cache_key)
795
+
796
+ # return new_auth["access_token"]
797
+
798
+ # """uncomment the following code block to implement the oauth login."""
799
+ # def login(settings: Settings):
800
+ # import karrio.providers.{{id}}.error as error
801
+
802
+ # result = lib.request(
803
+ # url=f"{settings.server_url}/oauth/token",
804
+ # method="POST",
805
+ # headers={"content-Type": "application/x-www-form-urlencoded"},
806
+ # data=lib.to_query_string(
807
+ # dict(
808
+ # grant_type="client_credentials",
809
+ # client_id=settings.client_id,
810
+ # client_secret=settings.client_secret,
811
+ # )
812
+ # ),
813
+ # )
814
+
815
+ # response = lib.to_dict(result)
816
+ # messages = error.parse_error_response(response, settings)
817
+
818
+ # if any(messages):
819
+ # raise errors.ParsedMessagesError(messages)
820
+
821
+ # expiry = datetime.datetime.now() + datetime.timedelta(
822
+ # seconds=float(response.get("expires_in", 0))
823
+ # )
824
+ # return {**response, "expiry": lib.fdatetime(expiry)}
825
+
826
+
827
+ class ConnectionConfig(lib.Enum):
828
+ shipping_options = lib.OptionEnum("shipping_options", list)
829
+ shipping_services = lib.OptionEnum("shipping_services", list)
830
+ label_type = lib.OptionEnum("label_type", str, "PDF") # Example of label type config with PDF default
831
+
832
+ '''
833
+ )
834
+
835
+
836
+ TEST_FIXTURE_TEMPLATE = Template(
837
+ '''"""{{name}} carrier tests fixtures."""
838
+
839
+ import karrio.sdk as karrio
840
+
841
+
842
+ gateway = karrio.gateway["{{id}}"].create(
843
+ dict(
844
+ id="123456789",
845
+ test_mode=True,
846
+ carrier_id="{{id}}",
847
+ account_number="123456789",
848
+ {% if is_xml_api %}username="username",
849
+ password="password",{% else %}api_key="TEST_API_KEY",{% endif %}
850
+ )
851
+ )
852
+ '''
853
+ )
854
+
855
+ TEST_IMPORTS_TEMPLATE = Template(
856
+ """{% if "rating" in features %}
857
+ from {{id}}.test_rate import *{% endif %}{% if "pickup" in features %}
858
+ from {{id}}.test_pickup import *{% endif %}{% if "address" in features %}
859
+ from {{id}}.test_address import *{% endif %}{% if "tracking" in features %}
860
+ from {{id}}.test_tracking import *{% endif %}{% if "shipping" in features %}
861
+ from {{id}}.test_shipment import *{% endif %}{% if "document" in features %}
862
+ from {{id}}.test_document import *{% endif %}{% if "manifest" in features %}
863
+ from {{id}}.test_manifest import *
864
+ {% endif %}
865
+ """
866
+ )
867
+
868
+ TEST_PROVIDER_IMPORTS_TEMPLATE = Template(
869
+ """from .fixture import gateway
870
+ """
871
+ )
872
+
873
+ XML_SCHEMA_ERROR_TEMPLATE = Template(
874
+ """<?xml version="1.0"?>
875
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://{{id}}.com/ws/error" xmlns="http://{{id}}.com/ws/error" elementFormDefault="qualified">
876
+ <xsd:element name="error-response">
877
+ <xsd:complexType>
878
+ <xsd:sequence>
879
+ <xsd:element name="error" maxOccurs="unbounded">
880
+ <xsd:complexType>
881
+ <xsd:all>
882
+ <xsd:element name="code" type="xsd:string" />
883
+ <xsd:element name="message" type="xsd:string" />
884
+ <xsd:element name="details" type="xsd:string" minOccurs="0" />
885
+ </xsd:all>
886
+ </xsd:complexType>
887
+ </xsd:element>
888
+ </xsd:sequence>
889
+ </xsd:complexType>
890
+ </xsd:element>
891
+ </xsd:schema>
892
+ """
893
+ )
894
+
895
+ JSON_SCHEMA_ERROR_TEMPLATE = Template(
896
+ """{
897
+ "errorResponse": {
898
+ "errors": [
899
+ {
900
+ "code": "ERROR_CODE",
901
+ "message": "Error message description",
902
+ "details": "Additional error details"
903
+ }
904
+ ]
905
+ }
906
+ }
907
+ """
908
+ )
909
+
910
+ VALIDATOR_IMPORTS_TEMPLATE = Template(
911
+ """from karrio.validators.{{id}}.validator import Validator
912
+ """
913
+ )
914
+
915
+ VALIDATOR_TEMPLATE = Template('''"""{{name}} address validator."""
916
+
917
+ import typing
918
+ import karrio.lib as lib
919
+ import karrio.api.validator as validator
920
+ import karrio.core.models as models
921
+ import karrio.providers.{{id}} as provider
922
+ from karrio.providers.{{id}}.utils import Settings
923
+
924
+
925
+ class Validator(validator.Validator):
926
+ """{{name}} address validator."""
927
+
928
+ def __init__(self, settings: Settings):
929
+ self.settings = settings
930
+
931
+ def validate_address(self, payload: models.Address) -> models.AddressValidation:
932
+ """
933
+ Validate a shipping address using {{name}}'s API.
934
+
935
+ Args:
936
+ payload: The address to validate
937
+
938
+ Returns:
939
+ AddressValidation object with validation results
940
+ """
941
+ request = provider.address_validation_request(payload, self.settings)
942
+ response = provider.validation_call(request, self.settings)
943
+ result = provider.parse_address_validation_response(response, self.settings)
944
+
945
+ return result
946
+ '''
947
+ )