amsdal 0.3.1__cp310-cp310-macosx_10_9_universal2.whl → 0.3.3__cp310-cp310-macosx_10_9_universal2.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.

Potentially problematic release.


This version of amsdal might be problematic. Click here for more details.

Files changed (87) hide show
  1. amsdal/__about__.py +1 -1
  2. amsdal/cloud/__init__.cpython-310-darwin.so +0 -0
  3. amsdal/cloud/client.cpython-310-darwin.so +0 -0
  4. amsdal/cloud/constants.cpython-310-darwin.so +0 -0
  5. amsdal/cloud/enums.cpython-310-darwin.so +0 -0
  6. amsdal/cloud/models/__init__.cpython-310-darwin.so +0 -0
  7. amsdal/cloud/models/base.cpython-310-darwin.so +0 -0
  8. amsdal/cloud/services/__init__.cpython-310-darwin.so +0 -0
  9. amsdal/cloud/services/actions/__init__.cpython-310-darwin.so +0 -0
  10. amsdal/cloud/services/actions/add_allowlist_ip.cpython-310-darwin.so +0 -0
  11. amsdal/cloud/services/actions/add_basic_auth.cpython-310-darwin.so +0 -0
  12. amsdal/cloud/services/actions/add_dependency.cpython-310-darwin.so +0 -0
  13. amsdal/cloud/services/actions/add_secret.cpython-310-darwin.so +0 -0
  14. amsdal/cloud/services/actions/base.cpython-310-darwin.so +0 -0
  15. amsdal/cloud/services/actions/create_deploy.cpython-310-darwin.so +0 -0
  16. amsdal/cloud/services/actions/create_env.cpython-310-darwin.so +0 -0
  17. amsdal/cloud/services/actions/create_session.cpython-310-darwin.so +0 -0
  18. amsdal/cloud/services/actions/delete_allowlist_ip.cpython-310-darwin.so +0 -0
  19. amsdal/cloud/services/actions/delete_basic_auth.cpython-310-darwin.so +0 -0
  20. amsdal/cloud/services/actions/delete_dependency.cpython-310-darwin.so +0 -0
  21. amsdal/cloud/services/actions/delete_env.cpython-310-darwin.so +0 -0
  22. amsdal/cloud/services/actions/delete_secret.cpython-310-darwin.so +0 -0
  23. amsdal/cloud/services/actions/destroy_deploy.cpython-310-darwin.so +0 -0
  24. amsdal/cloud/services/actions/expose_db.cpython-310-darwin.so +0 -0
  25. amsdal/cloud/services/actions/get_basic_auth_credentials.cpython-310-darwin.so +0 -0
  26. amsdal/cloud/services/actions/get_monitoring_info.cpython-310-darwin.so +0 -0
  27. amsdal/cloud/services/actions/list_dependencies.cpython-310-darwin.so +0 -0
  28. amsdal/cloud/services/actions/list_deploys.cpython-310-darwin.so +0 -0
  29. amsdal/cloud/services/actions/list_envs.cpython-310-darwin.so +0 -0
  30. amsdal/cloud/services/actions/list_secrets.cpython-310-darwin.so +0 -0
  31. amsdal/cloud/services/actions/manager.cpython-310-darwin.so +0 -0
  32. amsdal/cloud/services/actions/signup_action.cpython-310-darwin.so +0 -0
  33. amsdal/cloud/services/actions/update_deploy.cpython-310-darwin.so +0 -0
  34. amsdal/cloud/services/auth/__init__.cpython-310-darwin.so +0 -0
  35. amsdal/cloud/services/auth/base.cpython-310-darwin.so +0 -0
  36. amsdal/cloud/services/auth/credentials.cpython-310-darwin.so +0 -0
  37. amsdal/cloud/services/auth/manager.cpython-310-darwin.so +0 -0
  38. amsdal/cloud/services/auth/signup_service.cpython-310-darwin.so +0 -0
  39. amsdal/cloud/services/auth/token.cpython-310-darwin.so +0 -0
  40. amsdal/contrib/__init__.cpython-310-darwin.so +0 -0
  41. amsdal/contrib/auth/decorators/__init__.py +22 -7
  42. amsdal/contrib/auth/lifecycle/consumer.py +172 -0
  43. amsdal/contrib/auth/lifecycle/consumer.pyi +36 -0
  44. amsdal/contrib/auth/models/login_session/hooks/pre_init.py +14 -10
  45. amsdal/contrib/auth/models/user/hooks/post_init.py +19 -0
  46. amsdal/contrib/frontend_configs/lifecycle/consumer.py +45 -0
  47. amsdal/contrib/frontend_configs/lifecycle/consumer.pyi +12 -0
  48. amsdal/fixtures/__init__.cpython-310-darwin.so +0 -0
  49. amsdal/fixtures/manager.cpython-310-darwin.so +0 -0
  50. amsdal/fixtures/manager.pyi +102 -0
  51. amsdal/manager.cpython-310-darwin.so +0 -0
  52. amsdal/manager.pyi +170 -1
  53. amsdal/migration/__init__.cpython-310-darwin.so +0 -0
  54. amsdal/migration/base_migration_schemas.cpython-310-darwin.so +0 -0
  55. amsdal/migration/data_classes.cpython-310-darwin.so +0 -0
  56. amsdal/migration/executors/__init__.cpython-310-darwin.so +0 -0
  57. amsdal/migration/executors/base.cpython-310-darwin.so +0 -0
  58. amsdal/migration/executors/base.pyi +12 -0
  59. amsdal/migration/executors/default_executor.cpython-310-darwin.so +0 -0
  60. amsdal/migration/executors/default_executor.pyi +88 -1
  61. amsdal/migration/executors/state_executor.cpython-310-darwin.so +0 -0
  62. amsdal/migration/file_migration_executor.cpython-310-darwin.so +0 -0
  63. amsdal/migration/file_migration_generator.cpython-310-darwin.so +0 -0
  64. amsdal/migration/file_migration_store.cpython-310-darwin.so +0 -0
  65. amsdal/migration/file_migration_writer.cpython-310-darwin.so +0 -0
  66. amsdal/migration/migrations.cpython-310-darwin.so +0 -0
  67. amsdal/migration/migrations_loader.cpython-310-darwin.so +0 -0
  68. amsdal/migration/schemas_loaders.cpython-310-darwin.so +0 -0
  69. amsdal/migration/utils.cpython-310-darwin.so +0 -0
  70. amsdal/mixins/__init__.cpython-310-darwin.so +0 -0
  71. amsdal/mixins/build_mixin.cpython-310-darwin.so +0 -0
  72. amsdal/mixins/class_versions_mixin.cpython-310-darwin.so +0 -0
  73. amsdal/mixins/class_versions_mixin.pyi +7 -1
  74. amsdal/schemas/core/file/hooks/pre_create.py +13 -0
  75. amsdal/schemas/core/file/hooks/pre_update.py +13 -0
  76. amsdal/schemas/manager.cpython-310-darwin.so +0 -0
  77. amsdal/services/__init__.cpython-310-darwin.so +0 -0
  78. amsdal/services/transaction_execution.cpython-310-darwin.so +0 -0
  79. amsdal/services/transaction_execution.pyi +16 -0
  80. amsdal/utils/rollback/__init__.py +195 -0
  81. amsdal/utils/rollback/__init__.pyi +16 -0
  82. {amsdal-0.3.1.dist-info → amsdal-0.3.3.dist-info}/METADATA +3 -1
  83. {amsdal-0.3.1.dist-info → amsdal-0.3.3.dist-info}/RECORD +87 -87
  84. {amsdal-0.3.1.dist-info → amsdal-0.3.3.dist-info}/LICENSE.txt +0 -0
  85. {amsdal-0.3.1.dist-info → amsdal-0.3.3.dist-info}/WHEEL +0 -0
  86. {amsdal-0.3.1.dist-info → amsdal-0.3.3.dist-info}/licenses/LICENSE.txt +0 -0
  87. {amsdal-0.3.1.dist-info → amsdal-0.3.3.dist-info}/top_level.txt +0 -0
