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.
- amsdal/Third-Party Materials - AMSDAL Dependencies - License Notices.md +28 -0
- amsdal/__about__.py +1 -1
- amsdal/__migrations__/0000_initial.py +22 -203
- amsdal/__migrations__/0001_create_class_file.py +61 -0
- amsdal/__migrations__/0002_create_class_file.py +109 -0
- amsdal/__migrations__/0003_update_class_file.py +91 -0
- amsdal/__migrations__/0004_update_class_file.py +45 -0
- amsdal/cloud/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/client.cpython-312-darwin.so +0 -0
- amsdal/cloud/constants.cpython-312-darwin.so +0 -0
- amsdal/cloud/enums.cpython-312-darwin.so +0 -0
- amsdal/cloud/models/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/models/base.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_allowlist_ip.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_basic_auth.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_dependency.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_secret.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/base.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/create_deploy.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/create_env.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/create_session.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_allowlist_ip.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_basic_auth.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_dependency.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_env.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_secret.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/destroy_deploy.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/expose_db.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/get_basic_auth_credentials.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/get_monitoring_info.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_dependencies.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_deploys.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_envs.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_secrets.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/manager.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/signup_action.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/update_deploy.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/base.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/credentials.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/manager.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/signup_service.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/token.cpython-312-darwin.so +0 -0
- amsdal/configs/main.py +17 -1
- amsdal/configs/main.pyi +7 -3
- amsdal/contrib/__init__.cpython-312-darwin.so +0 -0
- amsdal/contrib/auth/errors.py +36 -0
- amsdal/contrib/auth/errors.pyi +12 -0
- amsdal/contrib/auth/lifecycle/consumer.py +3 -3
- amsdal/contrib/auth/lifecycle/consumer.pyi +3 -0
- amsdal/contrib/auth/migrations/0000_initial.py +55 -52
- amsdal/contrib/auth/migrations/0001_add_mfa_support.py +188 -0
- amsdal/contrib/auth/models/__init__.py +1 -0
- amsdal/contrib/auth/models/backup_code.py +85 -0
- amsdal/contrib/auth/models/email_mfa_device.py +108 -0
- amsdal/contrib/auth/models/login_session.py +117 -0
- amsdal/contrib/auth/models/mfa_device.py +86 -0
- amsdal/contrib/auth/models/sms_device.py +113 -0
- amsdal/contrib/auth/models/totp_device.py +58 -0
- amsdal/contrib/auth/models/user.py +50 -0
- amsdal/contrib/auth/services/__init__.py +1 -0
- amsdal/contrib/auth/services/mfa_device_service.py +544 -0
- amsdal/contrib/auth/services/mfa_device_service.pyi +216 -0
- amsdal/contrib/auth/services/totp_service.py +358 -0
- amsdal/contrib/auth/services/totp_service.pyi +158 -0
- amsdal/contrib/auth/settings.py +8 -0
- amsdal/contrib/auth/settings.pyi +8 -0
- amsdal/contrib/auth/transactions/__init__.py +1 -0
- amsdal/contrib/auth/transactions/mfa_device_transactions.py +458 -0
- amsdal/contrib/auth/transactions/mfa_device_transactions.pyi +226 -0
- amsdal/contrib/auth/transactions/totp_transactions.py +203 -0
- amsdal/contrib/auth/transactions/totp_transactions.pyi +113 -0
- amsdal/contrib/auth/utils/mfa.py +257 -0
- amsdal/contrib/auth/utils/mfa.pyi +119 -0
- amsdal/contrib/frontend_configs/conversion/convert.py +32 -5
- amsdal/contrib/frontend_configs/migrations/0000_initial.py +154 -183
- amsdal/contrib/frontend_configs/migrations/0001_update_frontend_control_config.py +245 -0
- amsdal/contrib/frontend_configs/migrations/0002_add_button_and_invoke_actions.py +352 -0
- amsdal/contrib/frontend_configs/migrations/0003_create_class_frontendconfigdashboardelement.py +145 -0
- amsdal/contrib/frontend_configs/models/frontend_config_control_action.py +57 -1
- amsdal/contrib/frontend_configs/models/frontend_config_dashboard.py +51 -0
- amsdal/contrib/frontend_configs/models/frontend_control_config.py +69 -46
- amsdal/fixtures/__init__.cpython-312-darwin.so +0 -0
- amsdal/fixtures/manager.cpython-312-darwin.so +0 -0
- amsdal/fixtures/utils.cpython-312-darwin.so +0 -0
- amsdal/manager.cpython-312-darwin.so +0 -0
- amsdal/manager.pyi +5 -0
- amsdal/mixins/__init__.cpython-312-darwin.so +0 -0
- amsdal/mixins/class_versions_mixin.cpython-312-darwin.so +0 -0
- amsdal/models/core/class_object.py +7 -6
- amsdal/models/core/class_property.py +7 -1
- amsdal/models/core/file.py +168 -81
- amsdal/models/core/storage_metadata.py +15 -0
- amsdal/models/mixins.py +31 -0
- amsdal/models/types/object.py +3 -3
- amsdal/schemas/core/class_object/model.json +20 -0
- amsdal/schemas/core/class_property/model.json +19 -0
- amsdal/schemas/core/file/properties/validate_data.py +2 -3
- amsdal/schemas/core/storage_metadata/model.json +52 -0
- amsdal/schemas/interfaces.pyi +1 -1
- amsdal/schemas/manager.cpython-312-darwin.so +0 -0
- amsdal/schemas/mixins/check_dependencies_mixin.py +23 -8
- amsdal/schemas/mixins/check_dependencies_mixin.pyi +5 -2
- amsdal/schemas/utils.pyi +2 -2
- amsdal/services/__init__.py +11 -0
- amsdal/services/__init__.pyi +4 -0
- amsdal/services/external_connections.py +262 -0
- amsdal/services/external_connections.pyi +190 -0
- amsdal/services/external_model_generator.py +350 -0
- amsdal/services/external_model_generator.pyi +134 -0
- amsdal/services/transaction_execution.cpython-312-darwin.so +0 -0
- amsdal/services/transaction_execution.pyi +1 -0
- amsdal/storages/__init__.py +20 -0
- amsdal/storages/__init__.pyi +8 -0
- amsdal/storages/file_system.py +214 -0
- amsdal/storages/file_system.pyi +36 -0
- amsdal/utils/rollback/__init__.pyi +6 -0
- amsdal/utils/tests/enums.py +0 -2
- amsdal/utils/tests/helpers.py +213 -381
- amsdal/utils/tests/migrations.py +157 -0
- {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/METADATA +17 -11
- {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/RECORD +131 -124
- {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/WHEEL +1 -1
- amsdal/__migrations__/0001_datetime_type.py +0 -18
- amsdal/__migrations__/0002_fixture_order.py +0 -44
- amsdal/__migrations__/0003_schema_type_in_class_meta.py +0 -44
- amsdal/contrib/auth/models/login_session.pyi +0 -37
- amsdal/contrib/auth/models/permission.pyi +0 -18
- amsdal/contrib/auth/models/user.pyi +0 -46
- amsdal/contrib/frontend_configs/models/frontend_activator_config.pyi +0 -12
- amsdal/contrib/frontend_configs/models/frontend_config_async_validator.pyi +0 -7
- amsdal/contrib/frontend_configs/models/frontend_config_control_action.pyi +0 -32
- amsdal/contrib/frontend_configs/models/frontend_config_group_validator.pyi +0 -11
- amsdal/contrib/frontend_configs/models/frontend_config_option.pyi +0 -8
- amsdal/contrib/frontend_configs/models/frontend_config_skip_none_base.pyi +0 -8
- amsdal/contrib/frontend_configs/models/frontend_config_slider_option.pyi +0 -9
- amsdal/contrib/frontend_configs/models/frontend_config_text_mask.pyi +0 -10
- amsdal/contrib/frontend_configs/models/frontend_config_validator.pyi +0 -15
- amsdal/contrib/frontend_configs/models/frontend_control_config.pyi +0 -35
- amsdal/contrib/frontend_configs/models/frontend_model_config.pyi +0 -9
- amsdal/models/__init__.pyi +0 -9
- amsdal/models/core/class_object.pyi +0 -24
- amsdal/models/core/class_object_meta.py +0 -26
- amsdal/models/core/class_object_meta.pyi +0 -15
- amsdal/models/core/class_property.pyi +0 -11
- amsdal/models/core/class_property_meta.py +0 -15
- amsdal/models/core/class_property_meta.pyi +0 -10
- amsdal/models/core/file.pyi +0 -104
- amsdal/models/core/fixture.pyi +0 -14
- amsdal/models/core/option.pyi +0 -8
- amsdal/models/core/validator.pyi +0 -8
- amsdal/models/types/object.pyi +0 -16
- amsdal/schemas/core/class_object_meta/model.json +0 -59
- amsdal/schemas/core/class_property_meta/model.json +0 -23
- amsdal/services/__init__.cpython-312-darwin.so +0 -0
- /amsdal/contrib/auth/{models → services}/__init__.pyi +0 -0
- /amsdal/contrib/{frontend_configs/models → auth/transactions}/__init__.pyi +0 -0
- /amsdal/{models/core/__init__.pyi → contrib/auth/utils/__init__.py} +0 -0
- /amsdal/{models/types → contrib/auth/utils}/__init__.pyi +0 -0
- {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/licenses/LICENSE.txt +0 -0
- {amsdal-0.4.10.dist-info → amsdal-0.5.33.dist-info}/top_level.txt +0 -0
amsdal/models/core/file.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
If the data is None, it defaults to an empty byte string.
|
|
52
|
+
persist_file(self, storage=self.storage)
|
|
30
53
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
self
|
|
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
|
-
|
|
41
|
-
If the data is None, it defaults to an empty byte string.
|
|
67
|
+
await apersist_file(self, storage=self.storage)
|
|
42
68
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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,
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
def data_base64_decode(cls, data: Any) -> bytes:
|
|
83
|
+
if isinstance(data, str):
|
|
84
|
+
data = data.encode('utf-8')
|
|
53
85
|
|
|
54
|
-
|
|
55
|
-
If the byte string is not base64-encoded, it returns the original byte string.
|
|
86
|
+
is_base64: bool = False
|
|
56
87
|
|
|
57
|
-
|
|
58
|
-
|
|
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(
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
@
|
|
100
|
-
def
|
|
138
|
+
@classmethod
|
|
139
|
+
def from_bytes(cls, filename: str, data: bytes) -> 'File':
|
|
101
140
|
"""
|
|
102
|
-
|
|
141
|
+
Creates a `File` object from a byte string.
|
|
103
142
|
|
|
104
|
-
|
|
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
|
-
|
|
148
|
+
File: The created `File` object.
|
|
108
149
|
"""
|
|
109
|
-
|
|
150
|
+
obj = cls(filename=filename, data=data, size=len(data))
|
|
151
|
+
return obj
|
|
110
152
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def pre_create(self) -> None:
|
|
153
|
+
def to_file(self, file_or_path: Path | BinaryIO) -> None:
|
|
114
154
|
"""
|
|
115
|
-
|
|
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.
|
|
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
|
|
175
|
+
def url(self) -> str:
|
|
126
176
|
"""
|
|
127
|
-
|
|
177
|
+
Return a URL for this file using its storage_address.
|
|
128
178
|
|
|
129
|
-
|
|
130
|
-
|
|
179
|
+
Raises StateError if storage_address is missing.
|
|
180
|
+
"""
|
|
181
|
+
return self.storage.url(self)
|
|
131
182
|
|
|
132
|
-
|
|
133
|
-
None
|
|
183
|
+
def open(self, mode: str = 'rb') -> IO[Any]:
|
|
134
184
|
"""
|
|
135
|
-
|
|
185
|
+
Open a binary stream for reading (or other modes if supported) using storage_address.
|
|
136
186
|
|
|
137
|
-
|
|
187
|
+
Raises StateError if storage_address is missing.
|
|
138
188
|
"""
|
|
139
|
-
|
|
189
|
+
return self.storage.open(self, mode)
|
|
140
190
|
|
|
141
|
-
|
|
142
|
-
|
|
191
|
+
async def aurl(self) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Async variant of url().
|
|
143
194
|
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
ValueError: If the provided path is a directory.
|
|
203
|
+
async def aopen(self, mode: str = 'rb') -> Any:
|
|
149
204
|
"""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
amsdal/models/mixins.py
ADDED
|
@@ -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]
|
amsdal/models/types/object.py
CHANGED
|
@@ -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:
|
|
15
|
-
type:
|
|
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
|
-
|
|
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
|
+
}
|
amsdal/schemas/interfaces.pyi
CHANGED
|
@@ -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
|
|
Binary file
|
|
@@ -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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
_dependencies = [_dependency]
|
|
78
|
+
if '|' in _dependency:
|
|
79
|
+
_dependencies = [dep.strip() for dep in _dependency.split('|')]
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
@
|
|
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
|
+
]
|
amsdal/services/__init__.pyi
CHANGED
|
@@ -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']
|