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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardo-python-utils
3
- Version: 0.5.dev48
3
+ Version: 0.5.dev50
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,4 +1,4 @@
1
- cardo_python_utils-0.5.dev48.dist-info/licenses/LICENSE,sha256=N-YtxDy8n5A1Mo7JKKItNIlboiK_pMOZ48ojx76jo3g,1046
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=J6zy05R4DC43gYFF9_fYz7T9zxDmFn7IkYh5OByePRY,6213
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=pKKuLczhI-1N5-ccFPeB1YV-n8Uu8LVOAvPVrB1zHeU,1316
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.dev48.dist-info/METADATA,sha256=EimWDluEjRNS_-U--n2S4C4jBu3Vx2nWS6PPnWeo3Ls,3007
66
- cardo_python_utils-0.5.dev48.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
67
- cardo_python_utils-0.5.dev48.dist-info/top_level.txt,sha256=zAx6OfEsjJs8BEW3okSiG_j9gpkI69xWShzum6oBgKI,13
68
- cardo_python_utils-0.5.dev48.dist-info/RECORD,,
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,,
@@ -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
- class TenantAwareTask(Task):
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
- # Celery backend_cleanup doesn't need a tenant and cannot be configured to pass the tenant kwarg
20
- # because it is dynamically generated at @connect_on_app_finalize by celery itself
21
- # ref: celery/app/builtins.py def add_backend_cleanup_task
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)