amsdal/__about__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2023-present
2
2
  #
3
3
  # SPDX-License-Identifier: AMSDAL End User License Agreement
4
- __version__ = '0.3.1'
4
+ __version__ = '0.3.3'
Binary file
Binary file
Binary file
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from collections.abc import Callable
2
3
  from functools import wraps
3
4
  from typing import Any
@@ -7,14 +8,28 @@ from amsdal.contrib.auth.errors import AuthenticationError
7
8
 
8
9
 
9
10
  def require_auth(func: Callable[..., Any]) -> Callable[..., Any]:
10
- @wraps(func)
11
- def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]:
12
- request = AmsdalContextManager().get_context().get('request', None)
11
+ if asyncio.iscoroutinefunction(func):
13
12
 
14
- if not request or not request.user:
15
- msg = 'Authentication required'
16
- raise AuthenticationError(msg)
13
+ @wraps(func)
14
+ async def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]:
15
+ request = AmsdalContextManager().get_context().get('request', None)
17
16
 
18
- return func(*args, **kwargs)
17
+ if not request or not request.user:
18
+ msg = 'Authentication required'
19
+ raise AuthenticationError(msg)
20
+
21
+ return await func(*args, **kwargs)
22
+
23
+ else:
24
+
25
+ @wraps(func)
26
+ def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]:
27
+ request = AmsdalContextManager().get_context().get('request', None)
28
+
29
+ if not request or not request.user:
30
+ msg = 'Authentication required'
31
+ raise AuthenticationError(msg)
32
+
33
+ return func(*args, **kwargs)
19
34
 
