cardo-python-utils 0.5.dev32__tar.gz → 0.5.dev33__tar.gz

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 (60) hide show
  1. {cardo_python_utils-0.5.dev32/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev33}/PKG-INFO +1 -1
  2. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33/cardo_python_utils.egg-info}/PKG-INFO +1 -1
  3. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/pyproject.toml +1 -1
  4. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/README.md +11 -8
  5. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/api/utils.py +1 -2
  6. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/middleware/tenant_aware_http_middleware.py +6 -16
  7. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/oidc_settings.py +1 -7
  8. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/settings.py +7 -1
  9. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/tenant_context.py +2 -2
  10. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/LICENSE +0 -0
  11. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/MANIFEST.in +0 -0
  12. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/README.rst +0 -0
  13. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/cardo_python_utils.egg-info/SOURCES.txt +0 -0
  14. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
  15. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/cardo_python_utils.egg-info/requires.txt +0 -0
  16. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/cardo_python_utils.egg-info/top_level.txt +0 -0
  17. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/__init__.py +0 -0
  18. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/choices.py +0 -0
  19. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/data_structures.py +0 -0
  20. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/db.py +0 -0
  21. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/__init__.py +0 -0
  22. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/admin/__init__.py +0 -0
  23. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/admin/auth.py +0 -0
  24. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/admin/templates/__init__.py +0 -0
  25. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/admin/templates/user_groups_changelist.html +0 -0
  26. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/admin/user_group.py +0 -0
  27. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/admin/views.py +0 -0
  28. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/api/__init__.py +0 -0
  29. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/api/drf.py +0 -0
  30. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/api/ninja.py +0 -0
  31. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/apps.py +0 -0
  32. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/auth/service.py +0 -0
  33. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/celery/__init__.py +0 -0
  34. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/celery/tenant_aware_task.py +0 -0
  35. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/db/__init__.py +0 -0
  36. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/db/routers.py +0 -0
  37. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/db/transaction.py +0 -0
  38. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/db/utils.py +0 -0
  39. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/management/__init__.py +0 -0
  40. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/management/commands/__init__.py +0 -0
  41. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/management/commands/migrateall.py +0 -0
  42. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/management/commands/tenant_aware_command.py +0 -0
  43. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/middleware/__init__.py +0 -0
  44. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/models/__init__.py +0 -0
  45. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/models/user_group.py +0 -0
  46. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/redis/__init__.py +0 -0
  47. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/redis/key_function.py +0 -0
  48. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/storage/__init__.py +0 -0
  49. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/storage/tenant_aware_storage.py +0 -0
  50. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/tests/__init__.py +0 -0
  51. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django/tests/conftest.py +0 -0
  52. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/django_utils.py +0 -0
  53. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/esma_choices.py +0 -0
  54. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/exceptions.py +0 -0
  55. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/imports.py +0 -0
  56. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/math.py +0 -0
  57. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/text.py +0 -0
  58. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/time.py +0 -0
  59. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/python_utils/types_hinting.py +0 -0
  60. {cardo_python_utils-0.5.dev32 → cardo_python_utils-0.5.dev33}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardo-python-utils
3
- Version: 0.5.dev32
3
+ Version: 0.5.dev33
4
4
  Summary: Python library enhanced with a wide range of functions for different scenarios.
5
5
  Author-email: CardoAI <hello@cardoai.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardo-python-utils
3
- Version: 0.5.dev32
3
+ Version: 0.5.dev33
4
4
  Summary: Python library enhanced with a wide range of functions for different scenarios.
5
5
  Author-email: CardoAI <hello@cardoai.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cardo-python-utils"
7
- version = "0.5.dev32"
7
+ version = "0.5.dev33"
8
8
  description = "Python library enhanced with a wide range of functions for different scenarios."
9
9
  readme = "README.rst"
10
10
  requires-python = ">=3.8"
@@ -2,18 +2,20 @@ This package provides utilities for facilitating IDP communication and multi-ten
2
2
 
3
3
  # Usage
4
4
 
5
-
6
5
  ## Environment variables to set
