cardo-python-utils 0.5.dev48__py3-none-any.whl → 0.5.dev50__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.
- {cardo_python_utils-0.5.dev48.dist-info → cardo_python_utils-0.5.dev50.dist-info}/METADATA +1 -1
- {cardo_python_utils-0.5.dev48.dist-info → cardo_python_utils-0.5.dev50.dist-info}/RECORD +8 -7
- python_utils/django/README.md +24 -0
- python_utils/django/celery/tenant_aware_task.py +46 -4
- python_utils/django/management/commands/shell.py +32 -0
- {cardo_python_utils-0.5.dev48.dist-info → cardo_python_utils-0.5.dev50.dist-info}/WHEEL +0 -0
- {cardo_python_utils-0.5.dev48.dist-info → cardo_python_utils-0.5.dev50.dist-info}/licenses/LICENSE +0 -0
- {cardo_python_utils-0.5.dev48.dist-info → cardo_python_utils-0.5.dev50.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
cardo_python_utils-0.5.
|
|
1
|
+
cardo_python_utils-0.5.dev50.dist-info/licenses/LICENSE,sha256=N-YtxDy8n5A1Mo7JKKItNIlboiK_pMOZ48ojx76jo3g,1046
|
|
2
2
|
python_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
python_utils/choices.py,sha256=_sLNkSnQqhg55gGKNRsOQCJ75W6gnz8J8Q00528MEYk,2548
|
|
4
4
|
python_utils/data_structures.py,sha256=ZqkZYPy20zyGYOVhwb9qst4vF_P7X2A9z5E36rMUC6I,16820
|
|
@@ -11,7 +11,7 @@ python_utils/math.py,sha256=p_v8a9nVSe9426nR8H_SM8hOQrkzESVpCnn3gntw7TA,5603
|
|
|
11
11
|
python_utils/text.py,sha256=pw9CZeM_Lcw-6k4GyR-4D1Wix8A7F_V1u1IIZTIazW4,3792
|
|
12
12
|
python_utils/time.py,sha256=7Wei3uJ02Bk-BFRf-e1axoG418XQOhrXPvTwNZgTdnw,9614
|
|
13
13
|
python_utils/types_hinting.py,sha256=QVWzmXRgNxhvln14tEX_FbQYryuVYhjWJ0dVOnlF6G4,120
|
|
14
|
-
python_utils/django/README.md,sha256=
|
|
14
|
+
python_utils/django/README.md,sha256=1_I6P3sB2_nBZ9hb0kUrTI4W4pDOxRAkfSO1iekfEVE,6750
|
|
15
15
|
python_utils/django/__init__.py,sha256=uXyqF-_5gZAlSIKoQkUTedAeBjnUHqh6lR6SzA1DEOM,64
|
|
16
16
|
python_utils/django/apps.py,sha256=vH2Ov8XgavTGKFLSjbH1kvuG7RWQCjeJepw6BSp2o3E,126
|
|
17
17
|
python_utils/django/oidc_settings.py,sha256=JqF-qOfW23JhmmVciN1B7ZV-KI7qrdn5VigTE7E2k_0,4367
|
|
@@ -30,7 +30,7 @@ python_utils/django/api/utils.py,sha256=ycpSnTtGcfdGP1_Hk0P2c8ZNId70xOYtjx1m0nAU
|
|
|
30
30
|
python_utils/django/auth/service.py,sha256=9gMURteyVnfjpZLyrvjAvgglfi2NPwrdia2LDVPfxBs,7475
|
|
31
31
|
python_utils/django/celery/__init__.py,sha256=eqKpBqhClH-7oK-kD1SUEpzt4Gqu7VWLWmhFUktee0A,79
|
|
32
32
|
python_utils/django/celery/tenant_aware_database_scheduler.py,sha256=Qcz8mFhfFcX8Opzb75qvuF9jxwuY8Nsn0gxIBKFaU8w,8087
|
|
33
|
-
python_utils/django/celery/tenant_aware_task.py,sha256=
|
|
33
|
+
python_utils/django/celery/tenant_aware_task.py,sha256=RS-6etWP16vYka0nlY1UbEdriqF8ouW5OML05fBF-LA,2944
|
|
34
34
|
python_utils/django/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
python_utils/django/db/alias.py,sha256=sybxhZsShJdnz13hqEyYLxuaRert9zpRPPi-bAayyM4,574
|
|
36
36
|
python_utils/django/db/routers.py,sha256=Q4YAvr37wgEgL04miPKSREhzFB5LDZrXulqStbqBZeA,252
|
|
@@ -39,6 +39,7 @@ python_utils/django/db/utils.py,sha256=DMrFHTjXOkeDsUTdYOCAnZOU_PHKh0zvx-WXsyn7Z
|
|
|
39
39
|
python_utils/django/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
40
|
python_utils/django/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
41
|
python_utils/django/management/commands/migrateall.py,sha256=j1AA29FOGr5Rwe6Fjm4RMFA1-qtb66z_zRi4e7ocHV0,783
|
|
42
|
+
python_utils/django/management/commands/shell.py,sha256=fGHJ1i-p-TQ9lFLOz0j4dm_HEH0kgTO4FGmtYMNqqK4,967
|
|
42
43
|
python_utils/django/management/commands/tenant_aware_command.py,sha256=MBI7N0BEHZAhK183QmFjjEdDecM060O0nsxg0spH9Mc,2440
|
|
43
44
|
python_utils/django/middleware/__init__.py,sha256=GyhuzaOtaXW13iB9GL0XF7OvEEzKoJzorJzYX597idA,117
|
|
44
45
|
python_utils/django/middleware/tenant_aware_http_middleware.py,sha256=W8U3-nR90b8m_wz5xDJTuImUrPEXvESzYh3VITYMIJU,3825
|
|
@@ -62,7 +63,7 @@ python_utils/django/storage/__init__.py,sha256=mNn2YmD7pkXhBLHMM1444BLsCMq78YdYx
|
|
|
62
63
|
python_utils/django/storage/tenant_aware_storage.py,sha256=5dDes6xLv7_R8hIBbFIzRvPL7HL9K_RM-G6LI8qUSxM,2550
|
|
63
64
|
python_utils/django/tests/__init__.py,sha256=Nkt0a7LEHyjLvuEBZ7113VjjAWJlyZlMy-H-JZ5tNcs,252
|
|
64
65
|
python_utils/django/tests/conftest.py,sha256=KozXmXUWVcDLbkVAb7Aq4sDydGLh2YZkbRa4tkA8Z6U,3167
|
|
65
|
-
cardo_python_utils-0.5.
|
|
66
|
-
cardo_python_utils-0.5.
|
|
67
|
-
cardo_python_utils-0.5.
|
|
68
|
-
cardo_python_utils-0.5.
|
|
66
|
+
cardo_python_utils-0.5.dev50.dist-info/METADATA,sha256=BMJmbLgqqHr5HtaBkeZ-OSFIz-I3o15trwrPqpW71ug,3007
|
|
67
|
+
cardo_python_utils-0.5.dev50.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
68
|
+
cardo_python_utils-0.5.dev50.dist-info/top_level.txt,sha256=zAx6OfEsjJs8BEW3okSiG_j9gpkI69xWShzum6oBgKI,13
|
|
69
|
+
cardo_python_utils-0.5.dev50.dist-info/RECORD,,
|
python_utils/django/README.md
CHANGED
|
@@ -155,6 +155,30 @@ admin.site.has_permission = has_admin_site_permission
|
|
|
155
155
|
|
|
156
156
|
If using `django-ninja`, apart from the settings configured above, auth utils are provided in the django/api/ninja.py module.
|
|
157
157
|
|
|
158
|
+
## Atomic Transactions
|
|
159
|
+
|
|
160
|
+
Django's `transaction.atomic` uses the default database. To make it tenant-aware, use `tenant_atomic`
|
|
161
|
+
|
|
162
|
+
```python3
|
|
163
|
+
from python_utils.django.db.transaction import tenant_atomic
|
|
164
|
+
|
|
165
|
+
@tenant_atomic
|
|
166
|
+
def my_function():
|
|
167
|
+
...
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Django Shell
|
|
171
|
+
|
|
172
|
+
This library overrides the shell command of Django, so that it requires the `tenant` arg.
|
|
173
|
+
This way, the shell is automatically initialized with the context set to the tenant.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
./manage.py shell --tenant=tenant1
|
|
177
|
+
|
|
178
|
+
Starting shell for tenant: -tenant=tenant
|
|
179
|
+
>>>
|
|
180
|
+
```
|
|
181
|
+
|
|
158
182
|
## Testing
|
|
159
183
|
|
|
160
184
|
In order for tests to work, create the following autouse fixtures:
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
|
|
1
3
|
from celery import Task
|
|
2
4
|
|
|
3
5
|
from ..settings import TENANT_KEY
|
|
4
6
|
from ..tenant_context import TenantContext
|
|
5
7
|
|
|
8
|
+
try:
|
|
9
|
+
# If celery_once is installed, use QueueOnce as the base class for TenantAwareTask.
|
|
10
|
+
from celery_once.tasks import QueueOnce
|
|
11
|
+
|
|
12
|
+
TaskClass = QueueOnce
|
|
13
|
+
USE_QUEUE_ONCE = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
TaskClass = Task
|
|
16
|
+
USE_QUEUE_ONCE = False
|
|
6
17
|
|
|
7
|
-
|
|
18
|
+
|
|
19
|
+
class TenantAwareTask(TaskClass):
|
|
8
20
|
#: Enable argument checking.
|
|
9
21
|
#: You can set this to false if you don't want the signature to be
|
|
10
22
|
#: checked when calling the task.
|
|
@@ -13,12 +25,17 @@ class TenantAwareTask(Task):
|
|
|
13
25
|
#: Defaults to :attr:`app.strict_typing <@Celery.strict_typing>`.
|
|
14
26
|
typing = False
|
|
15
27
|
|
|
28
|
+
once = {"graceful": True, "unlock_before_run": False}
|
|
29
|
+
|
|
16
30
|
def __call__(self, *args, **kwargs):
|
|
17
31
|
"""Override the __call__ method to set the tenant name in the thread namespace."""
|
|
18
32
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
33
|
+
# Only clear the lock before the task's execution if the
|
|
34
|
+
# "unlock_before_run" option is True
|
|
35
|
+
if USE_QUEUE_ONCE and self.unlock_before_run():
|
|
36
|
+
key = self.get_key(args, kwargs)
|
|
37
|
+
self.once_backend.clear_lock(key)
|
|
38
|
+
|
|
22
39
|
if self.name != "celery.backend_cleanup":
|
|
23
40
|
tenant = kwargs.pop(TENANT_KEY)
|
|
24
41
|
TenantContext.set(tenant)
|
|
@@ -29,3 +46,28 @@ class TenantAwareTask(Task):
|
|
|
29
46
|
"""Clear the tenant from the thread namespace after the task has returned."""
|
|
30
47
|
TenantContext.clear()
|
|
31
48
|
super().after_return(status, retval, task_id, args, kwargs, einfo)
|
|
49
|
+
|
|
50
|
+
def _get_call_args(self, args, kwargs):
|
|
51
|
+
"""This method is used by QueueOnce, to create the key for the lock."""
|
|
52
|
+
|
|
53
|
+
# Celery backend_cleanup doesn't need a tenant and cannot be configured to pass the tenant kwarg
|
|
54
|
+
# because it is dynamically generated at @connect_on_app_finalize by celery itself
|
|
55
|
+
# ref: celery/app/builtins.py def add_backend_cleanup_task
|
|
56
|
+
if self.name == "celery.backend_cleanup":
|
|
57
|
+
return super()._get_call_args(args, kwargs)
|
|
58
|
+
|
|
59
|
+
# QueueOnce._get_call_args validates the args and kwargs
|
|
60
|
+
# by binding them to the task signature.
|
|
61
|
+
# Since the TENANT_KEY kwarg is not part of the signature,
|
|
62
|
+
# we need to remove it from the kwargs before passing them
|
|
63
|
+
# to QueueOnce._get_call_args.
|
|
64
|
+
# Copy the kwargs so that we don't modify the original kwargs.
|
|
65
|
+
_kwargs = deepcopy(kwargs)
|
|
66
|
+
tenant = _kwargs.pop(TENANT_KEY)
|
|
67
|
+
tenant_kwarg = {TENANT_KEY: tenant}
|
|
68
|
+
task_call_args = super()._get_call_args(args, _kwargs)
|
|
69
|
+
|
|
70
|
+
# Add the tenant kwarg back to the return value
|
|
71
|
+
# since this value is being used to create the key for the lock.
|
|
72
|
+
# We want to lock the task for the tenant that is running it.
|
|
73
|
+
return task_call_args | tenant_kwarg
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from django.core.management.commands.shell import Command as ShellCommand
|
|
2
|
+
|
|
3
|
+
from ...settings import TENANT_DATABASES
|
|
4
|
+
from ...tenant_context import TenantContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Command(ShellCommand):
|
|
8
|
+
help = "Runs a Python interactive interpreter for a specific tenant."
|
|
9
|
+
|
|
10
|
+
def add_arguments(self, parser):
|
|
11
|
+
super().add_arguments(parser)
|
|
12
|
+
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"--tenant",
|
|
15
|
+
action="store",
|
|
16
|
+
dest="tenant",
|
|
17
|
+
help="Specify the tenant to run the shell for.",
|
|
18
|
+
required=True,
|
|
19
|
+
type=str,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def handle(self, **options):
|
|
23
|
+
tenant = options["tenant"]
|
|
24
|
+
|
|
25
|
+
if tenant not in TENANT_DATABASES:
|
|
26
|
+
self.stdout.write(self.style.ERROR(f"Tenant '{tenant}' not found in DATABASES settings."))
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
self.stdout.write(self.style.SUCCESS(f"Starting shell for tenant: {tenant}"))
|
|
30
|
+
|
|
31
|
+
with TenantContext(tenant):
|
|
32
|
+
super().handle(**options)
|
|
File without changes
|
{cardo_python_utils-0.5.dev48.dist-info → cardo_python_utils-0.5.dev50.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev48.dist-info → cardo_python_utils-0.5.dev50.dist-info}/top_level.txt
RENAMED
|
File without changes
|