karrio-server 2025.5rc30__py3-none-any.whl → 2025.5rc32__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/VERSION CHANGED
@@ -1 +1 @@
1
- 2025.5rc30
1
+ 2025.5rc32
@@ -5,6 +5,7 @@ import importlib.util
5
5
  from karrio.server.settings.base import *
6
6
  from karrio.server.settings.apm import *
7
7
  from karrio.server.settings.cache import *
8
+ from karrio.server.settings.workers import *
8
9
  from karrio.server.settings.debug import *
9
10
  from karrio.server.settings.email import *
10
11
  from karrio.server.settings.constance import *
@@ -164,7 +164,7 @@ if OTEL_ENABLED and OTEL_EXPORTER_OTLP_ENDPOINT:
164
164
  pass # Psycopg2 might not be installed
165
165
 
166
166
  # Instrument Redis if configured
167
- if config("REDIS_HOST", default=None):
167
+ if config("REDIS_URL", default=None) or config("REDIS_HOST", default=None):
168
168
  try:
169
169
  RedisInstrumentor().instrument()
170
170
  except Exception:
@@ -31,9 +31,11 @@ config = decouple.AutoConfig(search_path=Path().resolve())
31
31
 
32
32
  if not config("SECRET_KEY", default=None):
33
33
  try:
34
+ # Note: Using print here intentionally as logging isn't configured yet
34
35
  print("> fallback .env.sample...")
35
36
  config = decouple.Config(decouple.RepositoryEnv(".env.sample"))
36
37
  except Exception as e:
38
+ # Note: Using print here intentionally as logging isn't configured yet
37
39
  print(f"> error: {e}")
38
40
 
39
41
 
@@ -261,62 +263,6 @@ OTP_APPS = [
261
263
  "two_factor.plugins.email",
262
264
  ]
263
265
 