20
35
  return wrapper
@@ -2,6 +2,7 @@ import logging
2
2
  from typing import Any
3
3
 
4
4
  import jwt
5
+ from amsdal_data.transactions.decorators import async_transaction
5
6
  from amsdal_data.transactions.decorators import transaction
6
7
  from amsdal_models.classes.helpers.reference_loader import ReferenceLoader
7
8
  from amsdal_models.classes.model import Model
@@ -70,6 +71,54 @@ class CheckAndCreateSuperUserConsumer(LifecycleConsumer):
70
71
  instance.save(force_insert=True)
71
72
  logger.info('Super user created successfully')
72
73
 
74
+ @async_transaction
75
+ async def on_event_async(self) -> None: # type: ignore[override]
76
+ """
77
+ Checks for the existence of a super user and creates one if necessary.
78
+
79
+ This method ensures that a super user exists by checking the email and password
80
+ in the authentication settings. If the super user does not exist, it creates one
81
+ with the necessary permissions.
82
+ """
83
+ from amsdal.contrib.auth.settings import auth_settings
84
+ from models.contrib.permission import Permission # type: ignore[import-not-found]
85
+ from models.contrib.user import User # type: ignore[import-not-found]
86
+
87
+ logger.info('Ensure super user exists')
88
+
89
+ if not (auth_settings.ADMIN_USER_EMAIL and auth_settings.ADMIN_USER_PASSWORD):
90
+ logger.info('Email / password missing for super user - skipping')
91
+ return
92
+
93
+ user = (
94
+ await User.objects.filter(email=auth_settings.ADMIN_USER_EMAIL, _address__object_version=Versions.LATEST)
95
+ .get_or_none()
96
+ .aexecute()
97
+ )
98
+ if user is not None:
99
+ logger.info('Super user already exists - skipping')
100
+ return
101
+
102
+ logger.info("Super user doesn't exist - creating now")
103
+
104
+ access_all_permission = (
105
+ await Permission.objects.filter(
106
+ model='*',
107
+ action='*',
108
+ _address__object_version=Versions.LATEST,
109
+ )
110
+ .get()
111
+ .aexecute()
112
+ )
113
+
114
+ instance = User(
115
+ email=auth_settings.ADMIN_USER_EMAIL,
116
+ password=auth_settings.ADMIN_USER_PASSWORD,
117
+ permissions=[access_all_permission],
118
+ )
119
+ await instance.asave(force_insert=True)
120
+ logger.info('Super user created successfully')
121
+
73
122
 
