karrio-server-core 2025.5rc12__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.
Files changed (112) hide show
  1. karrio/server/core/authentication.py +59 -25
  2. karrio/server/core/config.py +31 -0
  3. karrio/server/core/datatypes.py +30 -4
  4. karrio/server/core/dataunits.py +53 -22
  5. karrio/server/core/exceptions.py +287 -17
  6. karrio/server/core/filters.py +14 -0
  7. karrio/server/core/gateway.py +285 -10
  8. karrio/server/core/logging.py +403 -0
  9. karrio/server/core/management/commands/runserver.py +5 -0
  10. karrio/server/core/middleware.py +104 -2
  11. karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
  12. karrio/server/core/models/base.py +34 -1
  13. karrio/server/core/oauth_validators.py +2 -3
  14. karrio/server/core/permissions.py +1 -2
  15. karrio/server/core/serializers.py +183 -10
  16. karrio/server/core/signals.py +22 -28
  17. karrio/server/core/telemetry.py +573 -0
  18. karrio/server/core/tests/__init__.py +27 -0
  19. karrio/server/core/{tests.py → tests/base.py} +6 -7
  20. karrio/server/core/tests/test_exception_level.py +159 -0
  21. karrio/server/core/tests/test_resource_token.py +593 -0
  22. karrio/server/core/utils.py +688 -38
  23. karrio/server/core/validators.py +144 -222
  24. karrio/server/core/views/oauth.py +13 -12
  25. karrio/server/core/views/references.py +2 -2
  26. karrio/server/iam/apps.py +1 -4
  27. karrio/server/iam/migrations/0002_setup_carrier_permission_groups.py +103 -0
  28. karrio/server/iam/migrations/0003_remove_permission_groups.py +91 -0
  29. karrio/server/iam/permissions.py +7 -134
  30. karrio/server/iam/serializers.py +17 -2
  31. karrio/server/iam/signals.py +2 -4
  32. karrio/server/providers/admin.py +1 -1
  33. karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
  34. karrio/server/providers/migrations/0082_add_zone_identifiers.py +50 -0
  35. karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py +33 -0
  36. karrio/server/providers/migrations/0084_alter_servicelevel_currency.py +168 -0
  37. karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
  38. karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
  39. karrio/server/providers/migrations/0087_alter_carrier_capabilities.py +38 -0
  40. karrio/server/providers/migrations/0088_servicelevel_surcharges.py +24 -0
  41. karrio/server/providers/migrations/0089_servicelevel_cost_max_volume.py +31 -0
  42. karrio/server/providers/migrations/0090_ratesheet_surcharges_servicelevel_zone_surcharge_ids.py +47 -0
  43. karrio/server/providers/migrations/0091_migrate_legacy_zones_surcharges.py +154 -0
  44. karrio/server/providers/models/__init__.py +1 -2
  45. karrio/server/providers/models/carrier.py +103 -18
  46. karrio/server/providers/models/service.py +188 -1
  47. karrio/server/providers/models/sheet.py +371 -0
  48. karrio/server/providers/serializers/base.py +263 -2
  49. karrio/server/providers/signals.py +2 -4
  50. karrio/server/providers/templates/providers/oauth_callback.html +105 -0
  51. karrio/server/providers/tests/__init__.py +5 -0
  52. karrio/server/providers/tests/test_connections.py +895 -0
  53. karrio/server/providers/views/carriers.py +1 -3
  54. karrio/server/providers/views/connections.py +322 -2
  55. karrio/server/samples.py +1 -1
  56. karrio/server/serializers/abstract.py +116 -21
  57. karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
  58. karrio/server/tracing/models.py +2 -0
  59. karrio/server/tracing/utils.py +5 -8
  60. karrio/server/user/migrations/0007_user_metadata.py +25 -0
  61. karrio/server/user/models.py +38 -23
  62. karrio/server/user/serializers.py +1 -0
  63. karrio/server/user/templates/registration/registration_confirm_email.html +1 -1
  64. {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/METADATA +2 -2
  65. {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/RECORD +67 -86
  66. karrio/server/providers/extension/__init__.py +0 -1
  67. karrio/server/providers/extension/models/__init__.py +0 -1
  68. karrio/server/providers/extension/models/allied_express.py +0 -22
  69. karrio/server/providers/extension/models/allied_express_local.py +0 -22
  70. karrio/server/providers/extension/models/amazon_shipping.py +0 -27
  71. karrio/server/providers/extension/models/aramex.py +0 -25
  72. karrio/server/providers/extension/models/asendia_us.py +0 -21
  73. karrio/server/providers/extension/models/australiapost.py +0 -20
  74. karrio/server/providers/extension/models/boxknight.py +0 -19
  75. karrio/server/providers/extension/models/bpost.py +0 -21
  76. karrio/server/providers/extension/models/canadapost.py +0 -21
  77. karrio/server/providers/extension/models/canpar.py +0 -19
  78. karrio/server/providers/extension/models/chronopost.py +0 -22
  79. karrio/server/providers/extension/models/colissimo.py +0 -22
  80. karrio/server/providers/extension/models/dhl_express.py +0 -23
  81. karrio/server/providers/extension/models/dhl_parcel_de.py +0 -25
  82. karrio/server/providers/extension/models/dhl_poland.py +0 -22
  83. karrio/server/providers/extension/models/dhl_universal.py +0 -19
  84. karrio/server/providers/extension/models/dicom.py +0 -20
  85. karrio/server/providers/extension/models/dpd.py +0 -37
  86. karrio/server/providers/extension/models/dpdhl.py +0 -26
  87. karrio/server/providers/extension/models/easypost.py +0 -20
  88. karrio/server/providers/extension/models/eshipper.py +0 -21
  89. karrio/server/providers/extension/models/fedex.py +0 -25
  90. karrio/server/providers/extension/models/fedex_ws.py +0 -24
  91. karrio/server/providers/extension/models/freightcom.py +0 -21
  92. karrio/server/providers/extension/models/generic.py +0 -35
  93. karrio/server/providers/extension/models/geodis.py +0 -22
  94. karrio/server/providers/extension/models/hay_post.py +0 -22
  95. karrio/server/providers/extension/models/laposte.py +0 -19
  96. karrio/server/providers/extension/models/locate2u.py +0 -22
  97. karrio/server/providers/extension/models/nationex.py +0 -22
  98. karrio/server/providers/extension/models/purolator.py +0 -21
  99. karrio/server/providers/extension/models/roadie.py +0 -18
  100. karrio/server/providers/extension/models/royalmail.py +0 -19
  101. karrio/server/providers/extension/models/sendle.py +0 -22
  102. karrio/server/providers/extension/models/tge.py +0 -63
  103. karrio/server/providers/extension/models/tnt.py +0 -23
  104. karrio/server/providers/extension/models/ups.py +0 -23
  105. karrio/server/providers/extension/models/usps.py +0 -23
  106. karrio/server/providers/extension/models/usps_international.py +0 -23
  107. karrio/server/providers/extension/models/usps_wt.py +0 -24
  108. karrio/server/providers/extension/models/usps_wt_international.py +0 -24
  109. karrio/server/providers/extension/models/zoom2u.py +0 -23
  110. karrio/server/providers/tests.py +0 -3
  111. {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/WHEEL +0 -0
  112. {karrio_server_core-2025.5rc12.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
 
@@ -153,7 +158,7 @@ class AddressData(validators.AugmentedAddressSerializer):
153
158
  required=False,
154
159
  allow_blank=True,
155
160
  allow_null=True,
156
- max_length=10,
161
+ max_length=20,
157
162
  help_text="""The address postal code
158
163
  **(required for shipment purchase)**
159
164
  """,
@@ -323,6 +328,26 @@ class CommodityData(serializers.Serializer):
323
328
  choices=COUNTRIES,
324
329
  help_text="The origin or manufacture country",
325
330
  )
331
+ product_url = serializers.CharField(
332
+ required=False,
333
+ allow_null=True,
334
+ help_text="The product url",
335
+ )
336
+ image_url = serializers.CharField(
337
+ required=False,
338
+ allow_null=True,
339
+ help_text="The image url",
340
+ )
341
+ product_id = serializers.CharField(
342
+ required=False,
343
+ allow_null=True,
344
+ help_text="The product id",
345
+ )
346
+ variant_id = serializers.CharField(
347
+ required=False,
348
+ allow_null=True,
349
+ help_text="The variant id",
350
+ )
326
351
  parent_id = serializers.CharField(
327
352
  required=False,
328
353
  allow_null=True,
@@ -372,7 +397,7 @@ class ParcelData(validators.PresetSerializer):
372
397
  required=False,
373
398
  allow_blank=True,
374
399
  allow_null=True,
375
- max_length=50,
400
+ max_length=100,
376
401
  help_text=f"""The parcel's packaging type.<br/>
377
402
  **Note that the packaging is optional when using a package preset.**<br/>
378
403
  values: <br/>
@@ -384,7 +409,7 @@ class ParcelData(validators.PresetSerializer):
384
409
  required=False,
385
410
  allow_blank=True,
386
411
  allow_null=True,
387
- max_length=50,
412
+ max_length=100,
388
413
  help_text="""The parcel's package preset.<br/>
389
414
  For carrier specific package presets, please consult the reference.
390
415
  """,
@@ -613,6 +638,12 @@ class Charge(serializers.Serializer):
613
638
  allow_null=True,
614
639
  help_text="The charge amount currency",
615
640
  )
641
+ id = serializers.CharField(
642
+ required=False,
643
+ allow_blank=True,
644
+ allow_null=True,
645
+ help_text="A surcharge id",
646
+ )
616
647
 
617
648
 
618
649
  @serializers.allow_model_id(
@@ -660,6 +691,7 @@ class RateRequest(validators.OptionDefaultSerializer):
660
691
  {
661
692
  "currency": "USD",
662
693
  "insurance": 100.00,
694
+ "insured_by": "carrier",
663
695
  "cash_on_delivery": 30.00,
664
696
  "dangerous_good": true,
665
697
  "declared_value": 150.00,
@@ -701,6 +733,18 @@ class RateRequest(validators.OptionDefaultSerializer):
701
733
  allow_null=True,
702
734
  help_text="The shipment reference",
703
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
+ )
704
748
  carrier_ids = serializers.StringListField(
705
749
  required=False,
706
750
  allow_null=True,
@@ -1042,11 +1086,24 @@ class TrackingEvent(serializers.Serializer):
1042
1086
  date = serializers.CharField(
1043
1087
  required=False, help_text="The tracking event's date. Format: `YYYY-MM-DD`"
1044
1088
  )
1045
- description = serializers.CharField(
1046
- required=False, help_text="The tracking event's description"
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`",
1047
1094
  )
1048
- location = serializers.CharField(
1049
- required=False, help_text="The tracking event's location"
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",
1050
1107
  )
1051
1108
  code = serializers.CharField(
1052
1109
  required=False,
@@ -1054,11 +1111,18 @@ class TrackingEvent(serializers.Serializer):
1054
1111
  allow_null=True,
1055
1112
  help_text="The tracking event's code",
1056
1113
  )
1057
- time = serializers.CharField(
1114
+ reason = serializers.ChoiceField(
1058
1115
  required=False,
1059
1116
  allow_blank=True,
1060
1117
  allow_null=True,
1061
- help_text="The tracking event's time. Format: `HH:MM AM/PM`",
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"
1062
1126
  )
1063
1127
  latitude = serializers.FloatField(
1064
1128
  required=False,
@@ -1153,6 +1217,62 @@ class TrackingStatus(TrackerDetails):
1153
1217
  )
1154
1218
 
1155
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
+
1156
1276
  class Documents(serializers.Serializer):
1157
1277
  label = serializers.CharField(
1158
1278
  required=False,
@@ -1585,6 +1705,7 @@ class ShipmentContent(serializers.Serializer):
1585
1705
  )
1586
1706
 
1587
1707
 
1708
+ @validators.shipment_documents_accessor
1588
1709
  class Shipment(serializers.EntitySerializer, ShipmentContent, ShipmentDetails):
1589
1710
  docs = None
1590
1711
  label_url = serializers.URLField(
@@ -1599,6 +1720,12 @@ class Shipment(serializers.EntitySerializer, ShipmentContent, ShipmentDetails):
1599
1720
  allow_null=True,
1600
1721
  help_text="The shipment invoice URL",
1601
1722
  )
1723
+ shipping_documents = ShippingDocument(
1724
+ required=False,
1725
+ many=True,
1726
+ default=[],
1727
+ help_text="The list of shipping documents",
1728
+ )
1602
1729
 
1603
1730
 
1604
1731
  class ShipmentCancelRequest(serializers.Serializer):
@@ -1750,7 +1877,10 @@ class OperationConfirmation(Operation):
1750
1877
  required=True, help_text="The operation carrier"
1751
1878
  )
1752
1879
  carrier_id = serializers.CharField(
1753
- required=True, help_text="The targeted carrier's name (unique identifier)"
1880
+ required=False,
1881
+ allow_blank=True,
1882
+ allow_null=True,
1883
+ help_text="The targeted carrier's name (unique identifier)",
1754
1884
  )
1755
1885
 
1756
1886
 
@@ -1887,6 +2017,49 @@ class DocumentUploadRecord(serializers.EntitySerializer):
1887
2017
  )
1888
2018
 
1889
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
+
1890
2063
  class ErrorMessages(serializers.Serializer):
1891
2064
  messages = Message(
1892
2065
  many=True,
@@ -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
- logger = logging.getLogger(__name__)
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 signals registered...")
14
+ logger.info("Signal registration complete", module="karrio.core")
17
15
 
18
16
 
19
- def initialize_settings(sender=None, **kwargs):
20
- # Only run once
21
- if not getattr(initialize_settings, 'has_run', False):
22
- try:
23
- update_settings(config)
24
- initialize_settings.has_run = True
25
- except Exception as e:
26
- logger.error(f"Failed to initialize settings: {e}")
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, key, old_value, new_value, **kwargs):
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
- CONSTANCE_CONFIG_KEYS = [
37
- key for key in settings.CONSTANCE_CONFIG.keys() if hasattr(settings, key)
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 CONSTANCE_CONFIG_KEYS:
38
+ for key in keys:
41
39
  try:
42
40
  setattr(settings, key, getattr(current, key))
43
- except Exception as e:
44
- logger.error(f"Failed to update setting {key}: {e}")
41
+ except Exception:
42
+ pass # Ignore errors during test/initialization when constance table may not exist
45
43
 
46
- # Check EMAIL_ENABLED after all settings are updated
44
+ # Update EMAIL_ENABLED based on email config
47
45
  try:
48
46
  settings.EMAIL_ENABLED = all(
49
- cfg is not None and 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 as e:
56
- logger.error(f"Failed to set EMAIL_ENABLED: {e}")
50
+ except Exception:
57
51
  settings.EMAIL_ENABLED = False