cardo-python-utils 0.5.dev35__tar.gz → 0.5.dev37__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.
- {cardo_python_utils-0.5.dev35/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev37}/PKG-INFO +1 -1
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37/cardo_python_utils.egg-info}/PKG-INFO +1 -1
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/pyproject.toml +1 -1
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/README.md +47 -8
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/tenant_context.py +35 -22
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/tests/conftest.py +1 -1
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/LICENSE +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/MANIFEST.in +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/README.rst +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/cardo_python_utils.egg-info/SOURCES.txt +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/cardo_python_utils.egg-info/requires.txt +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/cardo_python_utils.egg-info/top_level.txt +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/choices.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/data_structures.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/db.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/auth.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/templates/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/templates/user_groups_changelist.html +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/user_group.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/views.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/drf.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/ninja.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/utils.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/apps.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/auth/service.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/celery/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/celery/tenant_aware_task.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/routers.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/transaction.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/utils.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/management/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/management/commands/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/management/commands/migrateall.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/management/commands/tenant_aware_command.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/middleware/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/middleware/tenant_aware_http_middleware.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/models/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/models/user_group.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/oidc_settings.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/redis/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/redis/key_function.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/settings.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/storage/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/storage/tenant_aware_storage.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/tests/__init__.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django_utils.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/esma_choices.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/exceptions.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/imports.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/math.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/text.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/time.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/types_hinting.py +0 -0
- {cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/setup.cfg +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cardo-python-utils"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.dev37"
|
|
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"
|
|
@@ -35,20 +35,24 @@ MIDDLEWARE = [
|
|
|
35
35
|
|
|
36
36
|
# Include the database configuration for each tenant in the DATABASES setting.
|
|
37
37
|
# You can use the get_database_configs() function from python_utils.django.db.utils as a helper.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
from python_utils.django.db.utils import get_database_configs
|
|
39
|
+
|
|
40
|
+
for tenant, tenant_db_config in get_database_configs().items():
|
|
41
|
+
DATABASES[tenant] = {
|
|
42
|
+
"ENGINE": "django.db.backends.postgresql",
|
|
43
|
+
"NAME": tenant_db_config["name"],
|
|
44
|
+
"USER": tenant_db_config["user"],
|
|
45
|
+
"PASSWORD": tenant_db_config["password"],
|
|
46
|
+
"HOST": tenant_db_config["host"],
|
|
47
|
+
"PORT": tenant_db_config.get("port", 5432),
|
|
48
|
+
}
|
|
43
49
|
|
|
44
50
|
# If you want to override the database alias to use for local development (when DEBUG is True).
|
|
45
51
|
# By default, the first database defined in DATABASES is used.
|
|
46
52
|
DEVELOPMENT_TENANT = "development"
|
|
47
53
|
|
|
48
54
|
# This is required to use the tenant context when routing database queries
|
|
49
|
-
DATABASE_ROUTERS = [
|
|
50
|
-
"python_utils.django.db.routers.TenantAwareRouter"
|
|
51
|
-
]
|
|
55
|
+
DATABASE_ROUTERS = ["python_utils.django.db.routers.TenantAwareRouter"]
|
|
52
56
|
|
|
53
57
|
# If using celery, set the task class to TenantAwareTask:
|
|
54
58
|
CELERY_TASK_CLS = "python_utils.django.celery.TenantAwareTask"
|
|
@@ -143,3 +147,38 @@ admin.site.has_permission = has_admin_site_permission
|
|
|
143
147
|
## With django-ninja
|
|
144
148
|
|
|
145
149
|
If using `django-ninja`, apart from the settings configured above, auth utils are provided in the django/api/ninja.py module.
|
|
150
|
+
|
|
151
|
+
## Testing
|
|
152
|
+
|
|
153
|
+
In order for tests to work, create the following autouse fixtures:
|
|
154
|
+
|
|
155
|
+
```python3
|
|
156
|
+
import pytest
|
|
157
|
+
|
|
158
|
+
from python_utils.django.tenant_context import TenantContext
|
|
159
|
+
|
|
160
|
+
# Add this, if the test utils of the package are needed
|
|
161
|
+
pytest_plugins = ["python_utils.django.tests"]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
165
|
+
def test_database() -> str:
|
|
166
|
+
return "default"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
170
|
+
def set_tenant_context_session(test_database):
|
|
171
|
+
"""Set tenant context for the entire test session."""
|
|
172
|
+
TenantContext.set(test_database)
|
|
173
|
+
yield
|
|
174
|
+
TenantContext.clear()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@pytest.fixture(autouse=True)
|
|
178
|
+
def set_tenant_context(set_tenant_context_session, test_database):
|
|
179
|
+
"""Ensure tenant context is set for each test (depends on session fixture)."""
|
|
180
|
+
# Re-set in case it was cleared between tests
|
|
181
|
+
if not TenantContext.is_set():
|
|
182
|
+
TenantContext.set(test_database)
|
|
183
|
+
yield
|
|
184
|
+
```
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/tenant_context.py
RENAMED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import sys
|
|
3
3
|
from contextlib import ContextDecorator
|
|
4
|
-
from
|
|
4
|
+
from contextvars import ContextVar
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
from .settings import DATABASES
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
# ContextVar propagates automatically to async threads via sync_to_async
|
|
12
|
+
_tenant_var: ContextVar[Optional[str]] = ContextVar('tenant', default=None)
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class TenantContext(ContextDecorator):
|
|
14
16
|
"""
|
|
15
17
|
Context manager that sets the current tenant.
|
|
16
18
|
This class can be used in several ways:
|
|
17
|
-
1. As a context manager:
|
|
19
|
+
1. As a context manager (sync and async):
|
|
18
20
|
with TenantContext('tenant'):
|
|
19
21
|
# do something
|
|
22
|
+
|
|
23
|
+
async with TenantContext('tenant'):
|
|
24
|
+
# do something async
|
|
20
25
|
|
|
21
26
|
2. As a decorator:
|
|
22
27
|
@TenantContext('tenant')
|
|
@@ -33,23 +38,32 @@ class TenantContext(ContextDecorator):
|
|
|
33
38
|
|
|
34
39
|
def __init__(self, tenant: str):
|
|
35
40
|
self.tenant = tenant
|
|
41
|
+
self._token = None
|
|
36
42
|
|
|
37
43
|
def __enter__(self):
|
|
38
|
-
TenantContext.set(self.tenant)
|
|
44
|
+
self._token = TenantContext.set(self.tenant)
|
|
45
|
+
return self
|
|
39
46
|
|
|
40
47
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
41
|
-
TenantContext.clear()
|
|
48
|
+
TenantContext.clear(self._token)
|
|
49
|
+
|
|
50
|
+
async def __aenter__(self):
|
|
51
|
+
self._token = TenantContext.set(self.tenant)
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
55
|
+
TenantContext.clear(self._token)
|
|
42
56
|
|
|
43
57
|
@staticmethod
|
|
44
58
|
def is_set() -> bool:
|
|
45
|
-
return
|
|
59
|
+
return _tenant_var.get() is not None
|
|
46
60
|
|
|
47
61
|
@staticmethod
|
|
48
62
|
def get() -> Optional[str]:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
tenant = _tenant_var.get()
|
|
64
|
+
if tenant is None:
|
|
65
|
+
raise RuntimeError("Tenant context is not set.")
|
|
66
|
+
return tenant
|
|
53
67
|
|
|
54
68
|
@staticmethod
|
|
55
69
|
def set(tenant):
|
|
@@ -66,24 +80,23 @@ class TenantContext(ContextDecorator):
|
|
|
66
80
|
logger.error("ERROR: TENANT CONTEXT ALREADY SET")
|
|
67
81
|
logger.error(f"Current tenant: {TenantContext.get()}, new tenant: {tenant}")
|
|
68
82
|
return sys.exit(1)
|
|
69
|
-
|
|
70
83
|
else:
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
# multiple times to the same value.
|
|
75
|
-
logger.info("Tenant context already set to %s", tenant)
|
|
76
|
-
return
|
|
77
|
-
|
|
84
|
+
# If the tenant is already set to the same value, we do nothing and return None.
|
|
85
|
+
return None
|
|
86
|
+
|
|
78
87
|
if tenant not in DATABASES:
|
|
79
88
|
logger.error(f"Tenant '{tenant}' not found in DATABASES settings.")
|
|
80
89
|
return sys.exit(1)
|
|
81
90
|
|
|
82
|
-
|
|
91
|
+
token = _tenant_var.set(tenant)
|
|
83
92
|
logger.info(f"Tenant context set to {tenant}")
|
|
93
|
+
return token
|
|
84
94
|
|
|
85
95
|
@staticmethod
|
|
86
|
-
def clear():
|
|
96
|
+
def clear(token=None):
|
|
87
97
|
if TenantContext.is_set():
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
if token is not None:
|
|
99
|
+
_tenant_var.reset(token)
|
|
100
|
+
else:
|
|
101
|
+
_tenant_var.set(None)
|
|
102
|
+
logger.info("Tenant context cleared")
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/tests/conftest.py
RENAMED
|
@@ -80,7 +80,7 @@ def mock_verify_scopes_ninja():
|
|
|
80
80
|
Patches AuthBearer._verify_scopes, so that no real scope checking is done during tests.
|
|
81
81
|
This is done because scope checking uses route resolution, which the test client does not support.
|
|
82
82
|
"""
|
|
83
|
-
with patch("python_utils.
|
|
83
|
+
with patch("python_utils.django.api.ninja.AuthBearer._verify_scopes") as mock_verify:
|
|
84
84
|
mock_verify.return_value = None
|
|
85
85
|
yield mock_verify
|
|
86
86
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/data_structures.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/admin/views.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/drf.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/ninja.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/api/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/auth/service.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/celery/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/routers.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/transaction.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/db/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/oidc_settings.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/redis/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/settings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev35 → cardo_python_utils-0.5.dev37}/python_utils/django/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|