264
- # Configure HUEY before djhuey/apps are loaded
265
- # This must be done here (not in workers.py) because task modules import djhuey
266
- # at module load time, which happens when Django loads apps from INSTALLED_APPS
267
- _REDIS_HOST = config("REDIS_HOST", default=None)
268
- _REDIS_PORT = config("REDIS_PORT", default=None)
269
- _REDIS_PASSWORD = config("REDIS_PASSWORD", default=None)
270
- _REDIS_USERNAME = config("REDIS_USERNAME", default="default")
271
- _WORKER_IMMEDIATE_MODE = config("WORKER_IMMEDIATE_MODE", default=False, cast=bool) and (
272
- _REDIS_HOST is None
273
- )
274
-
275
- HUEY = {
276
- "huey_class": "huey.RedisHuey" if _REDIS_HOST else "huey.SqliteHuey",
277
- "name": "default",
278
- "results": True,
279
- "store_none": False,
280
- "utc": True,
281
- **(
282
- {
283
- "blocking": True,
284
- "connection": {
285
- "host": _REDIS_HOST,
286
- "port": _REDIS_PORT or 6379,
287
- "db": 0,
288
- "connection_pool": None,
289
- "read_timeout": 1,
290
- "max_connections": 20,
291
- **(
292
- {
293
- "username": _REDIS_USERNAME,
294
- "password": _REDIS_PASSWORD,
295
- }
296
- if _REDIS_PASSWORD
297
- else {}
298
- ),
299
- },
300
- }
301
- if _REDIS_HOST
302
- else {}
303
- ),
304
- **(
305
- {
306
- "filename": os.path.join(WORK_DIR, "tasks.sqlite3"),
307
- "immediate": _WORKER_IMMEDIATE_MODE,
308
- }
309
- if not _REDIS_HOST
310
- else {}
311
- ),
312
- }
313
-
314
- # Karrio Server Background jobs interval config
315
- DEFAULT_SCHEDULER_RUN_INTERVAL = 3600 # value is seconds. so 3600 seconds = 1 Hour
316
- DEFAULT_TRACKERS_UPDATE_INTERVAL = config(
317
- "TRACKING_PULSE", default=7200, cast=int
318
- ) # value is seconds. so 10800 seconds = 3 Hours
319
-
320
266
  INSTALLED_APPS = [
321
267
  "constance",
322
268
  *KARRIO_APPS,
@@ -409,7 +355,7 @@ if config("DATABASE_URL", default=None):
409
355
  DATABASES["default"].update(db_from_env)
410
356
 
411
357
  # Configure workers database for SQLite storage when Redis is not available
412
- if not config("REDIS_HOST", default=None):
358
+ if not config("REDIS_URL", default=None) and not config("REDIS_HOST", default=None):
413
359
  _WORKER_DB_DIR = config("WORKER_DB_DIR", default=WORK_DIR)
414
360
  _WORKER_DB_FILE_NAME = os.path.join(_WORKER_DB_DIR, "tasks.sqlite3")
415
361
  DATABASES["workers"] = {
@@ -637,54 +583,91 @@ LOG_FILE_DIR = config("LOG_DIR", default=WORK_DIR)
637
583
  LOG_FILE_NAME = os.path.join(LOG_FILE_DIR, "debug.log")
638
584
  DRF_TRACKING_ADMIN_LOG_READONLY = True
639
585
 
640
- LOGGING = {
641
- "version": 1,
642
- "disable_existing_loggers": False,
643
- "formatters": {
644
- "verbose": {
645
- "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
646
- "style": "{",
647
- },
648
- "simple": {
649
- "format": "{levelname} {filename} {lineno} {message}",
650
- "style": "{",
651
- },
652
- },
653
- "handlers": {
654
- "file": {
655
- "level": "DEBUG",
656
- "class": "logging.handlers.TimedRotatingFileHandler",
657
- "formatter": "verbose",
658
- "filename": LOG_FILE_NAME,
659
- "when": "D",
660
- "interval": 1,
661
- "backupCount": 20,
662
- },
663
- "console": {
664
- "class": "logging.StreamHandler",
665
- "formatter": "simple",
586
+ # Option to use Loguru (default: True)
587
+ USE_LOGURU = config("USE_LOGURU", default=True, cast=bool)
588
+
589
+ if USE_LOGURU:
590
+ # Minimal LOGGING config - will be intercepted by Loguru
591
+ LOGGING = {
592
+ "version": 1,
593
+ "disable_existing_loggers": False,
594
+ "handlers": {
595
+ "console": {
596
+ "class": "logging.StreamHandler",
597
+ },
666
598
  },
667
- },
668
- "loggers": {
669
- "oauth2_provider": {
670
- "level": "DEBUG",
599
+ "root": {
671
600
  "handlers": ["console"],
672
- "propagate": True,
601
+ "level": "INFO",
673
602
  },
674
- "django": {
675
- "handlers": ["file", "console"],
676
- "level": DJANGO_LOG_LEVEL,
677
- "propagate": False,
603
+ }
604
+ else:
605
+ # Traditional Django logging configuration
606
+ LOGGING = {
607
+ "version": 1,
608
+ "disable_existing_loggers": False,
609
+ "formatters": {
610
+ "verbose": {
611
+ "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
612
+ "style": "{",
613
+ },
614
+ "simple": {
615
+ "format": "{levelname} {filename} {lineno} {message}",
616
+ "style": "{",
617
+ },
678
618
  },
679
- "karrio": {
680
- "handlers": ["file", "console"],
681
- "level": LOG_LEVEL,
682
- "propagate": False,
619
+ "handlers": {
620
+ "file": {
621
+ "level": "DEBUG",
622
+ "class": "logging.handlers.TimedRotatingFileHandler",
623
+ "formatter": "verbose",
624
+ "filename": LOG_FILE_NAME,
625
+ "when": "D",
626
+ "interval": 1,
627
+ "backupCount": 20,
628
+ },
629
+ "console": {
630
+ "class": "logging.StreamHandler",
631
+ "formatter": "simple",
632
+ },
683
633
  },
684
- "karrio.server.core.exceptions": {
685
- "handlers": ["file", "console"],
686
- "level": "DEBUG",
687
- "propagate": False,
634
+ "loggers": {
635
+ "oauth2_provider": {
636
+ "level": "DEBUG",
637
+ "handlers": ["console"],
638
+ "propagate": True,
639
+ },
640
+ "django": {
641
+ "handlers": ["file", "console"],
642
+ "level": DJANGO_LOG_LEVEL,
643
+ "propagate": False,
644
+ },
645
+ "karrio": {
646
+ "handlers": ["file", "console"],
647
+ "level": LOG_LEVEL,
648
+ "propagate": False,
649
+ },
650
+ "karrio.server.core.exceptions": {
651
+ "handlers": ["file", "console"],
652
+ "level": "DEBUG",
653
+ "propagate": False,
654
+ },
688
655
  },
689
- },
690
- }
656
+ }
657
+
658
+ # Initialize Loguru if enabled
659
+ if USE_LOGURU:
660
+ try:
661
+ from karrio.server.core.logging import setup_django_loguru, logger
662
+
663
+ setup_django_loguru(
664
+ level=LOG_LEVEL,
665
+ log_file=LOG_FILE_NAME,
666
+ intercept_django=True,
667
+ enqueue=True, # Thread-safe async logging
668
+ )
669
+ except ImportError as e:
670
+ # Note: Using print here as Loguru failed to load
671
+ print(f"Warning: Failed to initialize Loguru: {e}")
672
+ print("Falling back to standard Django logging")
673
+ USE_LOGURU = False
@@ -2,28 +2,63 @@
2
2
  from decouple import config
3
3
  from karrio.server.settings.base import *
4
4
  from karrio.server.settings.apm import HEALTH_CHECK_APPS
5
+ from karrio.server.core.logging import logger
5
6
 
6
7
 
7
8
  CACHE_TTL = 60 * 15
8
- REDIS_HOST = config("REDIS_HOST", default=None)
9
- REDIS_PORT = config("REDIS_PORT", default=None)
10
- REDIS_PASSWORD = config("REDIS_PASSWORD", default=None)
11
- REDIS_USERNAME = config("REDIS_USERNAME", default="default")
9
+
10
+ # Redis configuration - REDIS_URL takes precedence and supersedes granular env vars
11
+ REDIS_URL = config("REDIS_URL", default=None)
12
12
  REDIS_PREFIX = config("REDIS_PREFIX", default="karrio")
13
+ REDIS_SSL = config("REDIS_SSL", default=False, cast=bool)
14
+
15
+ # Parse REDIS_URL or construct from individual parameters
16
+ if REDIS_URL is not None:
17
+ from urllib.parse import urlparse, urlunparse
18
+ import re
19
+
20
+ parsed = urlparse(REDIS_URL)
21
+
22
+ # Extract values from REDIS_URL (these supersede granular env vars)
23
+ REDIS_HOST = parsed.hostname
24
+ REDIS_PORT = parsed.port or 6379
25
+ REDIS_USERNAME = parsed.username or "default"
26
+ REDIS_PASSWORD = parsed.password
27
+
28
+ # Determine SSL from URL scheme (rediss:// means SSL is enabled)
29
+ REDIS_SCHEME = parsed.scheme if parsed.scheme in ("redis", "rediss") else "redis"
30
+ REDIS_SSL = REDIS_SCHEME == "rediss"
13
31
 
14
- # karrio server caching setup
32
+ # Build connection URL with database 1 for cache
33
+ REDIS_AUTH = f"{REDIS_USERNAME}:{REDIS_PASSWORD}@" if REDIS_PASSWORD else ""
34
+ REDIS_CONNECTION_URL = f'{REDIS_SCHEME}://{REDIS_AUTH}{REDIS_HOST}:{REDIS_PORT}/1'
35
+
36
+ else:
37
+ # Fall back to individual parameters
38
+ REDIS_HOST = config("REDIS_HOST", default=None)
39
+ REDIS_PORT = config("REDIS_PORT", default=None)
40
+ REDIS_PASSWORD = config("REDIS_PASSWORD", default=None)
41
+ REDIS_USERNAME = config("REDIS_USERNAME", default="default")
42
+
43
+ if REDIS_HOST is not None:
44
+ REDIS_AUTH = f"{REDIS_USERNAME}:{REDIS_PASSWORD}@" if REDIS_PASSWORD else ""
45
+ REDIS_SCHEME = "rediss" if REDIS_SSL else "redis"
46
+ REDIS_CONNECTION_URL = f'{REDIS_SCHEME}://{REDIS_AUTH}{REDIS_HOST}:{REDIS_PORT or "6379"}/1'
47
+
48
+ # Configure Django cache if Redis is available
15
49
  if REDIS_HOST is not None:
16
50
  HEALTH_CHECK_APPS += ["health_check.contrib.redis"]
17
51
  INSTALLED_APPS += ["health_check.contrib.redis"]
18
- REDIS_AUTH = f"{REDIS_USERNAME}:{REDIS_PASSWORD}@" if REDIS_PASSWORD else ""
19
52
 
20
- REDIS_CONNECTION_URL = f'redis://{REDIS_AUTH}{REDIS_HOST}:{REDIS_PORT or "6379"}/1'
21
53
  CACHES = {
22
54
  "default": {
23
55
  "BACKEND": "django_redis.cache.RedisCache",
24
56
  "LOCATION": REDIS_CONNECTION_URL,
25
- "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
57
+ "OPTIONS": {
58
+ "CLIENT_CLASS": "django_redis.client.DefaultClient",
59
+ **({"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": None}} if REDIS_SSL else {}),
60
+ },
26
61
  "KEY_PREFIX": REDIS_PREFIX,
27
62
  }
28
63
  }
29
- print(f"Redis connection initialized at: {REDIS_CONNECTION_URL}")
64
+ logger.info("Redis connection initialized", redis_url=REDIS_CONNECTION_URL)
@@ -0,0 +1,76 @@
1
+ # type: ignore
2
+ import os
3
+ import huey
4
+ import redis
5
+ import decouple
6
+ from karrio.server.settings import base as settings
7
+
8
+
9
+ # Karrio Server Background jobs interval config
10
+ DEFAULT_SCHEDULER_RUN_INTERVAL = 3600 # value is seconds. so 3600 seconds = 1 Hour
11
+ DEFAULT_TRACKERS_UPDATE_INTERVAL = decouple.config(
12
+ "TRACKING_PULSE", default=7200, cast=int
13
+ ) # value is seconds. so 10800 seconds = 3 Hours
14
+
15
+ WORKER_IMMEDIATE_MODE = decouple.config(
16
+ "WORKER_IMMEDIATE_MODE", default=False, cast=bool
17
+ )
18
+
19
+ # Redis configuration - REDIS_URL takes precedence and supersedes granular env vars
20
+ REDIS_URL = decouple.config("REDIS_URL", default=None)
21
+
22
+ # Parse REDIS_URL or construct from individual parameters
23
+ if REDIS_URL is not None:
24
+ from urllib.parse import urlparse
25
+
26
+ parsed = urlparse(REDIS_URL)
27
+
28
+ # Extract values from REDIS_URL (these supersede granular env vars)
29
+ REDIS_HOST = parsed.hostname
30
+ REDIS_PORT = parsed.port or 6379
31
+ REDIS_USERNAME = parsed.username or "default"
32
+ REDIS_PASSWORD = parsed.password
33
+
34
+ # Determine SSL from URL scheme (rediss:// means SSL is enabled)
35
+ REDIS_SCHEME = parsed.scheme if parsed.scheme in ("redis", "rediss") else "redis"
36
+ REDIS_SSL = REDIS_SCHEME == "rediss"
37
+
38
+ else:
39
+ # Fall back to individual parameters
40
+ REDIS_HOST = decouple.config("REDIS_HOST", default=None)
41
+ REDIS_PORT = decouple.config("REDIS_PORT", default=None)
42
+ REDIS_PASSWORD = decouple.config("REDIS_PASSWORD", default=None)
43
+ REDIS_USERNAME = decouple.config("REDIS_USERNAME", default="default")
44
+ REDIS_SSL = decouple.config("REDIS_SSL", default=False, cast=bool)
45
+
46
+ # Configure HUEY based on available Redis configuration
47
+ if REDIS_HOST is not None:
48
+ pool = redis.ConnectionPool(
49
+ host=REDIS_HOST,
50
+ port=REDIS_PORT,
51
+ max_connections=20,
52
+ **({"ssl": REDIS_SSL} if REDIS_SSL else {}),
53
+ **({"password": REDIS_PASSWORD} if REDIS_PASSWORD else {}),
54
+ **({"username": REDIS_USERNAME} if REDIS_USERNAME else {}),
55
+ )
56
+ HUEY = huey.RedisHuey(
57
+ "default",
58
+ connection_pool=pool,
59
+ **({"immediate": WORKER_IMMEDIATE_MODE} if WORKER_IMMEDIATE_MODE else {}),
60
+ )
61
+
62
+ else:
63
+ # No Redis configured, use SQLite
64
+ WORKER_DB_DIR = decouple.config("WORKER_DB_DIR", default=settings.WORK_DIR)
65
+ WORKER_DB_FILE_NAME = os.path.join(WORKER_DB_DIR, "tasks.sqlite3")
66
+
67
+ settings.DATABASES["workers"] = {
68
+ "NAME": WORKER_DB_FILE_NAME,
69
+ "ENGINE": "django.db.backends.sqlite3",
70
+ }
71
+
72
+ HUEY = huey.SqliteHuey(
73
+ name="default",
74
+ filename=WORKER_DB_FILE_NAME,
75
+ **({"immediate": WORKER_IMMEDIATE_MODE} if WORKER_IMMEDIATE_MODE else {}),
76
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_server
3
- Version: 2025.5rc30
3
+ Version: 2025.5rc32
4
4
  Summary: Multi-carrier shipping API
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: Apache-2.0
@@ -42,6 +42,7 @@ Requires-Dist: opentelemetry-instrumentation-logging
42
42
  Requires-Dist: opentelemetry-instrumentation-psycopg2
43
43
  Requires-Dist: opentelemetry-instrumentation-redis
44
44
  Requires-Dist: opentelemetry-semantic-conventions
45
+ Requires-Dist: loguru
45
46
 
46
47
  # <a href="https://karrio.io" target="_blank"><img alt="Karrio" src="https://docs.karrio.io/img/logo.svg" height="50px" /></a>
47
48
 
@@ -1,17 +1,18 @@
1
- karrio/server/VERSION,sha256=TS1XH1yu99ZMb3FKrCfrLPcYQPR8kVZYdkjtKn54feQ,10
1
+ karrio/server/VERSION,sha256=XNzgmUDikIDatuZ8iDHbUCq5nJQzGvX4ntLiAGaC9vE,10
2
2
  karrio/server/__init__.py,sha256=iOEMwnlORWezdO8-2vxBIPSR37D7JGjluZ8f55vzxls,81
3
3
  karrio/server/__main__.py,sha256=hy2-Zb2wSVe_Pu6zWZ-BhiM4rBaGZ3D16HSuhudygqg,632
4
4
  karrio/server/asgi.py,sha256=LsZYMWo8U9zURVPdHnvUsziOhMjdCdQoD2-gMJbS2U0,462
5
5
  karrio/server/workers.py,sha256=wOlWmXC7zRJV86IfbQnfUZsnCIvvWtXzFHH_EIkg1J0,179
6
6
  karrio/server/wsgi.py,sha256=SpWqkEYlMsj89_znZ8p8IjH3EgTVRWRq_9eS8t64dMw,403
7
7
  karrio/server/lib/otel_huey.py,sha256=6MP6vX6b6x6RPF2K1m8B8L8S9GK1Q3vANmLidsxh65k,5428
8
- karrio/server/settings/__init__.py,sha256=rpHZgzt7czC0ZUvlu7QWkFYuJokelp5yACtK8UQCaTk,821
9
- karrio/server/settings/apm.py,sha256=PfILlN71p38pNHeV3AysAdnyErnsIvh_8buPVt6ASfw,7378
10
- karrio/server/settings/base.py,sha256=2cv8tMgjiX-kmgnZbZgoNDFTuQc4d_GxhsbzpSRRDoc,22552
11
- karrio/server/settings/cache.py,sha256=BCFIjbLKBgYs34i099Z_i8EC55lRF39oJfsPHoXq3vc,1112
8
+ karrio/server/settings/__init__.py,sha256=iw-NBcReOnDYpnvSEBdYDfV7jC0040jYdupnmSdElec,866
9
+ karrio/server/settings/apm.py,sha256=fp9_CVzNfQ8MvNp1-WQbCILzLU3RSJeeu1vYAtdrgfs,7415
10
+ karrio/server/settings/base.py,sha256=311L71Lxk-9tv0nqeERtKIrWjvEhD9g2LmUcpd0MkoA,22246
11
+ karrio/server/settings/cache.py,sha256=wiuhbTPjEyHY5xgvVfwNV0-QxsFN5kTI8sSwnibCSVI,2502
12
12
  karrio/server/settings/constance.py,sha256=wKi7u-NORAPjIJMIbl3k5kPRkUA6jJJWe9kxsV3aoeA,7113
13
13
  karrio/server/settings/debug.py,sha256=fFyK2XX47UGeK0eRNSV-9ZLaComay5QvJW0692vaH98,527
14
14
  karrio/server/settings/email.py,sha256=bHBLKM_v3HTkmjrz_Msdj_Z7_kMzAb7i6pvJCZuzu1E,1403
15
+ karrio/server/settings/workers.py,sha256=snzAxzZLxP5i0mTNEE6AD3bHT7WBjh-fe-A-vT0cQIE,2666
15
16
  karrio/server/static/extra/branding/android-chrome-192x192.png,sha256=qSwEKKBtk4udSHb0OWGC5-jfkP5cVpULDD1Cdkuk8PU,7005
16
17
  karrio/server/static/extra/branding/android-chrome-512x512.png,sha256=YFwVPnPChO30tTuyrwSc_z548H7C6OFXh4CCu2krvHA,21062
17
18
  karrio/server/static/extra/branding/favicon-16x16.png,sha256=wkELpij29bIvvKr5sDcjfNeYvj7i0yk-__bJbZckEK8,1085
@@ -72,8 +73,8 @@ karrio/server/templates/admin/base_site.html,sha256=kbcdvehXZ1EHaw07JL7fSZmjrnVM
72
73
  karrio/server/templates/openapi/openapi.html,sha256=3ApCZ5pE6Wjv7CJllVbqD2WiDQuLy-BFS-IIHurXhBY,1133
73
74
  karrio/server/urls/__init__.py,sha256=Ah-XqaqRsfecQgCGRHjxmXe8O7a0avq5ocU90tkVwQI,1998
74
75
  karrio/server/urls/jwt.py,sha256=QN2L-EpUEQCF2UGYPu_VVlA49Fc0BtcY7Ov3-xpp7_U,6772
75
- karrio_server-2025.5rc30.dist-info/METADATA,sha256=tifknyLMeuNQgaLGsQRT70r3T_7BxrhU-qS56CJhjQE,4283
76
- karrio_server-2025.5rc30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
- karrio_server-2025.5rc30.dist-info/entry_points.txt,sha256=c2eftt6MpJjyp0OFv1OmO9nUYSDemt9fGq_RDdvpGLw,55
78
- karrio_server-2025.5rc30.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
79
- karrio_server-2025.5rc30.dist-info/RECORD,,
76
+ karrio_server-2025.5rc32.dist-info/METADATA,sha256=FoCDlHgfC1ctngm3M4xIDcTCajXjxQFYiWGouGBgklA,4305
77
+ karrio_server-2025.5rc32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
+ karrio_server-2025.5rc32.dist-info/entry_points.txt,sha256=c2eftt6MpJjyp0OFv1OmO9nUYSDemt9fGq_RDdvpGLw,55
79
+ karrio_server-2025.5rc32.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
80
+ karrio_server-2025.5rc32.dist-info/RECORD,,