7
6
 
8
7
  - AWS_STORAGE_TENANT_BUCKET_NAMES
9
- - A JSON dictionary where each key is the tenant name and the value is the bucket name.
8
+ - _This variable should be set if separate tenant buckets are needed._
9
+ - A JSON dictionary where each key is the tenant name and the value is the bucket name.
10
10
  - DATABASE_CONFIG
11
- - A dictionary where each key is the tenant name and the value is a dict with the datase config.
12
- - If multiple 'DATABASE_CONFIG'-prefixed variables are set, they will be merged into a single dictionary.
11
+ - A JSON dictionary where each key is the tenant name and the value is a dict with the datase config.
12
+ - If multiple 'DATABASE_CONFIG'-prefixed variables are set, they will be merged into a single dictionary.
13
13
  - KEYCLOAK_SERVER_URL
14
- - The URL of the Keycloak deployment
14
+ - The URL of the Keycloak deployment
15
15
  - KEYCLOAK_CONFIDENTIAL_CLIENT_ID
16
- - The id of the confidential client of the backend service
16
+ - The id of the confidential client of the backend service
17
+ - KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS
18
+ - A JSON dictionary where each key is the tenant name and the value is the file path of the service account token for the confidential client of that tenant
17
19
 
18
20
  ## settings.py file
19
21
 
@@ -61,7 +63,8 @@ CACHES = {
61
63
  }
62
64
  }
63
65
 
