cardo-python-utils 0.5.dev51__tar.gz → 0.5.dev54__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 (76) hide show
  1. {cardo_python_utils-0.5.dev51/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev54}/PKG-INFO +1 -1
  2. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54/cardo_python_utils.egg-info}/PKG-INFO +1 -1
  3. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/cardo_python_utils.egg-info/SOURCES.txt +1 -0
  4. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/pyproject.toml +1 -1
  5. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/README.md +11 -0
  6. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/api/drf.py +6 -4
  7. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/api/ninja.py +8 -10
  8. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/db/utils.py +2 -2
  9. cardo_python_utils-0.5.dev54/python_utils/django/management/commands/showmigrations.py +37 -0
  10. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/middleware/tenant_aware_http_middleware.py +5 -0
  11. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/esma_choices.py +21 -0
  12. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/LICENSE +0 -0
  13. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/MANIFEST.in +0 -0
  14. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/README.rst +0 -0
  15. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
  16. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/cardo_python_utils.egg-info/requires.txt +0 -0
  17. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/cardo_python_utils.egg-info/top_level.txt +0 -0
  18. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/__init__.py +0 -0
  19. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/choices.py +0 -0
  20. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/data_structures.py +0 -0
  21. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/db.py +0 -0
  22. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/__init__.py +0 -0
  23. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/admin/__init__.py +0 -0
  24. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/admin/auth.py +0 -0
  25. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/admin/templates/__init__.py +0 -0
  26. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/admin/templates/user_groups_changelist.html +0 -0
  27. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/admin/user_group.py +0 -0
  28. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/admin/views.py +0 -0
  29. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/api/__init__.py +0 -0
  30. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/api/utils.py +0 -0
  31. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/apps.py +0 -0
  32. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/auth/service.py +0 -0
  33. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/celery/__init__.py +0 -0
  34. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/celery/tenant_aware_database_scheduler.py +0 -0
  35. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/celery/tenant_aware_task.py +0 -0
  36. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/db/__init__.py +0 -0
  37. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/db/alias.py +0 -0
  38. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/db/routers.py +0 -0
  39. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/db/transaction.py +0 -0
  40. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/management/__init__.py +0 -0
  41. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/management/commands/__init__.py +0 -0
  42. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/management/commands/migrateall.py +0 -0
  43. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/management/commands/shell.py +0 -0
  44. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/management/commands/tenant_aware_command.py +0 -0
  45. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/middleware/__init__.py +0 -0
  46. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/middleware/tenant_aware_websocket_middleware.py +0 -0
  47. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0001_initial.py +0 -0
  48. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0001_initial_squashed_0005_alter_userrole_id.py +0 -0
  49. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0002_auto_20220120_1617.py +0 -0
  50. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0003_auto_20220513_1025.py +0 -0
  51. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0004_auto_20220817_1526.py +0 -0
  52. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0005_alter_userrole_id.py +0 -0
  53. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0006_userrole_organization_and_more.py +0 -0
  54. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0007_user_demo.py +0 -0
  55. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/0008_delete_userrole.py +0 -0
  56. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/migrations/__init__.py +0 -0
  57. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/models/__init__.py +0 -0
  58. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/models/user.py +0 -0
  59. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/models/user_group.py +0 -0
  60. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/oidc_settings.py +0 -0
  61. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/redis/__init__.py +0 -0
  62. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/redis/key_function.py +0 -0
  63. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/settings.py +0 -0
  64. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/storage/__init__.py +0 -0
  65. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/storage/tenant_aware_storage.py +0 -0
  66. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/tenant_context.py +0 -0
  67. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/tests/__init__.py +0 -0
  68. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django/tests/conftest.py +0 -0
  69. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/django_utils.py +0 -0
  70. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/exceptions.py +0 -0
  71. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/imports.py +0 -0
  72. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/math.py +0 -0
  73. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/text.py +0 -0
  74. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/time.py +0 -0
  75. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/python_utils/types_hinting.py +0 -0
  76. {cardo_python_utils-0.5.dev51 → cardo_python_utils-0.5.dev54}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardo-python-utils
3
- Version: 0.5.dev51
3
+ Version: 0.5.dev54
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.dev51
3
+ Version: 0.5.dev54
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
@@ -48,6 +48,7 @@ python_utils/django/management/__init__.py
48
48
  python_utils/django/management/commands/__init__.py
49
49
  python_utils/django/management/commands/migrateall.py
50
50
  python_utils/django/management/commands/shell.py
51
+ python_utils/django/management/commands/showmigrations.py
51
52
  python_utils/django/management/commands/tenant_aware_command.py
52
53
  python_utils/django/middleware/__init__.py
53
54
  python_utils/django/middleware/tenant_aware_http_middleware.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.dev51"
7
+ version = "0.5.dev54"
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"
@@ -170,6 +170,17 @@ def my_function():
170
170
  transaction.on_commit(do_smth, using=TenantContext.get())