74
123
  class AuthenticateUserConsumer(LifecycleConsumer):
75
124
  """
@@ -120,6 +169,46 @@ class AuthenticateUserConsumer(LifecycleConsumer):
120
169
 
121
170
  authentication_info.user = user
122
171
 
172
+ async def on_event_async(self, auth_header: str, authentication_info: Any) -> None:
173
+ """
174
+ Authenticates the user using the provided JWT token.
175
+
176
+ This method decodes the JWT token from the authorization header and retrieves
177
+ the corresponding user from the database. If the token is invalid or expired,
178
+ it raises an `AuthenticationError`.
179
+
180
+ Args:
181
+ auth_header (str): The JWT token from the authorization header.
182
+ authentication_info (Any): The authentication information object to update with the user.
183
+ """
184
+ from amsdal.contrib.auth.settings import auth_settings
185
+ from models.contrib.user import User # type: ignore[import-not-found]
186
+
187
+ authentication_info.user = None
188
+ email: str | None
189
+
190
+ try:
191
+ jwt_payload = jwt.decode(
192
+ auth_header,
193
+ auth_settings.AUTH_JWT_KEY, # type: ignore[arg-type]
194
+ algorithms=['HS256'],
195
+ )
196
+ email = jwt_payload['email']
197
+ except jwt.ExpiredSignatureError as exc:
198
+ logger.error('Auth token expired. Defaulting to anonymous user.')
199
+
200
+ msg = 'Auth token has expired.'
201
+ raise AuthenticationError(msg) from exc
202
+ except Exception as exc:
203
+ logger.error('Auth token decode failure. Defaulting to anonymous user.')
204
+
205
+ msg = 'Failed to decode auth token.'
206
+ raise AuthenticationError(msg) from exc
207
+
208
+ user = await User.objects.filter(email=email, _address__object_version=Versions.LATEST).get_or_none().aexecute()
209
+
210
+ authentication_info.user = user
211
+
123
212
 
124
213
  class CheckPermissionConsumer(LifecycleConsumer):
125
214
  """
@@ -155,6 +244,32 @@ class CheckPermissionConsumer(LifecycleConsumer):
155
244
  elif required_permission.action == 'delete':
156
245
  permissions_info.has_delete_permission = False
157
246
 
247
+ async def _async_prepopulate_default_permissions(self, object_class: type[Model], permissions_info: Any) -> None:
248
+ from amsdal.contrib.auth.settings import auth_settings
249
+ from models.contrib.permission import Permission # type: ignore[import-not-found]
250
+
251
+ permissions_info.has_read_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
252
+ permissions_info.has_create_permission = (
253
+ (not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION) if object_class.__name__ != 'LoginSession' else True
254
+ )
255
+ permissions_info.has_update_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
256
+ permissions_info.has_delete_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
257
+
258
+ required_permissions = await Permission.objects.filter(
259
+ model=object_class.__name__,
260
+ _address__object_version=Versions.LATEST,
261
+ ).aexecute()
262
+
263
+ for required_permission in required_permissions:
264
+ if required_permission.action == 'read':
265
+ permissions_info.has_read_permission = False
266
+ elif required_permission.action == 'create':
267
+ permissions_info.has_create_permission = False
268
+ elif required_permission.action == 'update':
269
+ permissions_info.has_update_permission = False
270
+ elif required_permission.action == 'delete':
271
+ permissions_info.has_delete_permission = False
272
+
158
273
  def _check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None:
159
274
  if hasattr(object_class, 'has_permission'):
160
275
  for action in ['read', 'create', 'update', 'delete']:
