cardo-python-utils 0.5.dev36__tar.gz → 0.5.dev38__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.dev36/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev38}/PKG-INFO +1 -1
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38/cardo_python_utils.egg-info}/PKG-INFO +1 -1
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/cardo_python_utils.egg-info/SOURCES.txt +1 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/pyproject.toml +1 -1
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/README.md +11 -5
- cardo_python_utils-0.5.dev38/python_utils/django/celery/__init__.py +5 -0
- cardo_python_utils-0.5.dev38/python_utils/django/celery/tenant_aware_database_scheduler.py +207 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/tenant_context.py +8 -9
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/tests/conftest.py +1 -1
- cardo_python_utils-0.5.dev36/python_utils/django/celery/__init__.py +0 -4
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/LICENSE +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/MANIFEST.in +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/README.rst +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/cardo_python_utils.egg-info/requires.txt +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/cardo_python_utils.egg-info/top_level.txt +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/choices.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/data_structures.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/db.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/auth.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/templates/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/templates/user_groups_changelist.html +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/user_group.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/views.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/drf.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/ninja.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/utils.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/apps.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/auth/service.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/celery/tenant_aware_task.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/db/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/db/routers.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/db/transaction.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/db/utils.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/management/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/management/commands/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/management/commands/migrateall.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/management/commands/tenant_aware_command.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/middleware/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/middleware/tenant_aware_http_middleware.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/models/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/models/user_group.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/oidc_settings.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/redis/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/redis/key_function.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/settings.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/storage/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/storage/tenant_aware_storage.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/tests/__init__.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django_utils.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/esma_choices.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/exceptions.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/imports.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/math.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/text.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/time.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/types_hinting.py +0 -0
- {cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/setup.cfg +0 -0
|
@@ -37,6 +37,7 @@ python_utils/django/api/ninja.py
|
|
|
37
37
|
python_utils/django/api/utils.py
|
|
38
38
|
python_utils/django/auth/service.py
|
|
39
39
|
python_utils/django/celery/__init__.py
|
|
40
|
+
python_utils/django/celery/tenant_aware_database_scheduler.py
|
|
40
41
|
python_utils/django/celery/tenant_aware_task.py
|
|
41
42
|
python_utils/django/db/__init__.py
|
|
42
43
|
python_utils/django/db/routers.py
|
|
@@ -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.dev38"
|
|
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,11 +35,17 @@ 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.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-tenant Celery Beat scheduler.
|
|
3
|
+
|
|
4
|
+
Reads periodic tasks from every tenant database and injects the ``tenant``
|
|
5
|
+
kwarg so that ``TenantAwareTask`` can route each execution to the correct
|
|
6
|
+
database. Static beat-schedule entries (``CELERY_BEAT_SCHEDULE``) are also
|
|
7
|
+
run once per tenant.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
|
|
12
|
+
from celery.utils.log import get_logger
|
|
13
|
+
from django.db import close_old_connections, transaction
|
|
14
|
+
from django.db.utils import DatabaseError, InterfaceError
|
|
15
|
+
from django_celery_beat.models import PeriodicTask, PeriodicTasks
|
|
16
|
+
from django_celery_beat.schedulers import DatabaseScheduler, ModelEntry
|
|
17
|
+
|
|
18
|
+
from ..settings import TENANT_DATABASES, TENANT_KEY
|
|
19
|
+
from ..tenant_context import TenantContext
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
debug, info, warning = logger.debug, logger.info, logger.warning
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TenantAwareModelEntry(ModelEntry):
|
|
26
|
+
"""
|
|
27
|
+
A schedule entry that remembers which tenant it belongs to and
|
|
28
|
+
injects the tenant kwarg when the task is applied.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, model, app=None, tenant=None):
|
|
32
|
+
self.tenant = tenant
|
|
33
|
+
super().__init__(model, app=app)
|
|
34
|
+
# Prefix the name so entries from different tenants don't collide.
|
|
35
|
+
self.name = f"{self.tenant}::{self.name}"
|
|
36
|
+
|
|
37
|
+
@contextmanager
|
|
38
|
+
def _ensure_tenant_context(self):
|
|
39
|
+
"""Set TenantContext only when not already active (TenantContext is not re-entrant)."""
|
|
40
|
+
if TenantContext.is_set():
|
|
41
|
+
yield
|
|
42
|
+
else:
|
|
43
|
+
with TenantContext(self.tenant):
|
|
44
|
+
yield
|
|
45
|
+
|
|
46
|
+
def _disable(self, model):
|
|
47
|
+
"""Route the save to the correct tenant database."""
|
|
48
|
+
with self._ensure_tenant_context():
|
|
49
|
+
super()._disable(model)
|
|
50
|
+
|
|
51
|
+
def save(self):
|
|
52
|
+
"""Persist last_run_at / total_run_count to the correct tenant database."""
|
|
53
|
+
with self._ensure_tenant_context():
|
|
54
|
+
super().save()
|
|
55
|
+
|
|
56
|
+
def __next__(self):
|
|
57
|
+
self.model.last_run_at = self._default_now()
|
|
58
|
+
self.model.total_run_count += 1
|
|
59
|
+
self.model.no_changes = True
|
|
60
|
+
return self.__class__(self.model, app=self.app, tenant=self.tenant)
|
|
61
|
+
|
|
62
|
+
next = __next__
|
|
63
|
+
|
|
64
|
+
def __repr__(self):
|
|
65
|
+
return f"<TenantAwareModelEntry: [{self.tenant}] {self.task} {self.schedule}>"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TenantAwareDatabaseScheduler(DatabaseScheduler):
|
|
69
|
+
"""
|
|
70
|
+
``DatabaseScheduler`` subclass that aggregates periodic tasks across
|
|
71
|
+
**all** tenant databases.
|
|
72
|
+
|
|
73
|
+
For every tick it:
|
|
74
|
+
|
|
75
|
+
1. Reads ``PeriodicTask`` rows from each tenant DB.
|
|
76
|
+
2. Wraps them in ``TenantAwareModelEntry`` (which carries the tenant name).
|
|
77
|
+
3. When a task is due, injects ``tenant=<name>`` into the task kwargs
|
|
78
|
+
so that ``TenantAwareTask`` can set the context on the worker side.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
Entry = TenantAwareModelEntry
|
|
82
|
+
|
|
83
|
+
def __init__(self, *args, **kwargs):
|
|
84
|
+
self._last_timestamps: dict[str, object] = {}
|
|
85
|
+
super().__init__(*args, **kwargs)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _get_tenant_databases() -> set[str]:
|
|
89
|
+
return TENANT_DATABASES
|
|
90
|
+
|
|
91
|
+
def all_as_schedule(self):
|
|
92
|
+
"""Build the combined schedule dict from all tenant databases."""
|
|
93
|
+
debug("TenantAwareDatabaseScheduler: Fetching schedule from all tenants")
|
|
94
|
+
combined: dict[str, TenantAwareModelEntry] = {}
|
|
95
|
+
|
|
96
|
+
for tenant in self._get_tenant_databases():
|
|
97
|
+
with TenantContext(tenant):
|
|
98
|
+
try:
|
|
99
|
+
for model in PeriodicTask.objects.enabled():
|
|
100
|
+
try:
|
|
101
|
+
entry = TenantAwareModelEntry(model, app=self.app, tenant=tenant)
|
|
102
|
+
combined[entry.name] = entry
|
|
103
|
+
except ValueError as exc:
|
|
104
|
+
logger.warning(
|
|
105
|
+
"TenantAwareDatabaseScheduler: skipping malformed periodic task '%s' in tenant '%s': %r",
|
|
106
|
+
model.name,
|
|
107
|
+
tenant,
|
|
108
|
+
exc,
|
|
109
|
+
)
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
logger.exception(
|
|
112
|
+
"TenantAwareDatabaseScheduler: error reading tenant %s: %r",
|
|
113
|
+
tenant,
|
|
114
|
+
exc,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return combined
|
|
118
|
+
|
|
119
|
+
def schedule_changed(self):
|
|
120
|
+
"""Check whether *any* tenant database has had a schedule change."""
|
|
121
|
+
changed = False
|
|
122
|
+
try:
|
|
123
|
+
close_old_connections()
|
|
124
|
+
|
|
125
|
+
for tenant in self._get_tenant_databases():
|
|
126
|
+
try:
|
|
127
|
+
transaction.commit(using=tenant)
|
|
128
|
+
except transaction.TransactionManagementError:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
with TenantContext(tenant):
|
|
133
|
+
ts = PeriodicTasks.last_change()
|
|
134
|
+
last = self._last_timestamps.get(tenant)
|
|
135
|
+
if ts and ts > (last if last else ts):
|
|
136
|
+
changed = True
|
|
137
|
+
self._last_timestamps[tenant] = ts
|
|
138
|
+
except (DatabaseError, InterfaceError) as exc:
|
|
139
|
+
logger.warning(
|
|
140
|
+
"TenantAwareDatabaseScheduler: error checking schedule_changed for tenant %s: %r",
|
|
141
|
+
tenant,
|
|
142
|
+
exc,
|
|
143
|
+
)
|
|
144
|
+
except (DatabaseError, InterfaceError) as exc:
|
|
145
|
+
logger.exception("Database error in schedule_changed: %r", exc)
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
return changed
|
|
149
|
+
|
|
150
|
+
def is_due(self, entry):
|
|
151
|
+
"""Wrap in TenantContext so that any model.save() calls within
|
|
152
|
+
(e.g. disabling one-off tasks) are routed to the correct tenant DB."""
|
|
153
|
+
tenant = getattr(entry, "tenant", None)
|
|
154
|
+
if tenant:
|
|
155
|
+
with TenantContext(tenant):
|
|
156
|
+
return entry.is_due()
|
|
157
|
+
return entry.is_due()
|
|
158
|
+
|
|
159
|
+
def apply_entry(self, entry, producer=None):
|
|
160
|
+
"""Inject the ``tenant`` kwarg before dispatching the task so that
|
|
161
|
+
``TenantAwareTask.__call__`` can set the tenant context on the worker.
|
|
162
|
+
|
|
163
|
+
Built-in tasks like ``celery.backend_cleanup`` are skipped because
|
|
164
|
+
they are not ``TenantAwareTask`` subclasses and don't accept the kwarg.
|
|
165
|
+
"""
|
|
166
|
+
tenant = getattr(entry, "tenant", None)
|
|
167
|
+
if tenant and entry.task != "celery.backend_cleanup":
|
|
168
|
+
extra_kwargs = dict(entry.kwargs or {})
|
|
169
|
+
extra_kwargs[TENANT_KEY] = tenant
|
|
170
|
+
entry.kwargs = extra_kwargs
|
|
171
|
+
super().apply_entry(entry, producer=producer)
|
|
172
|
+
|
|
173
|
+
def setup_schedule(self):
|
|
174
|
+
# Skip install_default_entries: it calls update_from_dict →
|
|
175
|
+
# Entry.from_entry which writes via _default_manager (no tenant
|
|
176
|
+
# routing). The celery.backend_cleanup task can be added to
|
|
177
|
+
# CELERY_BEAT_SCHEDULE in settings if needed.
|
|
178
|
+
self._update_static_schedule_per_tenant()
|
|
179
|
+
|
|
180
|
+
def _update_static_schedule_per_tenant(self):
|
|
181
|
+
"""
|
|
182
|
+
For each entry in ``app.conf.beat_schedule``, create a
|
|
183
|
+
``PeriodicTask`` row in every tenant database so that static
|
|
184
|
+
cron jobs run once per tenant.
|
|
185
|
+
|
|
186
|
+
Uses ``TenantContext`` so that all DB operations inside
|
|
187
|
+
``_unpack_fields`` (schedule model lookups and saves) are
|
|
188
|
+
routed to the correct tenant database.
|
|
189
|
+
"""
|
|
190
|
+
beat_schedule = self.app.conf.beat_schedule or {}
|
|
191
|
+
if not beat_schedule:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
for tenant in self._get_tenant_databases():
|
|
195
|
+
with TenantContext(tenant):
|
|
196
|
+
for name, entry_fields in beat_schedule.items():
|
|
197
|
+
try:
|
|
198
|
+
defaults = ModelEntry._unpack_fields(**entry_fields)
|
|
199
|
+
PeriodicTask.objects.update_or_create(name=name, defaults=defaults)
|
|
200
|
+
except Exception as exc:
|
|
201
|
+
logger.exception(
|
|
202
|
+
"TenantAwareDatabaseScheduler: could not create "
|
|
203
|
+
"static schedule entry '%s' for tenant '%s': %r",
|
|
204
|
+
name,
|
|
205
|
+
tenant,
|
|
206
|
+
exc,
|
|
207
|
+
)
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/tenant_context.py
RENAMED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import sys
|
|
3
2
|
from contextlib import ContextDecorator
|
|
4
3
|
from contextvars import ContextVar
|
|
5
4
|
from typing import Optional
|
|
@@ -9,7 +8,7 @@ from .settings import DATABASES
|
|
|
9
8
|
logger = logging.getLogger(__name__)
|
|
10
9
|
|
|
11
10
|
# ContextVar propagates automatically to async threads via sync_to_async
|
|
12
|
-
_tenant_var: ContextVar[Optional[str]] = ContextVar(
|
|
11
|
+
_tenant_var: ContextVar[Optional[str]] = ContextVar("tenant", default=None)
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class TenantContext(ContextDecorator):
|
|
@@ -19,7 +18,7 @@ class TenantContext(ContextDecorator):
|
|
|
19
18
|
1. As a context manager (sync and async):
|
|
20
19
|
with TenantContext('tenant'):
|
|
21
20
|
# do something
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
async with TenantContext('tenant'):
|
|
24
23
|
# do something async
|
|
25
24
|
|
|
@@ -77,16 +76,16 @@ class TenantContext(ContextDecorator):
|
|
|
77
76
|
# The same applies to a single task, it is not allowed to set the
|
|
78
77
|
# tenant in the same task to a different value.
|
|
79
78
|
if TenantContext.get() != tenant:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
raise RuntimeError(
|
|
80
|
+
f"Tenant context already set to '{TenantContext.get()}', "
|
|
81
|
+
f"cannot change to '{tenant}' within the same request/task lifecycle."
|
|
82
|
+
)
|
|
83
83
|
else:
|
|
84
84
|
# If the tenant is already set to the same value, we do nothing and return None.
|
|
85
85
|
return None
|
|
86
86
|
|
|
87
87
|
if tenant not in DATABASES:
|
|
88
|
-
|
|
89
|
-
return sys.exit(1)
|
|
88
|
+
raise ValueError(f"Tenant '{tenant}' not found in DATABASES settings.")
|
|
90
89
|
|
|
91
90
|
token = _tenant_var.set(tenant)
|
|
92
91
|
logger.info(f"Tenant context set to {tenant}")
|
|
@@ -99,4 +98,4 @@ class TenantContext(ContextDecorator):
|
|
|
99
98
|
_tenant_var.reset(token)
|
|
100
99
|
else:
|
|
101
100
|
_tenant_var.set(None)
|
|
102
|
-
logger.info("Tenant context cleared")
|
|
101
|
+
logger.info("Tenant context cleared")
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/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
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/data_structures.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/admin/views.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/drf.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/ninja.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/api/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/auth/service.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/db/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/db/routers.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/db/transaction.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/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.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/oidc_settings.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/redis/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/python_utils/django/settings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev36 → cardo_python_utils-0.5.dev38}/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
|