171
171
  ```
172
172
 
173
+ ## Explicit database connection
174
+
175
+ If using django.db.connection anywhere in the code, you need to change that to get a tenant-aware connection:
176
+
177
+ ```python3
178
+ from python_utils.django.db.utils import get_connection
179
+ from python_utils.django.tenant_context import TenantContext
180
+
181
+ connection = get_connection(TenantContext.get())
182
+ ```
183
+
173
184
  ## Django Shell
174
185
 
175
186
  This library overrides the shell command of Django, so that it requires the `tenant` arg.
@@ -1,8 +1,8 @@
1
1
  from django.conf import settings
2
- from jwt.exceptions import InvalidTokenError, PyJWKClientError
2
+ from jwt.exceptions import ExpiredSignatureError, InvalidTokenError, PyJWKClientError
3
3
 
4
4
  from rest_framework import authentication
5
- from rest_framework.exceptions import AuthenticationFailed
5
+ from rest_framework.exceptions import AuthenticationFailed, PermissionDenied
6
6
  from rest_framework.permissions import BasePermission
7
7
 
8
8
  from .utils import create_or_update_user, decode_jwt
@@ -14,13 +14,15 @@ class AuthenticationBackend(authentication.TokenAuthentication):
14
14
  def authenticate_credentials(self, token: str):
15
15
  try:
16
16
  payload = decode_jwt(token, audience=self._get_audience())
17
+ except ExpiredSignatureError as e:
18
+ raise AuthenticationFailed("Token has expired.") from e
17
19
  except (InvalidTokenError, PyJWKClientError) as e:
18
- raise AuthenticationFailed(f"Invalid token: {str(e)}") from e
20
+ raise PermissionDenied(f"Invalid token: {str(e)}") from e
19
21
 
20
22
  try:
21
23
  username = payload["preferred_username"]
22
24
  except KeyError as e:
23
- raise AuthenticationFailed(
25
+ raise PermissionDenied(
24
26
  "Invalid token: preferred_username not present."
25
27
  ) from e
26
28
 
@@ -1,12 +1,12 @@
1
1
  import logging
2
2
  from typing import Literal, Optional, Union
3
3
 
4
- from jwt.exceptions import InvalidTokenError, PyJWKClientError
4
+ from jwt.exceptions import ExpiredSignatureError, InvalidTokenError, PyJWKClientError
5
5
 
6
6
  from django.conf import settings
7
7
  from django.http import HttpRequest
8
8
  from ninja.security import HttpBearer
9
- from ninja.errors import AuthenticationError, HttpError
9
+ from ninja.errors import AuthenticationError, AuthorizationError, HttpError
10
10
 
11
11
  from .utils import (
12
12
  acreate_or_update_user,
@@ -42,7 +42,7 @@ class AuthBearer(HttpBearer):
42
42
 
43
43
  def _get_token(self, request: HttpRequest) -> Optional[str]:
44
44
  """
45
- This part of the token validation is similar to what
45
+ This part of the token validation is similar to what
46
46
  django-ninja is doing in HttpBearer.__call__
47
47
  """
48
48
  headers = request.headers
@@ -61,16 +61,16 @@ class AuthBearer(HttpBearer):
61
61
  def _decode_token(self, token: str) -> TokenPayload:
62
62
  try:
63
63
  return decode_jwt(token)
64
+ except ExpiredSignatureError as e:
65
+ raise AuthenticationError("Token has expired.") from e
64
66
  except (InvalidTokenError, PyJWKClientError) as e:
65
- raise AuthenticationError(f"Invalid token: {str(e)}") from e
67
+ raise AuthorizationError(f"Invalid token: {str(e)}") from e
66
68
 
67
69
  def _get_username(self, payload: TokenPayload) -> str:
68
70
  try:
69
71
  return payload["preferred_username"]
70
72
  except KeyError as e:
71
- raise AuthenticationError(
72
- "Invalid token: 'preferred_username' claim not present."
73
- ) from e
73
+ raise AuthorizationError("Invalid token: 'preferred_username' claim not present.") from e
74
74
 
75
75
  def _verify_scopes(self, request, token_payload):
76
76
  allowed_scopes = self._get_view_allowed_scopes(request)
@@ -106,9 +106,7 @@ class AuthBearer(HttpBearer):
106
106
  if operation.methods and method in operation.methods:
107
107
  return operation.view_func
108
108
 
109
- raise Exception(
110
- f"Could not determine the view function for {request.method} {request.path}."
111
- )
109
+ raise Exception(f"Could not determine the view function for {request.method} {request.path}.")
112
110
 
113
111
 
114
112
  class AuthBearerAsync(AuthBearer):
@@ -1,7 +1,7 @@
1
1
  from functools import reduce
2
2
  import json
3
3
  import os
4
- from typing import NotRequired, TypedDict
4
+ from typing import TypedDict
5
5
  from django.db import connections
6
6
 
7
7
 
@@ -10,7 +10,7 @@ class DatabaseConfigData(TypedDict):
10
10
  name: str
11
11
  user: str
12
12
  password: str
13
- port: NotRequired[int]
13
+ port: int | None
14
14
 
15
15
 
16
16
  def get_connection(tenant: str = None):
@@ -0,0 +1,37 @@
1
+ from django.core.management.commands.showmigrations import Command as ShowMigrationsCommand
2
+
3
+ from ...settings import TENANT_DATABASES
4
+ from ...tenant_context import TenantContext
5
+
6
+
7
+ class Command(ShowMigrationsCommand):
8
+ help = "Shows all available migrations 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 show migrations for, either a single tenant name or 'all'.",
18
+ required=True,
19
+ type=str,
20
+ )
21
+
22
+ def handle(self, *args, **options):
23
+ tenant = options["tenant"]
24
+
25
+ if tenant.lower() == "all":
26
+ tenants_to_use = list(TENANT_DATABASES)
27
+ else:
28
+ if tenant not in TENANT_DATABASES:
29
+ self.stdout.write(self.style.ERROR(f"Tenant '{tenant}' not found in DATABASES settings."))
30
+ return
31
+ tenants_to_use = [tenant]
32
+
33
+ for t in tenants_to_use:
34
+ self.stdout.write(self.style.MIGRATE_LABEL(f"\n=== Tenant: {t} ==="))
35
+ options["database"] = t
36
+ with TenantContext(t):
37
+ super().handle(*args, **options)
@@ -10,6 +10,8 @@ from ..tenant_context import TenantContext
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
+ DEFAULT_EXCLUDED_PATHS = ["/", "/healthz", "/healthz/"]
14
+
13
15
 
14
16
  class TenantAwareHttpMiddleware:
15
17
  """
@@ -72,6 +74,9 @@ class TenantAwareHttpMiddleware:
72
74
  """
73
75
  Check if the path should be excluded from tenant handling.
74
76
  """
77
+ if path in DEFAULT_EXCLUDED_PATHS:
78
+ return True
79
+
75
80
  for excluded_path in TENANT_AWARE_EXCLUDED_PATHS:
76
81
  if path.startswith(excluded_path):
77
82
  return True
@@ -42,6 +42,9 @@ class FinancialCreditAssetFinancingPurposeChoices(ChoiceEnum):
42
42
  OTHV = 16, "Other Vehicle"
43
43
  EQUP = 17, "Equipment"
44
44
  PROP = 18, "Property"
45
+ PURC = 20, "Purchase"
46
+ RFTM = 21, "Rate/Term Refinance"
47
+ COUT = 22, "Cash-Out Refinance"
45
48
  OTHR = 100, "Other"
46
49
 
47
50
 
@@ -60,6 +63,9 @@ class InterestRateTypeChoices(ChoiceEnum):
60
63
  MODE = 12, "Modular"
61
64
  OTHR = 13, "Other"
62
65
  FXRT = 14, "Fixed Rate"
66
+ VARI = 15, "Variable (ARM)"
67
+ HYBR = 16, "Hybrid ARM (Fixed-to-Variable)"
68
+ STEP = 17, "Step Rate"
63
69
 
64
70
 
65
71
  class SeniorityChoices(ChoiceEnum):
@@ -86,7 +92,13 @@ class EmploymentStatusChoices(ChoiceEnum):
86
92
  STNT = 6, "Student"
87
93
  PNNR = 7, "Pensioner"
88
94
  NOEM = 8, "No Employment, Obligor is Legal Entity"
95
+ RETI = 9, "Retired"
89
96
  OTHR = 10, "Other"
97
+ UNUM = 11, "Unemployed"
98
+ PATE = 12, "Part-Time Employed"
99
+ CNTR = 13, "Contract / Freelance"
100
+ BOWN = 14, "Business Owner"
101
+ SEAS = 15, "Seasonally Employed"
90
102
 
91
103
 
92
104
  class PrimaryIncomeTypeChoices(ChoiceEnum):
@@ -100,6 +112,15 @@ class PrimaryIncomeTypeChoices(ChoiceEnum):
100
112
  DSPL = 8, "Disposable Income"
101
113
  CORP = 9, "Borrower is legal entity"
102
114
  OTHR = 10, "Other"
115
+ SALR = 11, "Salary"
116
+ SEMP = 12, "Self-Employment Income"
117
+ BUSI = 13, "Business Income"
118
+ RENT = 14, "Rental Income"
119
+ INVT = 15, "Investment Income"
120
+ CNTR = 16, "1099 / Contract Income"
121
+ PENS = 17, "Pension / Retirement"
122
+ SOCS = 18, "Social Security / Disability"
123
+ ROYA = 19, "Royalties"
103
124
 
104
125
 
105
126
  class PrimaryIncomeVerificationChoices(ChoiceEnum):