@@ -185,6 +300,36 @@ class CheckPermissionConsumer(LifecycleConsumer):
185
300
  permissions_info.has_update_permission = True
186
301
  permissions_info.has_delete_permission = True
187
302
 
303
+ async def _async_check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None:
304
+ if hasattr(object_class, 'has_permission'):
305
+ for action in ['read', 'create', 'update', 'delete']:
306
+ setattr(permissions_info, f'has_{action}_permission', object_class.has_permission(user, action))
307
+
308
+ if not user or not getattr(user, 'permissions', None):
309
+ return
310
+
311
+ user_permissions = [
312
+ await ReferenceLoader(p).aload_reference() if isinstance(p, Reference) else p for p in user.permissions
313
+ ]
314
+
315
+ for user_permission in user_permissions:
316
+ if user_permission.model not in [object_class.__name__, '*']:
317
+ continue
318
+
319
+ if user_permission.action == 'read':
320
+ permissions_info.has_read_permission = True
321
+ elif user_permission.action == 'create':
322
+ permissions_info.has_create_permission = True
323
+ elif user_permission.action == 'update':
324
+ permissions_info.has_update_permission = True
325
+ elif user_permission.action == 'delete':
326
+ permissions_info.has_delete_permission = True
327
+ elif user_permission.action == '*':
328
+ permissions_info.has_read_permission = True
329
+ permissions_info.has_create_permission = True
330
+ permissions_info.has_update_permission = True
331
+ permissions_info.has_delete_permission = True
332
+
188
333
  def _check_object_permissions(self, obj: Model, user: Any, permissions_info: Any) -> None:
189
334
  if hasattr(obj, 'has_object_permission'):
190
335
  for action in ['read', 'update', 'delete']:
@@ -220,3 +365,30 @@ class CheckPermissionConsumer(LifecycleConsumer):
220
365
 
221
366
  if obj:
222
367
  self._check_object_permissions(obj, user, permissions_info)
368
+
369
+ async def on_event_async(
370
+ self,
371
+ object_class: type[Model],
372
+ user: Any,
373
+ access_types: list[Any], # noqa: ARG002
374
+ permissions_info: Any,
375
+ obj: Model | None = None,
376
+ ) -> None:
377
+ """
378
+ Main method to check permissions for a given user and object.
379
+
380
+ This method prepopulates default permissions, checks class-level permissions,
381
+ and object-level permissions for the given user and object.
382
+
383
+ Args:
384
+ object_class (type[Model]): The class of the object to check permissions for.
385
+ user (Any): The user to check permissions for.
386
+ access_types (list[Any]): The list of access types to check.
387
+ permissions_info (Any): The permissions information object to update.
388
+ obj (Model | None): The object to check permissions for, if any.
389
+ """
390
+ await self._async_prepopulate_default_permissions(object_class, permissions_info)
391
+ await self._async_check_class_permissions(object_class, user, permissions_info)
392
+
393
+ if obj:
394
+ self._check_object_permissions(obj, user, permissions_info)
@@ -17,6 +17,14 @@ class CheckAndCreateSuperUserConsumer(LifecycleConsumer):
17
17
  """
18
18
  Checks for the existence of a super user and creates one if necessary.
19
19
 
20
+ This method ensures that a super user exists by checking the email and password
21
+ in the authentication settings. If the super user does not exist, it creates one
22
+ with the necessary permissions.
23
+ """
24
+ async def on_event_async(self) -> None:
25
+ """
26
+ Checks for the existence of a super user and creates one if necessary.
27
+
20
28
  This method ensures that a super user exists by checking the email and password
21
29
  in the authentication settings. If the super user does not exist, it creates one
22
30
  with the necessary permissions.
@@ -38,6 +46,18 @@ class AuthenticateUserConsumer(LifecycleConsumer):
38
46
  the corresponding user from the database. If the token is invalid or expired,
39
47
  it raises an `AuthenticationError`.
40
48
 
49
+ Args:
50
+ auth_header (str): The JWT token from the authorization header.
51
+ authentication_info (Any): The authentication information object to update with the user.
52
+ """
53
+ async def on_event_async(self, auth_header: str, authentication_info: Any) -> None:
54
+ """
55
+ Authenticates the user using the provided JWT token.
56
+
57
+ This method decodes the JWT token from the authorization header and retrieves
58
+ the corresponding user from the database. If the token is invalid or expired,
59
+ it raises an `AuthenticationError`.
60
+
41
61
  Args:
42
62
  auth_header (str): The JWT token from the authorization header.
43
63
  authentication_info (Any): The authentication information object to update with the user.
@@ -51,7 +71,9 @@ class CheckPermissionConsumer(LifecycleConsumer):
51
71
  and object-level permissions for a given user and object.
52
72
  """
