karrio-server-core 2025.5rc31__py3-none-any.whl → 2026.1.1__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/server/core/authentication.py +38 -20
- karrio/server/core/config.py +31 -0
- karrio/server/core/datatypes.py +30 -4
- karrio/server/core/dataunits.py +26 -7
- karrio/server/core/exceptions.py +287 -17
- karrio/server/core/filters.py +14 -0
- karrio/server/core/gateway.py +284 -11
- karrio/server/core/logging.py +403 -0
- karrio/server/core/middleware.py +104 -2
- karrio/server/core/models/base.py +34 -1
- karrio/server/core/oauth_validators.py +2 -3
- karrio/server/core/permissions.py +1 -2
- karrio/server/core/serializers.py +154 -7
- karrio/server/core/signals.py +22 -28
- karrio/server/core/telemetry.py +573 -0
- karrio/server/core/tests/__init__.py +27 -0
- karrio/server/core/{tests.py → tests/base.py} +6 -7
- karrio/server/core/tests/test_exception_level.py +159 -0
- karrio/server/core/tests/test_resource_token.py +593 -0
- karrio/server/core/utils.py +688 -38
- karrio/server/core/validators.py +144 -222
- karrio/server/core/views/oauth.py +13 -12
- karrio/server/core/views/references.py +2 -2
- karrio/server/iam/apps.py +1 -4
- karrio/server/iam/migrations/0002_setup_carrier_permission_groups.py +103 -0
- karrio/server/iam/migrations/0003_remove_permission_groups.py +91 -0
- karrio/server/iam/permissions.py +7 -134
- karrio/server/iam/serializers.py +9 -3
- karrio/server/iam/signals.py +2 -4
- karrio/server/providers/admin.py +1 -1
- karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
- karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
- karrio/server/providers/migrations/0087_alter_carrier_capabilities.py +38 -0
- karrio/server/providers/migrations/0088_servicelevel_surcharges.py +24 -0
- karrio/server/providers/migrations/0089_servicelevel_cost_max_volume.py +31 -0
- karrio/server/providers/migrations/0090_ratesheet_surcharges_servicelevel_zone_surcharge_ids.py +47 -0
- karrio/server/providers/migrations/0091_migrate_legacy_zones_surcharges.py +154 -0
- karrio/server/providers/models/carrier.py +101 -29
- karrio/server/providers/models/service.py +182 -125
- karrio/server/providers/models/sheet.py +342 -198
- karrio/server/providers/serializers/base.py +263 -2
- karrio/server/providers/signals.py +2 -4
- karrio/server/providers/templates/providers/oauth_callback.html +105 -0
- karrio/server/providers/tests/__init__.py +5 -0
- karrio/server/providers/tests/test_connections.py +895 -0
- karrio/server/providers/views/carriers.py +1 -3
- karrio/server/providers/views/connections.py +322 -2
- karrio/server/serializers/abstract.py +112 -21
- karrio/server/tracing/utils.py +5 -8
- karrio/server/user/models.py +36 -34
- karrio/server/user/serializers.py +1 -0
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/METADATA +2 -2
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/RECORD +55 -38
- karrio/server/providers/tests.py +0 -3
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/WHEEL +0 -0
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/top_level.txt +0 -0
|
@@ -21,6 +21,7 @@ class ShipmentStatus(utils.Enum):
|
|
|
21
21
|
|
|
22
22
|
class TrackerStatus(utils.Enum):
|
|
23
23
|
pending = "pending"
|
|
24
|
+
picked_up = "picked_up"
|
|
24
25
|
unknown = "unknown"
|
|
25
26
|
on_hold = "on_hold"
|
|
26
27
|
cancelled = "cancelled"
|
|
@@ -49,6 +50,9 @@ CUSTOMS_CONTENT_TYPE = [(c.name, c.name) for c in list(units.CustomsContentType)
|
|
|
49
50
|
UPLOAD_DOCUMENT_TYPE = [(c.name, c.name) for c in list(units.UploadDocumentType)]
|
|
50
51
|
LABEL_TYPES = [(c.name, c.name) for c in list(units.LabelType)]
|
|
51
52
|
LABEL_TEMPLATE_TYPES = [("SVG", "SVG"), ("ZPL", "ZPL")]
|
|
53
|
+
TRACKING_INCIDENT_REASONS = [
|
|
54
|
+
(c.name, c.name) for c in list(units.TrackingIncidentReason)
|
|
55
|
+
]
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
class CarrierDetails(serializers.Serializer):
|
|
@@ -129,6 +133,7 @@ class APIError(serializers.Serializer):
|
|
|
129
133
|
required=False, help_text="The error or warning message"
|
|
130
134
|
)
|
|
131
135
|
code = serializers.CharField(required=False, help_text="The message code")
|
|
136
|
+
level = serializers.CharField(required=False, help_text="The message level")
|
|
132
137
|
details = serializers.DictField(required=False, help_text="any additional details")
|
|
133
138
|
|
|
134
139
|
|
|
@@ -686,6 +691,7 @@ class RateRequest(validators.OptionDefaultSerializer):
|
|
|
686
691
|
{
|
|
687
692
|
"currency": "USD",
|
|
688
693
|
"insurance": 100.00,
|
|
694
|
+
"insured_by": "carrier",
|
|
689
695
|
"cash_on_delivery": 30.00,
|
|
690
696
|
"dangerous_good": true,
|
|
691
697
|
"declared_value": 150.00,
|
|
@@ -727,6 +733,18 @@ class RateRequest(validators.OptionDefaultSerializer):
|
|
|
727
733
|
allow_null=True,
|
|
728
734
|
help_text="The shipment reference",
|
|
729
735
|
)
|
|
736
|
+
payment = Payment(
|
|
737
|
+
required=False,
|
|
738
|
+
allow_null=True,
|
|
739
|
+
help_text="The payment details",
|
|
740
|
+
)
|
|
741
|
+
customs = CustomsData(
|
|
742
|
+
required=False,
|
|
743
|
+
allow_null=True,
|
|
744
|
+
help_text="""The customs details.<br/>
|
|
745
|
+
**Note that this is required for international shipments.**
|
|
746
|
+
""",
|
|
747
|
+
)
|
|
730
748
|
carrier_ids = serializers.StringListField(
|
|
731
749
|
required=False,
|
|
732
750
|
allow_null=True,
|
|
@@ -1068,11 +1086,24 @@ class TrackingEvent(serializers.Serializer):
|
|
|
1068
1086
|
date = serializers.CharField(
|
|
1069
1087
|
required=False, help_text="The tracking event's date. Format: `YYYY-MM-DD`"
|
|
1070
1088
|
)
|
|
1071
|
-
|
|
1072
|
-
required=False,
|
|
1089
|
+
time = serializers.CharField(
|
|
1090
|
+
required=False,
|
|
1091
|
+
allow_blank=True,
|
|
1092
|
+
allow_null=True,
|
|
1093
|
+
help_text="The tracking event's time. Format: `HH:MM AM/PM`",
|
|
1073
1094
|
)
|
|
1074
|
-
|
|
1075
|
-
required=False,
|
|
1095
|
+
timestamp = serializers.CharField(
|
|
1096
|
+
required=False,
|
|
1097
|
+
allow_blank=True,
|
|
1098
|
+
allow_null=True,
|
|
1099
|
+
help_text="The tracking event's timestamp. Format: `YYYY-MM-DDTHH:MM:SS.sssZ` (ISO 8601)",
|
|
1100
|
+
)
|
|
1101
|
+
status = serializers.ChoiceField(
|
|
1102
|
+
required=False,
|
|
1103
|
+
allow_blank=True,
|
|
1104
|
+
allow_null=True,
|
|
1105
|
+
choices=TRACKER_STATUS,
|
|
1106
|
+
help_text="The normalized status of this specific event",
|
|
1076
1107
|
)
|
|
1077
1108
|
code = serializers.CharField(
|
|
1078
1109
|
required=False,
|
|
@@ -1080,11 +1111,18 @@ class TrackingEvent(serializers.Serializer):
|
|
|
1080
1111
|
allow_null=True,
|
|
1081
1112
|
help_text="The tracking event's code",
|
|
1082
1113
|
)
|
|
1083
|
-
|
|
1114
|
+
reason = serializers.ChoiceField(
|
|
1084
1115
|
required=False,
|
|
1085
1116
|
allow_blank=True,
|
|
1086
1117
|
allow_null=True,
|
|
1087
|
-
|
|
1118
|
+
choices=TRACKING_INCIDENT_REASONS,
|
|
1119
|
+
help_text="The normalized incident reason (for exception events only)",
|
|
1120
|
+
)
|
|
1121
|
+
description = serializers.CharField(
|
|
1122
|
+
required=False, help_text="The tracking event's description"
|
|
1123
|
+
)
|
|
1124
|
+
location = serializers.CharField(
|
|
1125
|
+
required=False, help_text="The tracking event's location"
|
|
1088
1126
|
)
|
|
1089
1127
|
latitude = serializers.FloatField(
|
|
1090
1128
|
required=False,
|
|
@@ -1179,6 +1217,62 @@ class TrackingStatus(TrackerDetails):
|
|
|
1179
1217
|
)
|
|
1180
1218
|
|
|
1181
1219
|
|
|
1220
|
+
class ShippingDocument(serializers.Serializer):
|
|
1221
|
+
"""Serializer for shipping document download response."""
|
|
1222
|
+
|
|
1223
|
+
category = serializers.CharField(
|
|
1224
|
+
required=True,
|
|
1225
|
+
help_text="The document category (e.g., 'label', 'invoice', 'manifest')",
|
|
1226
|
+
)
|
|
1227
|
+
format = serializers.CharField(
|
|
1228
|
+
required=True,
|
|
1229
|
+
help_text="The document format (e.g., 'PDF', 'ZPL')",
|
|
1230
|
+
)
|
|
1231
|
+
url = serializers.CharField(
|
|
1232
|
+
required=False,
|
|
1233
|
+
allow_blank=True,
|
|
1234
|
+
allow_null=True,
|
|
1235
|
+
help_text="The document URL",
|
|
1236
|
+
)
|
|
1237
|
+
base64 = serializers.CharField(
|
|
1238
|
+
required=False,
|
|
1239
|
+
allow_blank=True,
|
|
1240
|
+
allow_null=True,
|
|
1241
|
+
help_text="The document content encoded in base64",
|
|
1242
|
+
)
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
class ShippingDocument(serializers.Serializer):
|
|
1246
|
+
"""Serializer for shipping document download response."""
|
|
1247
|
+
|
|
1248
|
+
category = serializers.CharField(
|
|
1249
|
+
required=True,
|
|
1250
|
+
help_text="The document category (e.g., 'label', 'invoice', 'manifest')",
|
|
1251
|
+
)
|
|
1252
|
+
format = serializers.CharField(
|
|
1253
|
+
required=True,
|
|
1254
|
+
help_text="The document format (e.g., 'PDF', 'ZPL')",
|
|
1255
|
+
)
|
|
1256
|
+
print_format = serializers.CharField(
|
|
1257
|
+
required=False,
|
|
1258
|
+
allow_blank=True,
|
|
1259
|
+
allow_null=True,
|
|
1260
|
+
help_text="The document print format (e.g., 'A4', '6x4', '8.5x11')",
|
|
1261
|
+
)
|
|
1262
|
+
url = serializers.CharField(
|
|
1263
|
+
required=False,
|
|
1264
|
+
allow_blank=True,
|
|
1265
|
+
allow_null=True,
|
|
1266
|
+
help_text="The document URL",
|
|
1267
|
+
)
|
|
1268
|
+
base64 = serializers.CharField(
|
|
1269
|
+
required=False,
|
|
1270
|
+
allow_blank=True,
|
|
1271
|
+
allow_null=True,
|
|
1272
|
+
help_text="The document content encoded in base64",
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
|
|
1182
1276
|
class Documents(serializers.Serializer):
|
|
1183
1277
|
label = serializers.CharField(
|
|
1184
1278
|
required=False,
|
|
@@ -1611,6 +1705,7 @@ class ShipmentContent(serializers.Serializer):
|
|
|
1611
1705
|
)
|
|
1612
1706
|
|
|
1613
1707
|
|
|
1708
|
+
@validators.shipment_documents_accessor
|
|
1614
1709
|
class Shipment(serializers.EntitySerializer, ShipmentContent, ShipmentDetails):
|
|
1615
1710
|
docs = None
|
|
1616
1711
|
label_url = serializers.URLField(
|
|
@@ -1625,6 +1720,12 @@ class Shipment(serializers.EntitySerializer, ShipmentContent, ShipmentDetails):
|
|
|
1625
1720
|
allow_null=True,
|
|
1626
1721
|
help_text="The shipment invoice URL",
|
|
1627
1722
|
)
|
|
1723
|
+
shipping_documents = ShippingDocument(
|
|
1724
|
+
required=False,
|
|
1725
|
+
many=True,
|
|
1726
|
+
default=[],
|
|
1727
|
+
help_text="The list of shipping documents",
|
|
1728
|
+
)
|
|
1628
1729
|
|
|
1629
1730
|
|
|
1630
1731
|
class ShipmentCancelRequest(serializers.Serializer):
|
|
@@ -1776,7 +1877,10 @@ class OperationConfirmation(Operation):
|
|
|
1776
1877
|
required=True, help_text="The operation carrier"
|
|
1777
1878
|
)
|
|
1778
1879
|
carrier_id = serializers.CharField(
|
|
1779
|
-
required=
|
|
1880
|
+
required=False,
|
|
1881
|
+
allow_blank=True,
|
|
1882
|
+
allow_null=True,
|
|
1883
|
+
help_text="The targeted carrier's name (unique identifier)",
|
|
1780
1884
|
)
|
|
1781
1885
|
|
|
1782
1886
|
|
|
@@ -1913,6 +2017,49 @@ class DocumentUploadRecord(serializers.EntitySerializer):
|
|
|
1913
2017
|
)
|
|
1914
2018
|
|
|
1915
2019
|
|
|
2020
|
+
class OAuthAuthorizeData(serializers.Serializer):
|
|
2021
|
+
"""OAuth authorization request data serializer."""
|
|
2022
|
+
|
|
2023
|
+
state = serializers.CharField(
|
|
2024
|
+
required=True,
|
|
2025
|
+
help_text="A unique state value for CSRF protection.",
|
|
2026
|
+
)
|
|
2027
|
+
redirect_uri = serializers.CharField(
|
|
2028
|
+
required=False,
|
|
2029
|
+
allow_null=True,
|
|
2030
|
+
help_text="The URI to redirect to after authorization.",
|
|
2031
|
+
)
|
|
2032
|
+
options = serializers.PlainDictField(
|
|
2033
|
+
required=False,
|
|
2034
|
+
default={},
|
|
2035
|
+
help_text="Additional carrier-specific options.",
|
|
2036
|
+
)
|
|
2037
|
+
|
|
2038
|
+
|
|
2039
|
+
class OAuthCallbackData(serializers.Serializer):
|
|
2040
|
+
"""OAuth callback request data serializer."""
|
|
2041
|
+
|
|
2042
|
+
url = serializers.CharField(
|
|
2043
|
+
required=True,
|
|
2044
|
+
help_text="The full callback URL.",
|
|
2045
|
+
)
|
|
2046
|
+
query = serializers.PlainDictField(
|
|
2047
|
+
required=False,
|
|
2048
|
+
default={},
|
|
2049
|
+
help_text="The query parameters from the callback URL.",
|
|
2050
|
+
)
|
|
2051
|
+
body = serializers.PlainDictField(
|
|
2052
|
+
required=False,
|
|
2053
|
+
default={},
|
|
2054
|
+
help_text="The request body if any.",
|
|
2055
|
+
)
|
|
2056
|
+
headers = serializers.PlainDictField(
|
|
2057
|
+
required=False,
|
|
2058
|
+
default={},
|
|
2059
|
+
help_text="The request headers.",
|
|
2060
|
+
)
|
|
2061
|
+
|
|
2062
|
+
|
|
1916
2063
|
class ErrorMessages(serializers.Serializer):
|
|
1917
2064
|
messages = Message(
|
|
1918
2065
|
many=True,
|
karrio/server/core/signals.py
CHANGED
|
@@ -1,57 +1,51 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from django.conf import settings
|
|
3
2
|
from django.dispatch import receiver
|
|
4
3
|
from constance import config
|
|
5
4
|
from constance.signals import config_updated
|
|
6
5
|
from django.core.signals import request_started
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
from karrio.server.core.logging import logger
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
def register_signals():
|
|
12
11
|
config_updated.connect(constance_updated)
|
|
13
|
-
# Defer config initialization until after Django is fully loaded
|
|
14
12
|
request_started.connect(initialize_settings)
|
|
15
13
|
|
|
16
|
-
logger.info("karrio.core
|
|
14
|
+
logger.info("Signal registration complete", module="karrio.core")
|
|
17
15
|
|
|
18
16
|
|
|
19
|
-
def initialize_settings(
|
|
20
|
-
|
|
21
|
-
if
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
def initialize_settings(_sender=None, **_kwargs):
|
|
18
|
+
"""Initialize Django settings from constance on first request."""
|
|
19
|
+
if getattr(initialize_settings, "has_run", False):
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
update_settings(config)
|
|
24
|
+
initialize_settings.has_run = True
|
|
25
|
+
except Exception as e:
|
|
26
|
+
logger.error("Failed to initialize settings", error=str(e))
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@receiver(config_updated)
|
|
30
|
-
def constance_updated(sender,
|
|
31
|
-
logger.info(f"Updated config {key} to {new_value}")
|
|
30
|
+
def constance_updated(sender, **_kwargs):
|
|
32
31
|
update_settings(sender)
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
def update_settings(current):
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
]
|
|
35
|
+
"""Sync constance values to Django settings."""
|
|
36
|
+
keys = [k for k in settings.CONSTANCE_CONFIG.keys() if hasattr(settings, k)]
|
|
39
37
|
|
|
40
|
-
for key in
|
|
38
|
+
for key in keys:
|
|
41
39
|
try:
|
|
42
40
|
setattr(settings, key, getattr(current, key))
|
|
43
|
-
except Exception
|
|
44
|
-
|
|
41
|
+
except Exception:
|
|
42
|
+
pass # Ignore errors during test/initialization when constance table may not exist
|
|
45
43
|
|
|
46
|
-
#
|
|
44
|
+
# Update EMAIL_ENABLED based on email config
|
|
47
45
|
try:
|
|
48
46
|
settings.EMAIL_ENABLED = all(
|
|
49
|
-
cfg
|
|
50
|
-
for cfg in [
|
|
51
|
-
current.EMAIL_HOST,
|
|
52
|
-
current.EMAIL_HOST_USER,
|
|
53
|
-
]
|
|
47
|
+
cfg not in (None, "")
|
|
48
|
+
for cfg in [current.EMAIL_HOST, current.EMAIL_HOST_USER]
|
|
54
49
|
)
|
|
55
|
-
except Exception
|
|
56
|
-
logger.error(f"Failed to set EMAIL_ENABLED: {e}")
|
|
50
|
+
except Exception:
|
|
57
51
|
settings.EMAIL_ENABLED = False
|