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.
- karrio_cli/__init__.py +0 -0
- karrio_cli/__main__.py +105 -0
- karrio_cli/ai/README.md +335 -0
- karrio_cli/ai/__init__.py +0 -0
- karrio_cli/ai/commands.py +102 -0
- karrio_cli/ai/karrio_ai/__init__.py +1 -0
- karrio_cli/ai/karrio_ai/agent.py +972 -0
- karrio_cli/ai/karrio_ai/architecture/INTEGRATION_AGENT_PROMPT.md +497 -0
- karrio_cli/ai/karrio_ai/architecture/MAPPING_AGENT_PROMPT.md +355 -0
- karrio_cli/ai/karrio_ai/architecture/REAL_WORLD_TESTING.md +305 -0
- karrio_cli/ai/karrio_ai/architecture/SCHEMA_AGENT_PROMPT.md +183 -0
- karrio_cli/ai/karrio_ai/architecture/TESTING_AGENT_PROMPT.md +448 -0
- karrio_cli/ai/karrio_ai/architecture/TESTING_GUIDE.md +271 -0
- karrio_cli/ai/karrio_ai/enhanced_tools.py +943 -0
- karrio_cli/ai/karrio_ai/rag_system.py +503 -0
- karrio_cli/ai/karrio_ai/tests/test_agent.py +350 -0
- karrio_cli/ai/karrio_ai/tests/test_real_integration.py +360 -0
- karrio_cli/ai/karrio_ai/tests/test_real_world_scenarios.py +513 -0
- karrio_cli/commands/__init__.py +0 -0
- karrio_cli/commands/codegen.py +336 -0
- karrio_cli/commands/login.py +139 -0
- karrio_cli/commands/plugins.py +168 -0
- karrio_cli/commands/sdk.py +870 -0
- karrio_cli/common/queries.py +101 -0
- karrio_cli/common/utils.py +368 -0
- karrio_cli/resources/__init__.py +0 -0
- karrio_cli/resources/carriers.py +91 -0
- karrio_cli/resources/connections.py +207 -0
- karrio_cli/resources/events.py +151 -0
- karrio_cli/resources/logs.py +151 -0
- karrio_cli/resources/orders.py +144 -0
- karrio_cli/resources/shipments.py +210 -0
- karrio_cli/resources/trackers.py +287 -0
- karrio_cli/templates/__init__.py +9 -0
- karrio_cli/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/address.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/address.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/docs.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/docs.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/documents.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/documents.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/manifest.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/manifest.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/pickup.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/pickup.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/rates.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/rates.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/sdk.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/sdk.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/shipments.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/shipments.cpython-312.pyc +0 -0
- karrio_cli/templates/__pycache__/tracking.cpython-311.pyc +0 -0
- karrio_cli/templates/__pycache__/tracking.cpython-312.pyc +0 -0
- karrio_cli/templates/address.py +308 -0
- karrio_cli/templates/docs.py +150 -0
- karrio_cli/templates/documents.py +428 -0
- karrio_cli/templates/manifest.py +396 -0
- karrio_cli/templates/pickup.py +839 -0
- karrio_cli/templates/rates.py +638 -0
- karrio_cli/templates/sdk.py +947 -0
- karrio_cli/templates/shipments.py +892 -0
- karrio_cli/templates/tracking.py +437 -0
- karrio_cli-2025.5rc3.dist-info/METADATA +165 -0
- karrio_cli-2025.5rc3.dist-info/RECORD +68 -0
- karrio_cli-2025.5rc3.dist-info/WHEEL +5 -0
- karrio_cli-2025.5rc3.dist-info/entry_points.txt +2 -0
- 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
|
+
)
|