53
73
  def _prepopulate_default_permissions(self, object_class: type[Model], permissions_info: Any) -> None: ...
74
+ async def _async_prepopulate_default_permissions(self, object_class: type[Model], permissions_info: Any) -> None: ...
54
75
  def _check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None: ...
76
+ async def _async_check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None: ...
55
77
  def _check_object_permissions(self, obj: Model, user: Any, permissions_info: Any) -> None: ...
56
78
  def on_event(self, object_class: type[Model], user: Any, access_types: list[Any], permissions_info: Any, obj: Model | None = None) -> None:
57
79
  """
@@ -60,6 +82,20 @@ class CheckPermissionConsumer(LifecycleConsumer):
60
82
  This method prepopulates default permissions, checks class-level permissions,
61
83
  and object-level permissions for the given user and object.
62
84
 
85
+ Args:
86
+ object_class (type[Model]): The class of the object to check permissions for.
87
+ user (Any): The user to check permissions for.
88
+ access_types (list[Any]): The list of access types to check.
89
+ permissions_info (Any): The permissions information object to update.
90
+ obj (Model | None): The object to check permissions for, if any.
91
+ """
92
+ async def on_event_async(self, object_class: type[Model], user: Any, access_types: list[Any], permissions_info: Any, obj: Model | None = None) -> None:
93
+ """
94
+ Main method to check permissions for a given user and object.
95
+
96
+ This method prepopulates default permissions, checks class-level permissions,
97
+ and object-level permissions for the given user and object.
98
+
63
99
  Args:
64
100
  object_class (type[Model]): The class of the object to check permissions for.
65
101
  user (Any): The user to check permissions for.
@@ -3,9 +3,10 @@ from datetime import timedelta
3
3
  from datetime import timezone
4
4
  from typing import Any
5
5
 
6
- import bcrypt
6
+ # import bcrypt
7
7
  import jwt
8
- from amsdal_utils.models.enums import Versions
8
+
9
+ # from amsdal_utils.models.enums import Versions
9
10
 
10
11
 
11
12
  def pre_init(self, *, is_new_object: bool, kwargs: dict[str, Any]) -> None: # type: ignore[no-untyped-def] # noqa: ARG001
@@ -41,17 +42,20 @@ def pre_init(self, *, is_new_object: bool, kwargs: dict[str, Any]) -> None: # t
41
42
 
42
43
  lowercased_email = email.lower()
43
44
 
44
- from models.contrib.user import User # type: ignore[import-not-found]
45
+ # from models.contrib.user import User # type: ignore[import-not-found]
45
46
 
46
- user = User.objects.filter(email=lowercased_email, _address__object_version=Versions.LATEST).get_or_none().execute()
47
+ # user = User.objects.filter(
48
+ # email=lowercased_email,
49
+ # _address__object_version=Versions.LATEST
50
+ # ).get_or_none().execute()
47
51
 
48
- if not user:
49
- msg = 'Invalid email / password'
50
- raise AuthenticationError(msg)
52
+ # if not user:
53
+ # msg = 'Invalid email / password'
54
+ # raise AuthenticationError(msg)
51
55
 
52
- if not bcrypt.checkpw(password.encode('utf-8') if isinstance(password, str) else password, user.password):
53
- msg = 'Invalid email / password'
54
- raise AuthenticationError(msg)
56
+ # if not bcrypt.checkpw(password.encode('utf-8') if isinstance(password, str) else password, user.password):
57
+ # msg = 'Invalid email / password'
58
+ # raise AuthenticationError(msg)
55
59
 
56
60
  kwargs['password'] = 'validated'
57
61
  expiration_time = datetime.now(tz=timezone.utc) + timedelta(seconds=auth_settings.AUTH_TOKEN_EXPIRATION)
@@ -55,3 +55,22 @@ def pre_update(self) -> None: # type: ignore[no-untyped-def]
55
55
  except ValueError:
56
56
  hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
57
57
  self.password = hashed_password
58
+
59
+
60
+ async def apre_update(self) -> None: # type: ignore[no-untyped-def]
61
+ import bcrypt
62
+
63
+ original_object = await self.arefetch_from_db()
64
+
65
+ password = self.password
66
+
67
+ if original_object.password and password is not None:
68
+ if isinstance(password, str):
69
+ password = password.encode('utf-8')
70
+
71
+ try:
72
+ if not bcrypt.checkpw(password, original_object.password):
73
+ self.password = password
74
+ except ValueError:
75
+ hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
76
+ self.password = hashed_password
@@ -240,3 +240,48 @@ class ProcessResponseConsumer(LifecycleConsumer):
240
240
  )
241
241
  else:
242
242
  response['control'] = populate_frontend_config_with_values(get_default_control(class_name), values)
243
+
244
+ async def on_event_async(
245
+ self,
246
+ request: Any,
247
+ response: dict[str, Any],
248
+ ) -> None:
249
+ """
250
+ Handles the event by extracting the class name and values from the request and response,
251
+ and populates the frontend configuration accordingly.
252
+
253
+ Args:
254
+ request (Any): The request object containing query and path parameters.
255
+ response (dict[str, Any]): The response dictionary to be processed.
256
+
257
+ Returns:
258
+ None
259
+ """
260
+ from models.contrib.frontend_model_config import FrontendModelConfig # type: ignore[import-not-found]
261
+
262
+ class_name = None
263
+ values = {}
264
+ if hasattr(request, 'query_params') and 'class_name' in request.query_params:
265
+ class_name = request.query_params['class_name']
266
+
267
+ if hasattr(request, 'path_params') and 'address' in request.path_params:
268
+ class_name = Address.from_string(request.path_params['address']).class_name
269
+ values = get_values_from_response(response)
270
+
271
+ if class_name and isinstance(response, dict):
272
+ config = (
273
+ await FrontendModelConfig.objects.all()
274
+ .first(
275
+ class_name=class_name,
276
+ _metadata__is_deleted=False,
277
+ _address__object_version=Versions.LATEST,
278
+ )
279
+ .aexecute()
280
+ )
281
+
282
+ if config and config.control:
283
+ response['control'] = populate_frontend_config_with_values(
284
+ config.control.model_dump(exclude_none=True), values
285
+ )
286
+ else:
287
+ response['control'] = populate_frontend_config_with_values(get_default_control(class_name), values)
@@ -77,6 +77,18 @@ class ProcessResponseConsumer(LifecycleConsumer):
77
77
  Handles the event by extracting the class name and values from the request and response,
78
78
  and populates the frontend configuration accordingly.
79
79
 
80
+ Args:
81
+ request (Any): The request object containing query and path parameters.
82
+ response (dict[str, Any]): The response dictionary to be processed.
83
+
84
+ Returns:
85
+ None
86
+ """
87
+ async def on_event_async(self, request: Any, response: dict[str, Any]) -> None:
88
+ """
89
+ Handles the event by extracting the class name and values from the request and response,
90
+ and populates the frontend configuration accordingly.
91
+
80
92
  Args:
81
93
  request (Any): The request object containing query and path parameters.
82
94
  response (dict[str, Any]): The response dictionary to be processed.