64
- # If using Django Storages with S3, configure the storage backends as follows:
66
+ # If using Django Storages with S3, and separate tenant buckets are needed,
67
+ # configure the storage backends as follows:
65
68
  STORAGES = {
66
69
  "default": {
67
70
  "BACKEND": "python_utils.django.storage.TenantAwarePrivateS3Storage",
@@ -119,4 +122,4 @@ SESSION_SAVE_EVERY_REQUEST = True # Extend session on each request
119
122
 
120
123
  ## With django-ninja
121
124
 
122
- If using `django-ninja`, apart from the settings configured above, auth utils are provided in the django/api/ninja.py module.
125
+ If using `django-ninja`, apart from the settings configured above, auth utils are provided in the django/api/ninja.py module.
@@ -4,8 +4,7 @@ from django.conf import settings
4
4
  from django.contrib.auth import get_user_model
5
5
  from jwt import decode, PyJWKClient
6
6
 
7
- from django.oidc_settings import get_oidc_op_jwks_endpoint
8
-
7
+ from ..oidc_settings import get_oidc_op_jwks_endpoint
9
8
  from ..tenant_context import TenantContext
10
9
 
11
10
  JWKS_CLIENTS = {} # Cache for PyJWKClient instances
@@ -5,7 +5,7 @@ from django.conf import settings
5
5
  from django.core.handlers.wsgi import WSGIRequest
6
6
  from django.http import HttpResponse
7
7
 
8
- from ..settings import DEVELOPMENT_TENANT, TENANT_AWARE_EXCLUDED_PATHS, TENANT_DATABASES
8
+ from ..settings import DEVELOPMENT_TENANT, TENANT_AWARE_EXCLUDED_PATHS
9
9
  from ..tenant_context import TenantContext
10
10
 
11
11
  logger = logging.getLogger(__name__)
@@ -51,9 +51,6 @@ class TenantAwareHttpMiddleware:
51
51
  if self._is_excluded_path(request.path):
52
52
  return self.get_response(request)
53
53
 
54
- if TenantContext.is_set():
55
- raise Exception("Tenant context already set")
56
-
57
54
  # In DEBUG mode, use DEVELOPMENT_TENANT directly
58
55
  # In production, extract tenant from subdomain
59
56
  if settings.DEBUG:
@@ -64,10 +61,6 @@ class TenantAwareHttpMiddleware:
64
61
  if tenant is None:
65
62
  raise Exception(f"Could not determine tenant from subdomain. Host: {request.get_host()}")
66
63
 
67
- # Validate tenant exists in configured databases
68
- if not self._is_valid_tenant(tenant):
69
- raise Exception(f"Unknown tenant: {tenant}")
70
-
71
64
  # Call the next middleware in the chain until the response is returned.
72
65
  # After that, the database alias is removed from the thread local variable.
73
66
  with TenantContext(tenant):
@@ -94,6 +87,11 @@ class TenantAwareHttpMiddleware:
94
87
  - <app>.tenant-internal.domain.com -> tenant (strips -internal suffix)
95
88
  """
96
89
  host = request.get_host().split(":")[0] # Remove port if present
90
+
91
+ if host == "testserver":
92
+ logger.debug("Using 'default' tenant for testserver host.")
93
+ return "default"
94
+
97
95
  parts = host.split(".")
98
96
 
99
97
  # Need at least 3 parts: <app>.<tenant>.<domain>
@@ -103,11 +101,3 @@ class TenantAwareHttpMiddleware:
103
101
  return tenant
104
102
 
105
103
  return None
106
-
107
- @staticmethod
108
- def _is_valid_tenant(tenant: str) -> bool:
109
- """
110
- Validate that the tenant exists in configured databases.
111
- Skip 'default' as it's typically a placeholder.
112
- """
113
- return tenant in TENANT_DATABASES
@@ -12,15 +12,9 @@ from .tenant_context import TenantContext
12
12
 
13
13
 
14
14
  KEYCLOAK_SERVER_URL = os.getenv("KEYCLOAK_SERVER_URL", None)
15
- if not KEYCLOAK_SERVER_URL:
16
- raise ValueError("KEYCLOAK_SERVER_URL env variable is not set.")
17
-
18
15
  KEYCLOAK_CONFIDENTIAL_CLIENT_ID = os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_ID", None)
19
- if not KEYCLOAK_CONFIDENTIAL_CLIENT_ID:
20
- raise ValueError("KEYCLOAK_CONFIDENTIAL_CLIENT_ID env variable is not set.")
21
-
22
16
  KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS: dict[str, str] = json.loads(
23
- os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS")
17
+ os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS", "{}")
24
18
  )
25
19
  KEYCLOAK_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"
26
20
  KEYCLOAK_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
@@ -1,6 +1,7 @@
1
1
  from django.conf import settings
2
2
 
3
3
  TENANT_KEY = "tenant"
4
+ DATABASES = settings.DATABASES
4
5
  TENANT_DATABASES = set(settings.DATABASES.keys()) - {"default"}
5
6
 
6
7
  TENANT_AWARE_EXCLUDED_PATHS = getattr(settings, "TENANT_AWARE_EXCLUDED_PATHS", ())
@@ -14,4 +15,9 @@ TENANT_AWARE_EXCLUDED_PATHS = (
14
15
  "/mediafiles",
15
16
  )
16
17
 
17
- DEVELOPMENT_TENANT = getattr(settings, "DEVELOPMENT_TENANT", list(TENANT_DATABASES)[0])
18
+ DEVELOPMENT_TENANT = getattr(settings, "DEVELOPMENT_TENANT")
19
+ if DEVELOPMENT_TENANT is None:
20
+ if TENANT_DATABASES:
21
+ DEVELOPMENT_TENANT = list(TENANT_DATABASES)[0]
22
+ else:
23
+ DEVELOPMENT_TENANT = list(DATABASES.keys())[0]
@@ -4,7 +4,7 @@ from contextlib import ContextDecorator
4
4
  from threading import local
5
5
  from typing import Optional
6
6
 
7
- from .settings import TENANT_DATABASES
7
+ from .settings import DATABASES
8
8
 
9
9
  logger = logging.getLogger(__name__)
10
10
  thread_namespace = local()
@@ -75,7 +75,7 @@ class TenantContext(ContextDecorator):
75
75
  logger.info("Tenant context already set to %s", tenant)
76
76
  return
77
77
 
78
- if tenant not in TENANT_DATABASES:
78
+ if tenant not in DATABASES:
79
79
  logger.error(f"Tenant '{tenant}' not found in DATABASES settings.")
80
80
  return sys.exit(1)
81
81