karrio-server-core 2025.5rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of karrio-server-core might be problematic. Click here for more details.
- karrio/server/conf.py +54 -0
- karrio/server/core/__init__.py +3 -0
- karrio/server/core/admin.py +1 -0
- karrio/server/core/apps.py +10 -0
- karrio/server/core/authentication.py +313 -0
- karrio/server/core/context_processors.py +12 -0
- karrio/server/core/datatypes.py +369 -0
- karrio/server/core/dataunits.py +156 -0
- karrio/server/core/exceptions.py +200 -0
- karrio/server/core/fields.py +12 -0
- karrio/server/core/filters.py +823 -0
- karrio/server/core/gateway.py +720 -0
- karrio/server/core/management/commands/cli.py +19 -0
- karrio/server/core/management/commands/create_oauth_client.py +41 -0
- karrio/server/core/middleware.py +95 -0
- karrio/server/core/migrations/0001_initial.py +28 -0
- karrio/server/core/migrations/0002_apilogindex.py +69 -0
- karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
- karrio/server/core/migrations/0004_metafield.py +74 -0
- karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
- karrio/server/core/migrations/__init__.py +0 -0
- karrio/server/core/models/__init__.py +48 -0
- karrio/server/core/models/base.py +70 -0
- karrio/server/core/models/entity.py +22 -0
- karrio/server/core/models/metafield.py +144 -0
- karrio/server/core/models/third_party.py +21 -0
- karrio/server/core/oauth_validators.py +171 -0
- karrio/server/core/permissions.py +37 -0
- karrio/server/core/renderers.py +11 -0
- karrio/server/core/router.py +3 -0
- karrio/server/core/serializers.py +1898 -0
- karrio/server/core/signals.py +57 -0
- karrio/server/core/tests.py +98 -0
- karrio/server/core/urls.py +12 -0
- karrio/server/core/utils.py +479 -0
- karrio/server/core/validators.py +416 -0
- karrio/server/core/views/__init__.py +2 -0
- karrio/server/core/views/api.py +133 -0
- karrio/server/core/views/metadata.py +44 -0
- karrio/server/core/views/oauth.py +74 -0
- karrio/server/core/views/references.py +82 -0
- karrio/server/core/views/schema.py +310 -0
- karrio/server/filters/__init__.py +2 -0
- karrio/server/filters/abstract.py +26 -0
- karrio/server/iam/__init__.py +0 -0
- karrio/server/iam/admin.py +3 -0
- karrio/server/iam/apps.py +21 -0
- karrio/server/iam/migrations/0001_initial.py +33 -0
- karrio/server/iam/migrations/__init__.py +0 -0
- karrio/server/iam/models.py +48 -0
- karrio/server/iam/permissions.py +134 -0
- karrio/server/iam/serializers.py +39 -0
- karrio/server/iam/signals.py +20 -0
- karrio/server/iam/tests.py +3 -0
- karrio/server/iam/views.py +3 -0
- karrio/server/openapi.py +75 -0
- karrio/server/providers/__init__.py +1 -0
- karrio/server/providers/admin.py +364 -0
- karrio/server/providers/apps.py +10 -0
- karrio/server/providers/extension/__init__.py +1 -0
- karrio/server/providers/extension/models/__init__.py +1 -0
- karrio/server/providers/extension/models/allied_express.py +22 -0
- karrio/server/providers/extension/models/allied_express_local.py +22 -0
- karrio/server/providers/extension/models/amazon_shipping.py +27 -0
- karrio/server/providers/extension/models/aramex.py +25 -0
- karrio/server/providers/extension/models/asendia_us.py +21 -0
- karrio/server/providers/extension/models/australiapost.py +20 -0
- karrio/server/providers/extension/models/boxknight.py +19 -0
- karrio/server/providers/extension/models/bpost.py +21 -0
- karrio/server/providers/extension/models/canadapost.py +21 -0
- karrio/server/providers/extension/models/canpar.py +19 -0
- karrio/server/providers/extension/models/chronopost.py +22 -0
- karrio/server/providers/extension/models/colissimo.py +22 -0
- karrio/server/providers/extension/models/dhl_express.py +23 -0
- karrio/server/providers/extension/models/dhl_parcel_de.py +25 -0
- karrio/server/providers/extension/models/dhl_poland.py +22 -0
- karrio/server/providers/extension/models/dhl_universal.py +19 -0
- karrio/server/providers/extension/models/dicom.py +20 -0
- karrio/server/providers/extension/models/dpd.py +37 -0
- karrio/server/providers/extension/models/dpdhl.py +26 -0
- karrio/server/providers/extension/models/easypost.py +20 -0
- karrio/server/providers/extension/models/eshipper.py +21 -0
- karrio/server/providers/extension/models/fedex.py +25 -0
- karrio/server/providers/extension/models/fedex_ws.py +24 -0
- karrio/server/providers/extension/models/freightcom.py +21 -0
- karrio/server/providers/extension/models/generic.py +35 -0
- karrio/server/providers/extension/models/geodis.py +22 -0
- karrio/server/providers/extension/models/hay_post.py +22 -0
- karrio/server/providers/extension/models/laposte.py +19 -0
- karrio/server/providers/extension/models/locate2u.py +22 -0
- karrio/server/providers/extension/models/nationex.py +22 -0
- karrio/server/providers/extension/models/purolator.py +21 -0
- karrio/server/providers/extension/models/roadie.py +18 -0
- karrio/server/providers/extension/models/royalmail.py +19 -0
- karrio/server/providers/extension/models/sendle.py +22 -0
- karrio/server/providers/extension/models/tge.py +63 -0
- karrio/server/providers/extension/models/tnt.py +23 -0
- karrio/server/providers/extension/models/ups.py +23 -0
- karrio/server/providers/extension/models/usps.py +23 -0
- karrio/server/providers/extension/models/usps_international.py +23 -0
- karrio/server/providers/extension/models/usps_wt.py +24 -0
- karrio/server/providers/extension/models/usps_wt_international.py +24 -0
- karrio/server/providers/extension/models/zoom2u.py +23 -0
- karrio/server/providers/migrations/0001_initial.py +140 -0
- karrio/server/providers/migrations/0002_carrier_active.py +18 -0
- karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
- karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
- karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
- karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
- karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
- karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
- karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
- karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
- karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
- karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
- karrio/server/providers/migrations/0013_tntsettings.py +30 -0
- karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
- karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
- karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
- karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
- karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
- karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
- karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
- karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
- karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
- karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
- karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
- karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
- karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
- karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
- karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
- karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
- karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
- karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
- karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
- karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
- karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
- karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
- karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
- karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
- karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
- karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
- karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
- karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
- karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
- karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
- karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
- karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
- karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
- karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
- karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
- karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
- karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
- karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
- karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
- karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
- karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
- karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
- karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
- karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
- karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
- karrio/server/providers/migrations/0059_ratesheet.py +81 -0
- karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
- karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
- karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
- karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
- karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
- karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
- karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
- karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
- karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
- karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
- karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
- karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
- karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
- karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
- karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
- karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
- karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
- karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
- karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
- karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
- karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
- karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -0
- karrio/server/providers/migrations/__init__.py +0 -0
- karrio/server/providers/models/__init__.py +17 -0
- karrio/server/providers/models/carrier.py +309 -0
- karrio/server/providers/models/config.py +30 -0
- karrio/server/providers/models/service.py +62 -0
- karrio/server/providers/models/sheet.py +60 -0
- karrio/server/providers/models/template.py +39 -0
- karrio/server/providers/models/utils.py +58 -0
- karrio/server/providers/router.py +3 -0
- karrio/server/providers/serializers/__init__.py +3 -0
- karrio/server/providers/serializers/base.py +277 -0
- karrio/server/providers/signals.py +27 -0
- karrio/server/providers/tests.py +3 -0
- karrio/server/providers/urls.py +11 -0
- karrio/server/providers/views/__init__.py +0 -0
- karrio/server/providers/views/carriers.py +269 -0
- karrio/server/providers/views/connections.py +176 -0
- karrio/server/samples.py +352 -0
- karrio/server/serializers/__init__.py +2 -0
- karrio/server/serializers/abstract.py +506 -0
- karrio/server/tracing/__init__.py +0 -0
- karrio/server/tracing/admin.py +63 -0
- karrio/server/tracing/apps.py +8 -0
- karrio/server/tracing/migrations/0001_initial.py +41 -0
- karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
- karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
- karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
- karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
- karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
- karrio/server/tracing/migrations/__init__.py +0 -0
- karrio/server/tracing/models.py +80 -0
- karrio/server/tracing/tests.py +3 -0
- karrio/server/tracing/utils.py +112 -0
- karrio/server/user/__init__.py +0 -0
- karrio/server/user/admin.py +96 -0
- karrio/server/user/apps.py +7 -0
- karrio/server/user/forms.py +35 -0
- karrio/server/user/migrations/0001_initial.py +41 -0
- karrio/server/user/migrations/0002_token.py +29 -0
- karrio/server/user/migrations/0003_token_test_mode.py +20 -0
- karrio/server/user/migrations/0004_group.py +26 -0
- karrio/server/user/migrations/0005_token_label.py +21 -0
- karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
- karrio/server/user/migrations/__init__.py +0 -0
- karrio/server/user/models.py +203 -0
- karrio/server/user/serializers.py +46 -0
- karrio/server/user/templates/registration/login.html +108 -0
- karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
- karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
- karrio/server/user/tests.py +3 -0
- karrio/server/user/urls.py +10 -0
- karrio/server/user/utils.py +60 -0
- karrio/server/user/views.py +9 -0
- karrio_server_core-2025.5rc1.dist-info/METADATA +32 -0
- karrio_server_core-2025.5rc1.dist-info/RECORD +241 -0
- karrio_server_core-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_server_core-2025.5rc1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import typing
|
|
3
|
+
import logging
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
from django.db.models import Q
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from rest_framework import status
|
|
9
|
+
from rest_framework.exceptions import NotFound
|
|
10
|
+
|
|
11
|
+
import karrio.lib as lib
|
|
12
|
+
import karrio.sdk as karrio
|
|
13
|
+
import karrio.server.core.utils as utils
|
|
14
|
+
import karrio.server.core.models as core
|
|
15
|
+
import karrio.server.core.datatypes as datatypes
|
|
16
|
+
import karrio.server.core.dataunits as dataunits
|
|
17
|
+
import karrio.server.core.validators as validators
|
|
18
|
+
import karrio.server.core.exceptions as exceptions
|
|
19
|
+
import karrio.server.providers.models as providers
|
|
20
|
+
import karrio.server.serializers as base_serializers
|
|
21
|
+
import karrio.server.core.serializers as serializers
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Carriers:
|
|
27
|
+
@staticmethod
|
|
28
|
+
def list(context=None, **kwargs) -> typing.List[providers.Carrier]:
|
|
29
|
+
list_filter = kwargs.copy()
|
|
30
|
+
user_filter = core.get_access_filter(context) if context is not None else []
|
|
31
|
+
|
|
32
|
+
test_mode = list_filter.get("test_mode") or getattr(context, "test_mode", None)
|
|
33
|
+
system_only = list_filter.get("system_only") is True
|
|
34
|
+
active_key = lib.identity(
|
|
35
|
+
"active_orgs__id" if settings.MULTI_ORGANIZATIONS else "active_users__id"
|
|
36
|
+
)
|
|
37
|
+
access_id = getattr(
|
|
38
|
+
getattr(context, "org" if settings.MULTI_ORGANIZATIONS else "user", None),
|
|
39
|
+
"id",
|
|
40
|
+
None,
|
|
41
|
+
)
|
|
42
|
+
creator_filter = lib.identity(
|
|
43
|
+
Q(
|
|
44
|
+
created_by__id=context.user.id,
|
|
45
|
+
**(dict(org=None) if settings.MULTI_ORGANIZATIONS else {}),
|
|
46
|
+
)
|
|
47
|
+
if getattr(context, "user", None) is not None
|
|
48
|
+
else Q()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
_user_carriers = providers.Carrier.user_carriers.filter(
|
|
52
|
+
user_filter if len(user_filter) > 0 else Q() | creator_filter
|
|
53
|
+
)
|
|
54
|
+
_system_carriers = providers.Carrier.system_carriers.filter(
|
|
55
|
+
Q(
|
|
56
|
+
**{
|
|
57
|
+
"active": True,
|
|
58
|
+
**({active_key: access_id} if access_id is not None else {}),
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
_queryset = lib.identity(
|
|
63
|
+
_system_carriers if system_only else _user_carriers | _system_carriers
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Check if the test filter is specified then set it otherwise return all carriers live and test mode
|
|
67
|
+
if test_mode is not None:
|
|
68
|
+
_queryset = _queryset.filter(test_mode=test_mode)
|
|
69
|
+
|
|
70
|
+
# Check if the active flag is specified and return all active carrier is active is not set to false
|
|
71
|
+
if list_filter.get("active") is not None:
|
|
72
|
+
active = False if list_filter["active"] is False else True
|
|
73
|
+
_queryset = _queryset.filter(Q(active=active))
|
|
74
|
+
|
|
75
|
+
# Check if a specific carrier_id is provided, to add it to the query
|
|
76
|
+
if "carrier_id" in list_filter:
|
|
77
|
+
_queryset = _queryset.filter(carrier_id=list_filter["carrier_id"])
|
|
78
|
+
|
|
79
|
+
# Check if a specific carrier_id is provided, to add it to the query
|
|
80
|
+
if "capability" in list_filter:
|
|
81
|
+
_queryset = _queryset.filter(
|
|
82
|
+
capabilities__icontains=list_filter["capability"]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Check if a metadata key is provided, to add it to the query
|
|
86
|
+
if "metadata_key" in list_filter:
|
|
87
|
+
_queryset = _queryset.filter(metadata__has_key=list_filter["metadata_key"])
|
|
88
|
+
|
|
89
|
+
# Check if a metadata value is provided, to add it to the query
|
|
90
|
+
if "metadata_value" in list_filter:
|
|
91
|
+
_value = list_filter["metadata_value"]
|
|
92
|
+
_queryset = _queryset.filter(
|
|
93
|
+
id__in=[
|
|
94
|
+
_["id"]
|
|
95
|
+
for _ in _queryset.values("id", "metadata")
|
|
96
|
+
if _value in (_.get("metadata") or {}).values()
|
|
97
|
+
]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Check if a list of carrier_ids are provided, to add the list to the query
|
|
101
|
+
if any(list_filter.get("carrier_ids", [])):
|
|
102
|
+
_queryset = _queryset.filter(carrier_id__in=list_filter["carrier_ids"])
|
|
103
|
+
|
|
104
|
+
if any(list_filter.get("services", [])):
|
|
105
|
+
carrier_names = [
|
|
106
|
+
name
|
|
107
|
+
for name, services in dataunits.contextual_reference(context)[
|
|
108
|
+
"services"
|
|
109
|
+
].items()
|
|
110
|
+
if any(
|
|
111
|
+
service in list_filter["services"] for service in services.keys()
|
|
112
|
+
)
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
if len(carrier_names) > 0:
|
|
116
|
+
_queryset = _queryset.filter(carrier_code__in=carrier_names)
|
|
117
|
+
if "carrier_name" in list_filter:
|
|
118
|
+
carrier_name = list_filter["carrier_name"]
|
|
119
|
+
_queryset = _queryset.filter(carrier_code=carrier_name)
|
|
120
|
+
|
|
121
|
+
carriers = _queryset.distinct()
|
|
122
|
+
|
|
123
|
+
# Raise an error if no carrier is found
|
|
124
|
+
if list_filter.get("raise_not_found") and len(carriers) == 0:
|
|
125
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
126
|
+
|
|
127
|
+
return carriers
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def first(**kwargs) -> providers.Carrier:
|
|
131
|
+
return next(iter(Carriers.list(**kwargs)), None)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class Address:
|
|
135
|
+
@staticmethod
|
|
136
|
+
def validate(payload: dict) -> datatypes.AddressValidation:
|
|
137
|
+
validation = validators.Address.validate(datatypes.Address(**payload))
|
|
138
|
+
|
|
139
|
+
if validation.success is False:
|
|
140
|
+
raise exceptions.APIException(detail=validation, code="invalid_address")
|
|
141
|
+
|
|
142
|
+
return validation
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Shipments:
|
|
146
|
+
@staticmethod
|
|
147
|
+
@utils.require_selected_rate
|
|
148
|
+
def create(
|
|
149
|
+
payload: dict,
|
|
150
|
+
carrier: providers.Carrier = None,
|
|
151
|
+
selected_rate: typing.Union[datatypes.Rate, dict] = None,
|
|
152
|
+
resolve_tracking_url: typing.Callable[[str, str], str] = None,
|
|
153
|
+
context: base_serializers.Context = None,
|
|
154
|
+
**kwargs,
|
|
155
|
+
) -> datatypes.Shipment:
|
|
156
|
+
selected_rate = lib.to_object(
|
|
157
|
+
datatypes.Rate,
|
|
158
|
+
lib.to_dict(selected_rate),
|
|
159
|
+
)
|
|
160
|
+
carrier = carrier or Carriers.first(
|
|
161
|
+
carrier_id=selected_rate.carrier_id,
|
|
162
|
+
test_mode=selected_rate.test_mode,
|
|
163
|
+
services=[selected_rate.service],
|
|
164
|
+
context=context,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if carrier is None:
|
|
168
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
169
|
+
|
|
170
|
+
request = lib.to_object(
|
|
171
|
+
datatypes.ShipmentRequest,
|
|
172
|
+
{**lib.to_dict(payload), "service": selected_rate.service},
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# The request is wrapped in utils.identity to simplify mocking in tests.
|
|
176
|
+
shipment, messages = utils.identity(
|
|
177
|
+
lambda: karrio.Shipment.create(request)
|
|
178
|
+
.from_(carrier.gateway)
|
|
179
|
+
.parse()
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if shipment is None:
|
|
183
|
+
raise exceptions.APIException(
|
|
184
|
+
detail=messages,
|
|
185
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def process_meta(parent) -> dict:
|
|
189
|
+
service_name = utils.upper(
|
|
190
|
+
(parent.meta or {}).get("service_name") or selected_rate.service
|
|
191
|
+
)
|
|
192
|
+
rate_provider = (
|
|
193
|
+
(parent.meta or {}).get("rate_provider") or carrier.carrier_name
|
|
194
|
+
).lower()
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
**(parent.meta or {}),
|
|
198
|
+
"ext": carrier.ext,
|
|
199
|
+
"carrier": rate_provider,
|
|
200
|
+
"service_name": service_name,
|
|
201
|
+
"rate_provider": rate_provider, # TODO: deprecate 'rate_provider' in favor of 'carrier'
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
def process_selected_rate() -> dict:
|
|
205
|
+
rate = lib.identity(
|
|
206
|
+
{
|
|
207
|
+
**lib.to_dict(shipment.selected_rate),
|
|
208
|
+
"id": f"rat_{uuid.uuid4().hex}",
|
|
209
|
+
"test_mode": carrier.test_mode,
|
|
210
|
+
}
|
|
211
|
+
if shipment.selected_rate is not None
|
|
212
|
+
else lib.to_dict(selected_rate)
|
|
213
|
+
)
|
|
214
|
+
return lib.to_dict({
|
|
215
|
+
**rate,
|
|
216
|
+
"meta": process_meta(shipment.selected_rate or selected_rate),
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
def process_tracking_url(rate: datatypes.Rate) -> str:
|
|
220
|
+
rate_provider = (rate.get("meta") or {}).get("rate_provider")
|
|
221
|
+
if (rate_provider not in dataunits.CARRIER_NAMES) and (
|
|
222
|
+
(shipment.meta or {}).get("tracking_url") is not None
|
|
223
|
+
):
|
|
224
|
+
return shipment.meta["tracking_url"]
|
|
225
|
+
|
|
226
|
+
if resolve_tracking_url is not None:
|
|
227
|
+
url = resolve_tracking_url(
|
|
228
|
+
shipment.tracking_number, rate_provider or rate.carrier_name
|
|
229
|
+
)
|
|
230
|
+
return utils.app_tracking_query_params(url, carrier)
|
|
231
|
+
|
|
232
|
+
return ""
|
|
233
|
+
|
|
234
|
+
def process_parcel_refs(parcels: typing.List[dict]) -> list:
|
|
235
|
+
references = (shipment.meta or {}).get("tracking_numbers") or [
|
|
236
|
+
shipment.tracking_number
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
return [
|
|
240
|
+
{
|
|
241
|
+
**lib.to_dict(parcel),
|
|
242
|
+
"reference_number": (
|
|
243
|
+
references[index]
|
|
244
|
+
if len(references) > index
|
|
245
|
+
else parcel.get("reference_number")
|
|
246
|
+
),
|
|
247
|
+
}
|
|
248
|
+
for index, parcel in enumerate(parcels)
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
shipment_rate = process_selected_rate()
|
|
252
|
+
|
|
253
|
+
return lib.to_object(
|
|
254
|
+
datatypes.Shipment,
|
|
255
|
+
{
|
|
256
|
+
"id": f"shp_{uuid.uuid4().hex}",
|
|
257
|
+
**payload,
|
|
258
|
+
**lib.to_dict(shipment),
|
|
259
|
+
"test_mode": carrier.test_mode,
|
|
260
|
+
"selected_rate": shipment_rate,
|
|
261
|
+
"service": shipment_rate["service"],
|
|
262
|
+
"selected_rate_id": shipment_rate["id"],
|
|
263
|
+
"parcels": process_parcel_refs(payload["parcels"]),
|
|
264
|
+
"tracking_url": process_tracking_url(shipment_rate),
|
|
265
|
+
"status": serializers.ShipmentStatus.purchased.value,
|
|
266
|
+
"created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f%z"),
|
|
267
|
+
"meta": process_meta(shipment),
|
|
268
|
+
"messages": messages,
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def cancel(
|
|
274
|
+
payload: dict, carrier: providers.Carrier = None, **carrier_filters
|
|
275
|
+
) -> datatypes.ConfirmationResponse:
|
|
276
|
+
carrier_id = lib.identity(
|
|
277
|
+
dict(carrier_id=payload.pop("carrier_id"))
|
|
278
|
+
if any(payload.get("carrier_id") or "")
|
|
279
|
+
else {}
|
|
280
|
+
)
|
|
281
|
+
carrier = carrier or Carriers.first(
|
|
282
|
+
**{
|
|
283
|
+
**dict(active=True, capability="shipping", raise_not_found=True),
|
|
284
|
+
**carrier_id,
|
|
285
|
+
**carrier_filters,
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if carrier is None:
|
|
290
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
291
|
+
|
|
292
|
+
request = karrio.Shipment.cancel(
|
|
293
|
+
lib.to_object(datatypes.ShipmentCancelRequest, payload)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# The request call is wrapped in utils.identity to simplify mocking in tests
|
|
297
|
+
confirmation, messages = lib.identity(
|
|
298
|
+
utils.identity(lambda: request.from_(carrier.gateway).parse())
|
|
299
|
+
if "cancel_shipment" in carrier.gateway.proxy_methods
|
|
300
|
+
else (
|
|
301
|
+
datatypes.Confirmation(
|
|
302
|
+
carrier_name=carrier.gateway.settings.carrier_name,
|
|
303
|
+
carrier_id=carrier.gateway.settings.carrier_id,
|
|
304
|
+
success=True,
|
|
305
|
+
operation="Safe cancellation allowed",
|
|
306
|
+
),
|
|
307
|
+
[],
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if confirmation is None:
|
|
312
|
+
raise exceptions.APIException(
|
|
313
|
+
detail=messages,
|
|
314
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return datatypes.ConfirmationResponse(
|
|
318
|
+
confirmation=confirmation,
|
|
319
|
+
messages=messages,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def track(
|
|
324
|
+
payload: dict,
|
|
325
|
+
carrier: providers.Carrier = None,
|
|
326
|
+
raise_on_error: bool = True,
|
|
327
|
+
**carrier_filters,
|
|
328
|
+
) -> datatypes.TrackingResponse:
|
|
329
|
+
carrier = carrier or Carriers.first(
|
|
330
|
+
**{
|
|
331
|
+
**dict(active=True, capability="tracking", raise_not_found=True),
|
|
332
|
+
**carrier_filters,
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if carrier is None:
|
|
337
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
338
|
+
|
|
339
|
+
request = karrio.Tracking.fetch(
|
|
340
|
+
lib.to_object(datatypes.TrackingRequest, payload)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# The request call is wrapped in utils.identity to simplify mocking in tests
|
|
344
|
+
results, messages = utils.identity(
|
|
345
|
+
lambda: request.from_(carrier.gateway).parse()
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if not any(results or []) and (
|
|
349
|
+
raise_on_error or utils.is_sdk_message(messages)
|
|
350
|
+
):
|
|
351
|
+
raise exceptions.APIException(
|
|
352
|
+
detail=messages,
|
|
353
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
result = next(iter(results or []), None)
|
|
357
|
+
tracking_number = payload["tracking_numbers"][0]
|
|
358
|
+
details = result or datatypes.TrackingDetails(
|
|
359
|
+
carrier_id=carrier.carrier_id,
|
|
360
|
+
carrier_name=carrier.carrier_name,
|
|
361
|
+
tracking_number=tracking_number,
|
|
362
|
+
events=[
|
|
363
|
+
datatypes.TrackingEvent(
|
|
364
|
+
date=datetime.datetime.now().strftime("%Y-%m-%d"),
|
|
365
|
+
description="Awaiting update from carrier...",
|
|
366
|
+
code="UNKNOWN",
|
|
367
|
+
time=datetime.datetime.now().strftime("%H:%M"),
|
|
368
|
+
)
|
|
369
|
+
],
|
|
370
|
+
delivered=False,
|
|
371
|
+
)
|
|
372
|
+
options = {
|
|
373
|
+
**(payload.get("options") or {}),
|
|
374
|
+
tracking_number: {
|
|
375
|
+
**(details.meta or {}),
|
|
376
|
+
**(payload.get("options") or {}).get(tracking_number, {}),
|
|
377
|
+
},
|
|
378
|
+
}
|
|
379
|
+
meta = {
|
|
380
|
+
"ext": carrier.ext,
|
|
381
|
+
"carrier": carrier.carrier_name,
|
|
382
|
+
**(details.meta or {}),
|
|
383
|
+
}
|
|
384
|
+
info = {
|
|
385
|
+
"carrier_tracking_link": utils.get_carrier_tracking_link(
|
|
386
|
+
carrier, tracking_number
|
|
387
|
+
),
|
|
388
|
+
"source": "api",
|
|
389
|
+
**(lib.to_dict(details.info or {})),
|
|
390
|
+
**(lib.to_dict(payload.get("info") or {})),
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return datatypes.TrackingResponse(
|
|
394
|
+
tracking=lib.to_object(
|
|
395
|
+
datatypes.Tracking,
|
|
396
|
+
{
|
|
397
|
+
**lib.to_dict(details),
|
|
398
|
+
"id": f"trk_{uuid.uuid4().hex}",
|
|
399
|
+
"test_mode": carrier.test_mode,
|
|
400
|
+
"status": utils.compute_tracking_status(result).value,
|
|
401
|
+
"options": options,
|
|
402
|
+
"meta": meta,
|
|
403
|
+
"info": info,
|
|
404
|
+
},
|
|
405
|
+
),
|
|
406
|
+
messages=messages,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class Pickups:
|
|
411
|
+
@staticmethod
|
|
412
|
+
def schedule(
|
|
413
|
+
payload: dict, carrier: providers.Carrier = None, **carrier_filters
|
|
414
|
+
) -> datatypes.PickupResponse:
|
|
415
|
+
carrier = carrier or Carriers.first(
|
|
416
|
+
**{
|
|
417
|
+
**dict(active=True, capability="pickup", raise_not_found=True),
|
|
418
|
+
**carrier_filters,
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if carrier is None:
|
|
423
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
424
|
+
|
|
425
|
+
request = karrio.Pickup.schedule(
|
|
426
|
+
datatypes.PickupRequest(**lib.to_dict(payload))
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# The request call is wrapped in utils.identity to simplify mocking in tests
|
|
430
|
+
pickup, messages = utils.identity(
|
|
431
|
+
lambda: request.from_(carrier.gateway).parse()
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if pickup is None:
|
|
435
|
+
raise exceptions.APIException(
|
|
436
|
+
detail=messages,
|
|
437
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
def process_meta(parent) -> dict:
|
|
441
|
+
return {
|
|
442
|
+
**(parent.meta or {}),
|
|
443
|
+
"ext": carrier.ext,
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return datatypes.PickupResponse(
|
|
447
|
+
pickup=datatypes.Pickup(
|
|
448
|
+
**{
|
|
449
|
+
**payload,
|
|
450
|
+
**lib.to_dict(pickup),
|
|
451
|
+
"id": f"pck_{uuid.uuid4().hex}",
|
|
452
|
+
"test_mode": carrier.test_mode,
|
|
453
|
+
"meta": process_meta(pickup),
|
|
454
|
+
"messages": messages,
|
|
455
|
+
}
|
|
456
|
+
),
|
|
457
|
+
messages=messages,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
@staticmethod
|
|
461
|
+
def update(
|
|
462
|
+
payload: dict, carrier: providers.Carrier = None, **carrier_filters
|
|
463
|
+
) -> datatypes.PickupResponse:
|
|
464
|
+
carrier = carrier or Carriers.first(
|
|
465
|
+
**{
|
|
466
|
+
**dict(active=True, capability="pickup", raise_not_found=True),
|
|
467
|
+
**carrier_filters,
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if carrier is None:
|
|
472
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
473
|
+
|
|
474
|
+
request = karrio.Pickup.update(
|
|
475
|
+
datatypes.PickupUpdateRequest(**lib.to_dict(payload))
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# The request call is wrapped in utils.identity to simplify mocking in tests
|
|
479
|
+
pickup, messages = utils.identity(
|
|
480
|
+
lambda: request.from_(carrier.gateway).parse()
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
if pickup is None:
|
|
484
|
+
raise exceptions.APIException(
|
|
485
|
+
detail=messages,
|
|
486
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
return datatypes.PickupResponse(
|
|
490
|
+
pickup=datatypes.Pickup(
|
|
491
|
+
**{
|
|
492
|
+
**payload,
|
|
493
|
+
**lib.to_dict(pickup),
|
|
494
|
+
"test_mode": carrier.test_mode,
|
|
495
|
+
}
|
|
496
|
+
),
|
|
497
|
+
messages=messages,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
@staticmethod
|
|
501
|
+
def cancel(
|
|
502
|
+
payload: dict, carrier: providers.Carrier = None, **carrier_filters
|
|
503
|
+
) -> datatypes.ConfirmationResponse:
|
|
504
|
+
carrier = carrier or Carriers.first(
|
|
505
|
+
**{
|
|
506
|
+
**dict(active=True, capability="pickup"),
|
|
507
|
+
**carrier_filters,
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if carrier is None:
|
|
512
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
513
|
+
|
|
514
|
+
request = karrio.Pickup.cancel(
|
|
515
|
+
datatypes.PickupCancelRequest(**lib.to_dict(payload))
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# The request call is wrapped in utils.identity to simplify mocking in tests
|
|
519
|
+
confirmation, messages = lib.identity(
|
|
520
|
+
utils.identity(lambda: request.from_(carrier.gateway).parse())
|
|
521
|
+
if "cancel_shipment" in carrier.gateway.proxy_methods
|
|
522
|
+
else (
|
|
523
|
+
datatypes.Confirmation(
|
|
524
|
+
carrier_name=carrier.gateway.settings.carrier_name,
|
|
525
|
+
carrier_id=carrier.gateway.settings.carrier_id,
|
|
526
|
+
success=True,
|
|
527
|
+
operation="Safe cancellation allowed",
|
|
528
|
+
),
|
|
529
|
+
[],
|
|
530
|
+
)
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if confirmation is None:
|
|
534
|
+
raise exceptions.APIException(
|
|
535
|
+
detail=messages,
|
|
536
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
return datatypes.ConfirmationResponse(
|
|
540
|
+
confirmation=confirmation, messages=messages
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@utils.post_processing(methods=["fetch"])
|
|
545
|
+
class Rates:
|
|
546
|
+
post_process_functions: typing.List[typing.Callable] = []
|
|
547
|
+
|
|
548
|
+
@staticmethod
|
|
549
|
+
def fetch(
|
|
550
|
+
payload: dict,
|
|
551
|
+
carriers: typing.List[providers.Carrier] = None,
|
|
552
|
+
raise_on_error: bool = True,
|
|
553
|
+
**carrier_filters,
|
|
554
|
+
) -> datatypes.RateResponse:
|
|
555
|
+
services = payload.get("services", [])
|
|
556
|
+
carrier_ids = payload.get("carrier_ids", [])
|
|
557
|
+
shipper_country_code = payload["shipper"].get("country_code")
|
|
558
|
+
carriers = carriers or Carriers.list(
|
|
559
|
+
**{
|
|
560
|
+
"active": True,
|
|
561
|
+
"capability": "rating",
|
|
562
|
+
"carrier_ids": carrier_ids,
|
|
563
|
+
"services": services,
|
|
564
|
+
**carrier_filters,
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
gateways = utils.filter_rate_carrier_compatible_gateways(
|
|
569
|
+
carriers, carrier_ids, shipper_country_code
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
if raise_on_error and len(gateways) == 0:
|
|
573
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
574
|
+
|
|
575
|
+
request = karrio.Rating.fetch(lib.to_object(datatypes.RateRequest, payload))
|
|
576
|
+
|
|
577
|
+
# The request call is wrapped in utils.identity to simplify mocking in tests
|
|
578
|
+
rates, messages = utils.identity(lambda: request.from_(*gateways).parse())
|
|
579
|
+
|
|
580
|
+
if raise_on_error and not any(rates) and any(messages):
|
|
581
|
+
raise exceptions.APIException(
|
|
582
|
+
detail=messages,
|
|
583
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
def process_rate(rate: datatypes.Rate) -> datatypes.Rate:
|
|
587
|
+
carrier = next((c for c in carriers if c.carrier_id == rate.carrier_id))
|
|
588
|
+
rate_provider = (
|
|
589
|
+
(rate.meta or {}).get("rate_provider")
|
|
590
|
+
or getattr(carrier, "custom_carrier_name", None)
|
|
591
|
+
or rate.carrier_name
|
|
592
|
+
).lower()
|
|
593
|
+
service_name = utils.upper(
|
|
594
|
+
(rate.meta or {}).get("service_name") or rate.service
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
meta = {
|
|
598
|
+
**(rate.meta or {}),
|
|
599
|
+
"ext": carrier.ext,
|
|
600
|
+
"carrier": rate_provider,
|
|
601
|
+
"service_name": service_name,
|
|
602
|
+
"rate_provider": rate_provider, # TODO: deprecate rate_provider
|
|
603
|
+
"carrier_connection_id": carrier.id,
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return lib.to_object(
|
|
607
|
+
datatypes.Rate,
|
|
608
|
+
{
|
|
609
|
+
**lib.to_dict(rate),
|
|
610
|
+
"id": f"rat_{uuid.uuid4().hex}",
|
|
611
|
+
"test_mode": carrier.test_mode,
|
|
612
|
+
"meta": meta,
|
|
613
|
+
},
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
formated_rates: typing.List[datatypes.Rate] = sorted(
|
|
617
|
+
map(process_rate, rates), key=lambda rate: rate.total_charge
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
return lib.to_object(
|
|
621
|
+
datatypes.RateResponse, dict(rates=formated_rates, messages=messages)
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
class Documents:
|
|
626
|
+
@staticmethod
|
|
627
|
+
def upload(
|
|
628
|
+
payload: dict,
|
|
629
|
+
carrier: providers.Carrier = None,
|
|
630
|
+
**carrier_filters,
|
|
631
|
+
) -> datatypes.DocumentUploadResponse:
|
|
632
|
+
carrier = carrier or Carriers.first(
|
|
633
|
+
**{
|
|
634
|
+
**dict(active=True, raise_not_found=True),
|
|
635
|
+
**carrier_filters,
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
if "upload_document" not in carrier.gateway.proxy_methods:
|
|
640
|
+
raise exceptions.APIException(
|
|
641
|
+
detail=f"trade document upload is not supported by carrier: '{carrier.carrier_id}'",
|
|
642
|
+
status_code=status.HTTP_406_NOT_ACCEPTABLE,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
request = karrio.Document.upload(
|
|
646
|
+
lib.to_object(datatypes.DocumentUploadRequest, payload)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# The request is wrapped in utils.identity to simplify mocking in tests.
|
|
650
|
+
upload, messages = utils.identity(
|
|
651
|
+
lambda: request.from_(carrier.gateway).parse()
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
if upload is None:
|
|
655
|
+
raise exceptions.APIException(
|
|
656
|
+
detail=messages,
|
|
657
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
return lib.to_object(
|
|
661
|
+
datatypes.DocumentUploadResponse,
|
|
662
|
+
{
|
|
663
|
+
**payload,
|
|
664
|
+
**lib.to_dict(upload),
|
|
665
|
+
"test_mode": carrier.test_mode,
|
|
666
|
+
"id": f"sdoc_{uuid.uuid4().hex}",
|
|
667
|
+
"messages": lib.to_dict(messages),
|
|
668
|
+
},
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
class Manifests:
|
|
673
|
+
@staticmethod
|
|
674
|
+
def create(
|
|
675
|
+
payload: dict, carrier: providers.Carrier = None, **carrier_filters
|
|
676
|
+
) -> datatypes.ManifestResponse:
|
|
677
|
+
carrier = carrier or Carriers.first(
|
|
678
|
+
**{
|
|
679
|
+
**dict(active=True, capability="manifest", raise_not_found=True),
|
|
680
|
+
**carrier_filters,
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if carrier is None:
|
|
685
|
+
raise NotFound("No active carrier connection found to process the request")
|
|
686
|
+
|
|
687
|
+
request = karrio.Manifest.create(
|
|
688
|
+
lib.to_object(datatypes.ManifestRequest, lib.to_dict(payload))
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
# The request call is wrapped in utils.identity to simplify mocking in tests
|
|
692
|
+
manifest, messages = utils.identity(
|
|
693
|
+
lambda: request.from_(carrier.gateway).parse()
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
if manifest is None:
|
|
697
|
+
raise exceptions.APIException(
|
|
698
|
+
detail=messages,
|
|
699
|
+
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
def process_meta(parent) -> dict:
|
|
703
|
+
return {
|
|
704
|
+
**(parent.meta or {}),
|
|
705
|
+
"ext": carrier.ext,
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return datatypes.ManifestResponse(
|
|
709
|
+
manifest=datatypes.Manifest(
|
|
710
|
+
**{
|
|
711
|
+
**payload,
|
|
712
|
+
**lib.to_dict(manifest),
|
|
713
|
+
"id": f"manf_{uuid.uuid4().hex}",
|
|
714
|
+
"test_mode": carrier.test_mode,
|
|
715
|
+
"meta": process_meta(manifest),
|
|
716
|
+
"messages": messages,
|
|
717
|
+
}
|
|
718
|
+
),
|
|
719
|
+
messages=messages,
|
|
720
|
+
)
|