amsdal 0.4.10__cp312-cp312-macosx_10_13_universal2.whl → 0.5.33__cp312-cp312-macosx_10_13_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.
Files changed (163) hide show
  1. amsdal/Third-Party Materials - AMSDAL Dependencies - License Notices.md +28 -0
  2. amsdal/__about__.py +1 -1
  3. amsdal/__migrations__/0000_initial.py +22 -203
  4. amsdal/__migrations__/0001_create_class_file.py +61 -0
  5. amsdal/__migrations__/0002_create_class_file.py +109 -0
  6. amsdal/__migrations__/0003_update_class_file.py +91 -0
  7. amsdal/__migrations__/0004_update_class_file.py +45 -0
  8. amsdal/cloud/__init__.cpython-312-darwin.so +0 -0
  9. amsdal/cloud/client.cpython-312-darwin.so +0 -0
  10. amsdal/cloud/constants.cpython-312-darwin.so +0 -0
  11. amsdal/cloud/enums.cpython-312-darwin.so +0 -0
  12. amsdal/cloud/models/__init__.cpython-312-darwin.so +0 -0
  13. amsdal/cloud/models/base.cpython-312-darwin.so +0 -0
  14. amsdal/cloud/services/__init__.cpython-312-darwin.so +0 -0
  15. amsdal/cloud/services/actions/__init__.cpython-312-darwin.so +0 -0
  16. amsdal/cloud/services/actions/add_allowlist_ip.cpython-312-darwin.so +0 -0
  17. amsdal/cloud/services/actions/add_basic_auth.cpython-312-darwin.so +0 -0
  18. amsdal/cloud/services/actions/add_dependency.cpython-312-darwin.so +0 -0
  19. amsdal/cloud/services/actions/add_secret.cpython-312-darwin.so +0 -0
  20. amsdal/cloud/services/actions/base.cpython-312-darwin.so +0 -0
  21. amsdal/cloud/services/actions/create_deploy.cpython-312-darwin.so +0 -0
  22. amsdal/cloud/services/actions/create_env.cpython-312-darwin.so +0 -0
  23. amsdal/cloud/services/actions/create_session.cpython-312-darwin.so +0 -0
  24. amsdal/cloud/services/actions/delete_allowlist_ip.cpython-312-darwin.so +0 -0
  25. amsdal/cloud/services/actions/delete_basic_auth.cpython-312-darwin.so +0 -0
  26. amsdal/cloud/services/actions/delete_dependency.cpython-312-darwin.so +0 -0
  27. amsdal/cloud/services/actions/delete_env.cpython-312-darwin.so +0 -0
  28. amsdal/cloud/services/actions/delete_secret.cpython-312-darwin.so +0 -0
  29. amsdal/cloud/services/actions/destroy_deploy.cpython-312-darwin.so +0 -0
  30. amsdal/cloud/services/actions/expose_db.cpython-312-darwin.so +0 -0
  31. amsdal/cloud/services/actions/get_basic_auth_credentials.cpython-312-darwin.so +0 -0
  32. amsdal/cloud/services/actions/get_monitoring_info.cpython-312-darwin.so +0 -0
  33. amsdal/cloud/services/actions/list_dependencies.cpython-312-darwin.so +0 -0
  34. amsdal/cloud/services/actions/list_deploys.cpython-312-darwin.so +0 -0
  35. amsdal/cloud/services/actions/list_envs.cpython-312-darwin.so +0 -0
  36. amsdal/cloud/services/actions/list_secrets.cpython-312-darwin.so +0 -0
  37. amsdal/cloud/services/actions/manager.cpython-312-darwin.so +0 -0
  38. amsdal/cloud/services/actions/signup_action.cpython-312-darwin.so +0 -0
  39. amsdal/cloud/services/actions/update_deploy.cpython-312-darwin.so +0 -0
  40. amsdal/cloud/services/auth/__init__.cpython-312-darwin.so +0 -0
  41. amsdal/cloud/services/auth/base.cpython-312-darwin.so +0 -0
  42. amsdal/cloud/services/auth/credentials.cpython-312-darwin.so +0 -0
  43. amsdal/cloud/services/auth/manager.cpython-312-darwin.so +0 -0
  44. amsdal/cloud/services/auth/signup_service.cpython-312-darwin.so +0 -0
  45. amsdal/cloud/services/auth/token.cpython-312-darwin.so +0 -0
  46. amsdal/configs/main.py +17 -1
  47. amsdal/configs/main.pyi +7 -3
  48. amsdal/contrib/__init__.cpython-312-darwin.so +0 -0
  49. amsdal/contrib/auth/errors.py +36 -0
  50. amsdal/contrib/auth/errors.pyi +12 -0
  51. amsdal/contrib/auth/lifecycle/consumer.py +3 -3
  52. amsdal/contrib/auth/lifecycle/consumer.pyi +3 -0
  53. amsdal/contrib/auth/migrations/0000_initial.py +55 -52
  54. amsdal/contrib/auth/migrations/0001_add_mfa_support.py +188 -0
  55. amsdal/contrib/auth/models/__init__.py +1 -0
  56. amsdal/contrib/auth/models/backup_code.py +85 -0
  57. amsdal/contrib/auth/models/email_mfa_device.py +108 -0
  58. amsdal/contrib/auth/models/login_session.py +117 -0
  59. amsdal/contrib/auth/models/mfa_device.py +86 -0
  60. amsdal/contrib/auth/models/sms_device.py +113 -0
  61. amsdal/contrib/auth/models/totp_device.py +58 -0
  62. amsdal/contrib/auth/models/user.py +50 -0
  63. amsdal/contrib/auth/services/__init__.py +1 -0
  64. amsdal/contrib/auth/services/mfa_device_service.py +544 -0
  65. amsdal/contrib/auth/services/mfa_device_service.pyi +216 -0
  66. amsdal/contrib/auth/services/totp_service.py +358 -0
  67. amsdal/contrib/auth/services/totp_service.pyi +158 -0
  68. amsdal/contrib/auth/settings.py +8 -0
  69. amsdal/contrib/auth/settings.pyi +8 -0
  70. amsdal/contrib/auth/transactions/__init__.py +1 -0
  71. amsdal/contrib/auth/transactions/mfa_device_transactions.py +458 -0
  72. amsdal/contrib/auth/transactions/mfa_device_transactions.pyi +226 -0
  73. amsdal/contrib/auth/transactions/totp_transactions.py +203 -0
  74. amsdal/contrib/auth/transactions/totp_transactions.pyi +113 -0
  75. amsdal/contrib/auth/utils/mfa.py +257 -0
  76. amsdal/contrib/auth/utils/mfa.pyi +119 -0
  77. amsdal/contrib/frontend_configs/conversion/convert.py +32 -5
  78. amsdal/contrib/frontend_configs/migrations/0000_initial.py +154 -183
  79. amsdal/contrib/frontend_configs/migrations/0001_update_frontend_control_config.py +245 -0
  80. amsdal/contrib/frontend_configs/migrations/0002_add_button_and_invoke_actions.py +352 -0
  81. amsdal/contrib/frontend_configs/migrations/0003_create_class_frontendconfigdashboardelement.py +145 -0
  82. amsdal/contrib/frontend_configs/models/frontend_config_control_action.py +57 -1
  83. amsdal/contrib/frontend_configs/models/frontend_config_dashboard.py +51 -0
  84. amsdal/contrib/frontend_configs/models/frontend_control_config.py +69 -46
  85. amsdal/fixtures/__init__.cpython-312-darwin.so +0 -0
  86. amsdal/fixtures/manager.cpython-312-darwin.so +0 -0
  87. amsdal/fixtures/utils.cpython-312-darwin.so +0 -0
  88. amsdal/manager.cpython-312-darwin.so +0 -0
  89. amsdal/manager.pyi +5 -0
  90. amsdal/mixins/__init__.cpython-312-darwin.so +0 -0
  91. amsdal/mixins/class_versions_mixin.cpython-312-darwin.so +0 -0
  92. amsdal/models/core/class_object.py +7 -6
  93. amsdal/models/core/class_property.py +7 -1
  94. amsdal/models/core/file.py +168 -81
  95. amsdal/models/core/storage_metadata.py +15 -0
  96. amsdal/models/mixins.py +31 -0
  97. amsdal/models/types/object.py +3 -3
  98. amsdal/schemas/core/class_object/model.json +20 -0
  99. amsdal/schemas/core/class_property/model.json +19 -0
  100. amsdal/schemas/core/file/properties/validate_data.py +2 -3
  101. amsdal/schemas/core/storage_metadata/model.json +52 -0
  102. amsdal/schemas/interfaces.pyi +1 -1
  103. amsdal/schemas/manager.cpython-312-darwin.so +0 -0
  104. amsdal/schemas/mixins/check_dependencies_mixin.py +23 -8
  105. amsdal/schemas/mixins/check_dependencies_mixin.pyi +5 -2
  106. amsdal/schemas/utils.pyi +2 -2
  107. amsdal/services/__init__.py +11 -0
  108. amsdal/services/__init__.pyi +4 -0
  109. amsdal/services/external_connections.py +262 -0
  110. amsdal/services/external_connections.pyi +190 -0
  111. amsdal/services/external_model_generator.py +350 -0
  112. amsdal/services/external_model_generator.pyi +134 -0
  113. amsdal/services/transaction_execution.cpython-312-darwin.so +0 -0
  114. amsdal/services/transaction_execution.pyi +1 -0
  115. amsdal/storages/__init__.py +20 -0
  116. amsdal/storages/__init__.pyi +8 -0
  117. amsdal/storages/file_system.py +214 -0
  118. amsdal/storages/file_system.pyi +36 -0
  119. amsdal/utils/rollback/__init__.pyi +6 -0
  120. amsdal/utils/tests/enums.py +0 -2
  121. amsdal/utils/tests/helpers.py +213 -381
  122. amsdal/utils/tests/migrations.py +157 -0
  123. {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/METADATA +17 -11
  124. {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/RECORD +131 -124
  125. {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/WHEEL +1 -1
  126. amsdal/__migrations__/0001_datetime_type.py +0 -18
  127. amsdal/__migrations__/0002_fixture_order.py +0 -44
  128. amsdal/__migrations__/0003_schema_type_in_class_meta.py +0 -44
  129. amsdal/contrib/auth/models/login_session.pyi +0 -37
  130. amsdal/contrib/auth/models/permission.pyi +0 -18
  131. amsdal/contrib/auth/models/user.pyi +0 -46
  132. amsdal/contrib/frontend_configs/models/frontend_activator_config.pyi +0 -12
  133. amsdal/contrib/frontend_configs/models/frontend_config_async_validator.pyi +0 -7
  134. amsdal/contrib/frontend_configs/models/frontend_config_control_action.pyi +0 -32
  135. amsdal/contrib/frontend_configs/models/frontend_config_group_validator.pyi +0 -11
  136. amsdal/contrib/frontend_configs/models/frontend_config_option.pyi +0 -8
  137. amsdal/contrib/frontend_configs/models/frontend_config_skip_none_base.pyi +0 -8
  138. amsdal/contrib/frontend_configs/models/frontend_config_slider_option.pyi +0 -9
  139. amsdal/contrib/frontend_configs/models/frontend_config_text_mask.pyi +0 -10
  140. amsdal/contrib/frontend_configs/models/frontend_config_validator.pyi +0 -15
  141. amsdal/contrib/frontend_configs/models/frontend_control_config.pyi +0 -35
  142. amsdal/contrib/frontend_configs/models/frontend_model_config.pyi +0 -9
  143. amsdal/models/__init__.pyi +0 -9
  144. amsdal/models/core/class_object.pyi +0 -24
  145. amsdal/models/core/class_object_meta.py +0 -26
  146. amsdal/models/core/class_object_meta.pyi +0 -15
  147. amsdal/models/core/class_property.pyi +0 -11
  148. amsdal/models/core/class_property_meta.py +0 -15
  149. amsdal/models/core/class_property_meta.pyi +0 -10
  150. amsdal/models/core/file.pyi +0 -104
  151. amsdal/models/core/fixture.pyi +0 -14
  152. amsdal/models/core/option.pyi +0 -8
  153. amsdal/models/core/validator.pyi +0 -8
  154. amsdal/models/types/object.pyi +0 -16
  155. amsdal/schemas/core/class_object_meta/model.json +0 -59
  156. amsdal/schemas/core/class_property_meta/model.json +0 -23
  157. amsdal/services/__init__.cpython-312-darwin.so +0 -0
  158. /amsdal/contrib/auth/{models → services}/__init__.pyi +0 -0
  159. /amsdal/contrib/{frontend_configs/models → auth/transactions}/__init__.pyi +0 -0
  160. /amsdal/{models/core/__init__.pyi → contrib/auth/utils/__init__.py} +0 -0
  161. /amsdal/{models/types → contrib/auth/utils}/__init__.pyi +0 -0
  162. {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/licenses/LICENSE.txt +0 -0
  163. {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/top_level.txt +0 -0
@@ -1,74 +1,97 @@
1
1
  import base64
2
+ import io
3
+ from contextlib import suppress
2
4
  from pathlib import Path
5
+ from typing import IO
6
+ from typing import Any
3
7
  from typing import BinaryIO
4
8
  from typing import ClassVar
5
9
 
6
10
  from amsdal_models.classes.model import Model
11
+ from amsdal_models.storage.backends.db import AsyncFileWrapper
12
+ from amsdal_models.storage.backends.db import DBStorage
13
+ from amsdal_models.storage.base import Storage
14
+ from amsdal_utils.models.data_models.reference import Reference
7
15
  from amsdal_utils.models.enums import ModuleType
8
- from pydantic import field_validator
16
+ from pydantic import PrivateAttr
17
+ from pydantic import model_validator
9
18
  from pydantic.fields import Field
10
19
 
11
20
 
12
21
  class File(Model):
13
22
  __module_type__: ClassVar[ModuleType] = ModuleType.CORE
14
23
  filename: str = Field(title='Filename')
15
- data: bytes = Field(title='Data')
16
- size: float | None = Field(None, title='Size')
24
+ data: bytes | None = Field(default=None, title='Data')
25
+ size: float | None = Field(default=None, title='Size')
26
+ storage_address: Reference | None = Field(default=None, title='Storage Reference')
27
+
28
+ _source: BinaryIO | None = PrivateAttr(default=None)
29
+ _storage: Storage | None = PrivateAttr(default=None)
30
+
31
+ @property
32
+ def storage(self) -> Storage:
33
+ from amsdal.storages import default_storage
34
+
35
+ if self._storage:
36
+ return self._storage
37
+
38
+ if self.storage_address:
39
+ return Storage.from_storage_spec({'storage_class': self.storage_address.ref.resource})
40
+
41
+ return default_storage()
17
42
 
18
43
  def __repr__(self) -> str:
19
- return f'File<{self.filename}>({self.size or len(self.data) or 0} bytes)'
44
+ return f'File<{self.filename}>({self.size or len(self.data or "") or 0} bytes)'
20
45
 
21
46
  def __str__(self) -> str:
22
47
  return repr(self)
23
48
 
24
- async def apre_create(self) -> None:
25
- """
26
- Prepares the object for creation by setting its size attribute.
49
+ def pre_create(self) -> None:
50
+ from amsdal_models.storage.persistence import persist_file
27
51
 
28
- This method calculates the size of the object's data and assigns it to the size attribute.
29
- If the data is None, it defaults to an empty byte string.
52
+ persist_file(self, storage=self.storage)
30
53
 
31
- Args:
32
- None
33
- """
34
- self.size = len(self.data or b'')
54
+ def pre_update(self) -> None:
55
+ from amsdal_models.storage.persistence import persist_file
56
+
57
+ persist_file(self, storage=self.storage)
58
+
59
+ async def apre_create(self) -> None:
60
+ from amsdal_models.storage.persistence import apersist_file
61
+
62
+ await apersist_file(self, storage=self.storage)
35
63
 
36
64
  async def apre_update(self) -> None:
37
- """
38
- Prepares the object for update by setting its size attribute.
65
+ from amsdal_models.storage.persistence import apersist_file
39
66
 
40
- This method calculates the size of the object's data and assigns it to the size attribute.
41
- If the data is None, it defaults to an empty byte string.
67
+ await apersist_file(self, storage=self.storage)
42
68
 
43
- Args:
44
- None
45
- """
46
- self.size = len(self.data or b'')
69
+ @model_validator(mode='before')
70
+ @classmethod
71
+ def validate_model_data(cls, data: Any) -> Any:
72
+ if isinstance(data, dict):
73
+ if 'data' in data:
74
+ if data['data']:
75
+ data['data'] = cls.data_base64_decode(data['data'])
76
+ data['size'] = len(data['data'])
77
+ else:
78
+ data['size'] = 0
79
+ return data
47
80
 
48
- @field_validator('data')
49
81
  @classmethod
50
- def data_base64_decode(cls, v: bytes) -> bytes:
51
- """
52
- Decodes a base64-encoded byte string if it is base64-encoded.
82
+ def data_base64_decode(cls, data: Any) -> bytes:
83
+ if isinstance(data, str):
84
+ data = data.encode('utf-8')
53
85
 
54
- This method checks if the provided byte string is base64-encoded and decodes it if true.
55
- If the byte string is not base64-encoded, it returns the original byte string.
86
+ is_base64: bool = False
56
87
 
57
- Args:
58
- cls: The class this method belongs to.
59
- v (bytes): The byte string to be checked and potentially decoded.
88
+ with suppress(Exception):
89
+ is_base64 = base64.b64encode(base64.b64decode(data)) == data
60
90
 
61
- Returns:
62
- bytes: The decoded byte string if it was base64-encoded, otherwise the original byte string.
63
- """
64
- is_base64: bool = False
65
- try:
66
- is_base64 = base64.b64encode(base64.b64decode(v)) == v
67
- except Exception:
68
- ...
69
91
  if is_base64:
70
- return base64.b64decode(v)
71
- return v
92
+ return base64.b64decode(data)
93
+
94
+ return data
72
95
 
73
96
  @classmethod
74
97
  def from_file(cls, file_or_path: Path | BinaryIO) -> 'File':
@@ -84,73 +107,137 @@ class File(Model):
84
107
  Raises:
85
108
  ValueError: If the provided path is a directory.
86
109
  """
110
+ f: BinaryIO | io.BufferedReader
111
+
87
112
  if isinstance(file_or_path, Path):
88
113
  if file_or_path.is_dir():
89
114
  msg = f'{file_or_path} is a directory'
90
115
  raise ValueError(msg)
91
- data = file_or_path.read_bytes()
116
+ f = file_or_path.open('rb')
92
117
  filename = file_or_path.name
118
+ size = file_or_path.stat().st_size
119
+
93
120
  else:
94
- file_or_path.seek(0)
95
- data = file_or_path.read()
96
- filename = Path(file_or_path.name).name
97
- return cls(data=data, filename=filename) # type: ignore[call-arg]
121
+ f = file_or_path
122
+ filename = Path(getattr(f, 'name', 'unnamed')).name
123
+
124
+ try:
125
+ if f.seekable():
126
+ f.seek(0, io.SEEK_END)
127
+ size = f.tell()
128
+ f.seek(0)
129
+ else:
130
+ size = None
131
+ except (OSError, AttributeError):
132
+ size = None
133
+
134
+ obj = cls(filename=filename, size=size)
135
+ obj._source = f
136
+ return obj
98
137
 
99
- @property
100
- def mimetype(self) -> str | None:
138
+ @classmethod
139
+ def from_bytes(cls, filename: str, data: bytes) -> 'File':
101
140
  """
102
- Returns the MIME type of the file based on its filename.
141
+ Creates a `File` object from a byte string.
103
142
 
104
- This method uses the `mimetypes` module to guess the MIME type of the file.
143
+ Args:
144
+ filename (str): The filename of the file.
145
+ data (bytes): The byte string containing the file data.:
105
146
 
106
147
  Returns:
107
- str | None: The guessed MIME type of the file, or None if it cannot be determined.
148
+ File: The created `File` object.
108
149
  """
109
- import mimetypes
150
+ obj = cls(filename=filename, data=data, size=len(data))
151
+ return obj
110
152
 
111
- return mimetypes.guess_type(self.filename)[0]
112
-
113
- def pre_create(self) -> None:
153
+ def to_file(self, file_or_path: Path | BinaryIO) -> None:
114
154
  """
115
- Prepares the object for creation by setting its size attribute.
116
-
117
- This method calculates the size of the object's data and assigns it to the size attribute.
118
- If the data is None, it defaults to an empty byte string.
155
+ Writes the object's data to a file path or a binary file object.
119
156
 
120
157
  Args:
158
+ file_or_path (Path | BinaryIO): The file path or binary file object where the data will be written.
159
+
160
+ Returns:
121
161
  None
162
+
163
+ Raises:
164
+ ValueError: If the provided path is a directory.
122
165
  """
123
- self.size = len(self.data or b'')
166
+ with self.open() as f:
167
+ if isinstance(file_or_path, Path):
168
+ if file_or_path.is_dir():
169
+ file_or_path = file_or_path / self.name
170
+ file_or_path.write_bytes(f.read()) # type: ignore[union-attr]
171
+ else:
172
+ file_or_path.write(f.read())
173
+ file_or_path.seek(0)
124
174
 
125
- def pre_update(self) -> None:
175
+ def url(self) -> str:
126
176
  """
127
- Prepares the object for update by setting its size attribute.
177
+ Return a URL for this file using its storage_address.
128
178
 
129
- This method calculates the size of the object's data and assigns it to the size attribute.
130
- If the data is None, it defaults to an empty byte string.
179
+ Raises StateError if storage_address is missing.
180
+ """
181
+ return self.storage.url(self)
131
182
 
132
- Args:
133
- None
183
+ def open(self, mode: str = 'rb') -> IO[Any]:
134
184
  """
135
- self.size = len(self.data or b'')
185
+ Open a binary stream for reading (or other modes if supported) using storage_address.
136
186
 
137
- def to_file(self, file_or_path: Path | BinaryIO) -> None:
187
+ Raises StateError if storage_address is missing.
138
188
  """
139
- Writes the object's data to a file path or a binary file object.
189
+ return self.storage.open(self, mode)
140
190
 
141
- Args:
142
- file_or_path (Path | BinaryIO): The file path or binary file object where the data will be written.
191
+ async def aurl(self) -> str:
192
+ """
193
+ Async variant of url().
143
194
 
144
- Returns:
145
- None
195
+ Uses the resolved storage to call aurl(); if the backend does not implement
196
+ async, falls back to the sync url().
197
+ """
198
+ try:
199
+ return await self.storage.aurl(self) # type: ignore[attr-defined]
200
+ except NotImplementedError:
201
+ return self.storage.url(self)
146
202
 
147
- Raises:
148
- ValueError: If the provided path is a directory.
203
+ async def aopen(self, mode: str = 'rb') -> Any:
149
204
  """
150
- if isinstance(file_or_path, Path):
151
- if file_or_path.is_dir():
152
- file_or_path = file_or_path / self.name
153
- file_or_path.write_bytes(self.data) # type: ignore[union-attr]
154
- else:
155
- file_or_path.write(self.data)
156
- file_or_path.seek(0)
205
+ Async variant of open().
206
+
207
+ Uses the resolved storage to call aopen(); if the backend does not implement
208
+ async, falls back to the sync open().
209
+ """
210
+ try:
211
+ return await self.storage.aopen(self, mode)
212
+ except NotImplementedError:
213
+ return AsyncFileWrapper(self.storage.open(self, mode))
214
+
215
+ @property
216
+ def mimetype(self) -> str | None:
217
+ """
218
+ Returns the MIME type of the file based on its filename.
219
+
220
+ This method uses the `mimetypes` module to guess the MIME type of the file.
221
+
222
+ Returns:
223
+ str | None: The guessed MIME type of the file, or None if it cannot be determined.
224
+ """
225
+ import mimetypes
226
+
227
+ return mimetypes.guess_type(self.filename)[0]
228
+
229
+ def read_bytes(self) -> bytes:
230
+ with self.open() as f:
231
+ return f.read()
232
+
233
+ async def aread_bytes(self) -> bytes:
234
+ async with await self.aopen() as f:
235
+ return await f.read()
236
+
237
+ def set_data(self, data: bytes | str) -> None:
238
+ if not isinstance(self.storage, DBStorage):
239
+ msg = 'Cannot set data on a file that is not stored in a database. Use `File.from_bytes` instead.'
240
+ raise ValueError(msg)
241
+
242
+ self.data = self.data_base64_decode(data)
243
+ self.size = len(self.data)
@@ -0,0 +1,15 @@
1
+ from typing import ClassVar
2
+ from typing import Optional
3
+
4
+ from amsdal_models.classes.model import TypeModel
5
+ from amsdal_utils.models.enums import ModuleType
6
+ from pydantic.fields import Field
7
+
8
+
9
+ class StorageMetadata(TypeModel):
10
+ __module_type__: ClassVar[ModuleType] = ModuleType.CORE
11
+ table_name: Optional[str] = Field(None, title='Table name') # noqa: UP007
12
+ db_fields: dict[str, list[str]] | None = Field(None, title='Database fields')
13
+ primary_key: Optional[list[str]] = Field(None, title='Primary key fields') # noqa: UP007
14
+ indexed: Optional[list[list[str]]] = Field(None, title='Indexed') # noqa: UP007
15
+ unique: Optional[list[list[str]]] = Field(None, title='Unique Fields') # noqa: UP007
@@ -0,0 +1,31 @@
1
+ import datetime as _dt
2
+
3
+
4
+ class TimestampMixin:
5
+ created_at: _dt.datetime | None = None
6
+ updated_at: _dt.datetime | None = None
7
+
8
+ def pre_create(self) -> None:
9
+ self.created_at = _dt.datetime.now(tz=_dt.UTC)
10
+ super().pre_create() # type: ignore[misc]
11
+
12
+ async def apre_create(self) -> None:
13
+ self.created_at = _dt.datetime.now(tz=_dt.UTC)
14
+ await super().apre_create() # type: ignore[misc]
15
+
16
+ def pre_update(self) -> None:
17
+ self.updated_at = _dt.datetime.now(tz=_dt.UTC)
18
+
19
+ if not self.created_at:
20
+ _metadata = self.get_metadata() # type: ignore[attr-defined]
21
+ self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)
22
+
23
+ super().pre_update() # type: ignore[misc]
24
+
25
+ async def apre_update(self) -> None:
26
+ self.updated_at = _dt.datetime.now(tz=_dt.UTC)
27
+ if not self.created_at:
28
+ _metadata = await self.aget_metadata() # type: ignore[attr-defined]
29
+ self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)
30
+
31
+ await super().apre_update() # type: ignore[misc]
@@ -11,12 +11,12 @@ from pydantic.functional_validators import field_validator
11
11
 
12
12
  class Object(Model):
13
13
  __module_type__: ClassVar[ModuleType] = ModuleType.TYPE
14
- title: Optional[str] = Field(None, title='Title') # noqa: UP007
15
- type: Optional[str] = Field(None, title='Type') # noqa: UP007
14
+ title: str = Field(title='Title', default='')
15
+ type: str = Field(title='Type', default='object')
16
+ module_type: str = Field(title='Module Type', default=ModuleType.CORE.value)
16
17
  default: Optional[Any] = Field(None, title='Default') # noqa: UP007
17
18
  properties: Optional[dict[str, Optional[Any]]] = Field(None, title='Properties') # noqa: UP007
18
19
  required: Optional[list[str]] = Field(None, title='Required') # noqa: UP007
19
- unique: Optional[list[list[str]]] = Field(None, title='Unique Fields') # noqa: UP007
20
20
  custom_code: Optional[str] = Field(None, title='Custom Code') # noqa: UP007
21
21
  meta_class: Optional[str] = Field(None, title='Meta Class') # noqa: UP007
22
22
 
@@ -2,6 +2,18 @@
2
2
  "title": "ClassObject",
3
3
  "type": "object",
4
4
  "properties": {
5
+ "title": {
6
+ "title": "Title",
7
+ "type": "string"
8
+ },
9
+ "type": {
10
+ "title": "Type",
11
+ "type": "string"
12
+ },
13
+ "module_type": {
14
+ "title": "Module Type",
15
+ "type": "string"
16
+ },
5
17
  "properties": {
6
18
  "title": "Properties",
7
19
  "type": "dictionary",
@@ -21,6 +33,14 @@
21
33
  "type": "string"
22
34
  }
23
35
  },
36
+ "custom_code": {
37
+ "title": "Custom Code",
38
+ "type": "string"
39
+ },
40
+ "storage_metadata": {
41
+ "title": "Storage metadata",
42
+ "type": "StorageMetadata"
43
+ },
24
44
  "meta_class": {
25
45
  "title": "Meta Class",
26
46
  "type": "string",
@@ -2,10 +2,25 @@
2
2
  "title": "ClassProperty",
3
3
  "type": "object",
4
4
  "properties": {
5
+ "title": {
6
+ "title": "Title",
7
+ "type": "string"
8
+ },
5
9
  "type": {
6
10
  "title": "Type",
7
11
  "type": "string"
8
12
  },
13
+ "default": {
14
+ "title": "Default",
15
+ "type": "anything"
16
+ },
17
+ "options": {
18
+ "title": "Options",
19
+ "type": "array",
20
+ "items": {
21
+ "type": "Option"
22
+ }
23
+ },
9
24
  "items": {
10
25
  "title": "Items",
11
26
  "type": "dictionary",
@@ -13,6 +28,10 @@
13
28
  "key": {"type": "string"},
14
29
  "value": {"type": "anything"}
15
30
  }
31
+ },
32
+ "discriminator": {
33
+ "title": "Discriminator",
34
+ "type": "string"
16
35
  }
17
36
  },
18
37
  "required": [
@@ -1,4 +1,5 @@
1
1
  import base64
2
+ from contextlib import suppress
2
3
 
3
4
  from pydantic import field_validator
4
5
 
@@ -21,10 +22,8 @@ def data_base64_decode(cls, v: bytes) -> bytes: # type: ignore[no-untyped-def]
21
22
  """
22
23
  is_base64: bool = False
23
24
 
24
- try:
25
+ with suppress(Exception):
25
26
  is_base64 = base64.b64encode(base64.b64decode(v)) == v
26
- except Exception:
27
- ...
28
27
 
29
28
  if is_base64:
30
29
  return base64.b64decode(v)
@@ -0,0 +1,52 @@
1
+ {
2
+ "title": "StorageMetadata",
3
+ "type": "object",
4
+ "properties": {
5
+ "table_name": {
6
+ "title": "Table name",
7
+ "type": "string"
8
+ },
9
+ "db_fields": {
10
+ "title": "Database fields",
11
+ "type": "dictionary",
12
+ "items": {
13
+ "key": {"type": "string"},
14
+ "value": {
15
+ "type": "array",
16
+ "items": {
17
+ "type": "string"
18
+ }
19
+ }
20
+ }
21
+ },
22
+ "primary_key": {
23
+ "title": "Primary key fields",
24
+ "type": "array",
25
+ "items": {
26
+ "type": "string"
27
+ }
28
+ },
29
+ "indexed": {
30
+ "title": "Indexed",
31
+ "type": "array",
32
+ "items": {
33
+ "type": "array",
34
+ "items": {
35
+ "type": "string"
36
+ }
37
+ }
38
+ },
39
+ "unique": {
40
+ "title": "Unique Fields",
41
+ "type": "array",
42
+ "items": {
43
+ "type": "array",
44
+ "items": {
45
+ "type": "string"
46
+ }
47
+ }
48
+ }
49
+ },
50
+ "required": [],
51
+ "meta_class": "TypeMeta"
52
+ }
@@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
3
3
  from amsdal_utils.schemas.schema import ObjectSchema as ObjectSchema
4
4
  from typing import TypeAlias
5
5
 
6
- ModulePathType: TypeAlias
6
+ ModulePathType: TypeAlias = str
7
7
 
8
8
  class BaseSchemaLoader(ABC, metaclass=abc.ABCMeta):
9
9
  @property
@@ -74,12 +74,17 @@ class CheckDependenciesMixin:
74
74
  ):
75
75
  for _schema in schemas:
76
76
  for _dependency in self.get_dependency_type_names(_schema):
77
- if _dependency not in _defined_schemas:
78
- exc_msg = f'Class {_dependency} ({source}) is undefined! This class is set as dependency for {_schema.title}' # noqa: E501
79
- raise AmsdalValidationError(exc_msg)
77
+ _dependencies = [_dependency]
78
+ if '|' in _dependency:
79
+ _dependencies = [dep.strip() for dep in _dependency.split('|')]
80
80
 
81
- @staticmethod
82
- def get_dependency_type_names(schema: ObjectSchema) -> set[str]:
81
+ for _d in _dependencies:
82
+ if _d not in _defined_schemas:
83
+ exc_msg = f'Class {_d} ({source}) is undefined! This class is set as dependency for {_schema.title}' # noqa: E501
84
+ raise AmsdalValidationError(exc_msg)
85
+
86
+ @classmethod
87
+ def get_dependency_type_names(cls, schema: ObjectSchema) -> set[str]:
83
88
  """
84
89
  Returns a set of dependency type names for the given schema.
85
90
 
@@ -97,19 +102,29 @@ class CheckDependenciesMixin:
97
102
  }
98
103
 
99
104
  for _property in schema.properties.values() if schema.properties else []:
105
+ if cls._is_enum(_property):
106
+ continue
107
+
100
108
  _dependencies.add(_property.type)
101
109
 
102
110
  if _property.type == CoreTypes.ARRAY and isinstance(_property.items, TypeData):
103
- _dependencies.add(_property.items.type)
111
+ if not cls._is_enum(_property.items):
112
+ _dependencies.add(_property.items.type)
104
113
  elif _property.type == CoreTypes.DICTIONARY:
105
114
  if isinstance(_property.items, LegacyDictSchema):
106
115
  _dependencies.add(_property.items.key_type)
107
116
  _dependencies.add(_property.items.value_type)
108
117
  elif isinstance(_property.items, DictSchema):
109
- _dependencies.add(_property.items.key.type)
110
- _dependencies.add(_property.items.value.type)
118
+ if not cls._is_enum(_property.items.key):
119
+ _dependencies.add(_property.items.key.type)
120
+ if not cls._is_enum(_property.items.value):
121
+ _dependencies.add(_property.items.value.type)
111
122
 
112
123
  # remove self reference
113
124
  _dependencies.discard(schema.title)
114
125
 
115
126
  return _dependencies
127
+
128
+ @classmethod
129
+ def _is_enum(cls, _type: TypeData) -> bool:
130
+ return bool(hasattr(_type, 'enum') and _type.enum and _type.options)
@@ -1,3 +1,4 @@
1
+ from amsdal_utils.models.data_models.core import TypeData
1
2
  from amsdal_utils.schemas.schema import ObjectSchema as ObjectSchema
2
3
 
3
4
  class CheckDependenciesMixin:
@@ -26,8 +27,8 @@ class CheckDependenciesMixin:
26
27
  Returns:
27
28
  None
28
29
  """
29
- @staticmethod
30
- def get_dependency_type_names(schema: ObjectSchema) -> set[str]:
30
+ @classmethod
31
+ def get_dependency_type_names(cls, schema: ObjectSchema) -> set[str]:
31
32
  """
32
33
  Returns a set of dependency type names for the given schema.
33
34
 
@@ -40,3 +41,5 @@ class CheckDependenciesMixin:
40
41
  Returns:
41
42
  set[str]: A set of dependency type names for the given schema.
42
43
  """
44
+ @classmethod
45
+ def _is_enum(cls, _type: TypeData) -> bool: ...
amsdal/schemas/utils.pyi CHANGED
@@ -1,8 +1,8 @@
1
1
  from amsdal_utils.models.enums import ModuleType as ModuleType
2
2
  from typing import TypeAlias
3
3
 
4
- ModulePathType: TypeAlias
5
- ClassNameType: TypeAlias
4
+ ModulePathType: TypeAlias = str
5
+ ClassNameType: TypeAlias = str
6
6
 
7
7
  class ModelModuleInfo:
8
8
  _info: dict[ModuleType, dict[ClassNameType, ModulePathType]]
@@ -0,0 +1,11 @@
1
+ # AMSDAL services package
2
+
3
+ from amsdal.services.external_connections import ExternalConnectionManager
4
+ from amsdal.services.external_connections import ExternalDatabaseReader
5
+ from amsdal.services.external_model_generator import ExternalModelGenerator
6
+
7
+ __all__ = [
8
+ 'ExternalConnectionManager',
9
+ 'ExternalDatabaseReader',
10
+ 'ExternalModelGenerator',
11
+ ]
@@ -0,0 +1,4 @@
1
+ from amsdal.services.external_connections import ExternalConnectionManager as ExternalConnectionManager, ExternalDatabaseReader as ExternalDatabaseReader
2
+ from amsdal.services.external_model_generator import ExternalModelGenerator as ExternalModelGenerator
3
+
4
+ __all__ = ['ExternalConnectionManager', 'ExternalDatabaseReader', 'ExternalModelGenerator']