amsdal 0.5.23__cp311-cp311-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.
- amsdal/Third-Party Materials - AMSDAL Dependencies - License Notices.md +1334 -0
- amsdal/__about__.py +4 -0
- amsdal/__about__.pyi +1 -0
- amsdal/__init__.py +23 -0
- amsdal/__init__.pyi +9 -0
- amsdal/__migrations__/0000_initial.py +36 -0
- 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-311-darwin.so +0 -0
- amsdal/cloud/__init__.pyi +0 -0
- amsdal/cloud/client.cpython-311-darwin.so +0 -0
- amsdal/cloud/client.pyi +57 -0
- amsdal/cloud/constants.cpython-311-darwin.so +0 -0
- amsdal/cloud/constants.pyi +13 -0
- amsdal/cloud/enums.cpython-311-darwin.so +0 -0
- amsdal/cloud/enums.pyi +68 -0
- amsdal/cloud/models/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/models/__init__.pyi +0 -0
- amsdal/cloud/models/base.cpython-311-darwin.so +0 -0
- amsdal/cloud/models/base.pyi +247 -0
- amsdal/cloud/services/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/__init__.pyi +0 -0
- amsdal/cloud/services/actions/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/__init__.pyi +0 -0
- amsdal/cloud/services/actions/add_allowlist_ip.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_allowlist_ip.pyi +19 -0
- amsdal/cloud/services/actions/add_basic_auth.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_basic_auth.pyi +21 -0
- amsdal/cloud/services/actions/add_dependency.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_dependency.pyi +19 -0
- amsdal/cloud/services/actions/add_secret.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_secret.pyi +20 -0
- amsdal/cloud/services/actions/base.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/base.pyi +122 -0
- amsdal/cloud/services/actions/create_deploy.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/create_deploy.pyi +41 -0
- amsdal/cloud/services/actions/create_env.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/create_env.pyi +19 -0
- amsdal/cloud/services/actions/create_session.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/create_session.pyi +17 -0
- amsdal/cloud/services/actions/delete_allowlist_ip.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_allowlist_ip.pyi +19 -0
- amsdal/cloud/services/actions/delete_basic_auth.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_basic_auth.pyi +20 -0
- amsdal/cloud/services/actions/delete_dependency.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_dependency.pyi +21 -0
- amsdal/cloud/services/actions/delete_env.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_env.pyi +21 -0
- amsdal/cloud/services/actions/delete_secret.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_secret.pyi +21 -0
- amsdal/cloud/services/actions/destroy_deploy.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/destroy_deploy.pyi +18 -0
- amsdal/cloud/services/actions/expose_db.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/expose_db.pyi +22 -0
- amsdal/cloud/services/actions/get_basic_auth_credentials.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/get_basic_auth_credentials.pyi +21 -0
- amsdal/cloud/services/actions/get_monitoring_info.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/get_monitoring_info.pyi +21 -0
- amsdal/cloud/services/actions/list_dependencies.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_dependencies.pyi +21 -0
- amsdal/cloud/services/actions/list_deploys.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_deploys.pyi +19 -0
- amsdal/cloud/services/actions/list_envs.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_envs.pyi +20 -0
- amsdal/cloud/services/actions/list_secrets.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_secrets.pyi +22 -0
- amsdal/cloud/services/actions/manager.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/manager.pyi +278 -0
- amsdal/cloud/services/actions/signup_action.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/signup_action.pyi +20 -0
- amsdal/cloud/services/actions/update_deploy.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/update_deploy.pyi +19 -0
- amsdal/cloud/services/auth/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/__init__.pyi +0 -0
- amsdal/cloud/services/auth/base.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/base.pyi +6 -0
- amsdal/cloud/services/auth/credentials.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/credentials.pyi +30 -0
- amsdal/cloud/services/auth/manager.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/manager.pyi +26 -0
- amsdal/cloud/services/auth/signup_service.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/signup_service.pyi +32 -0
- amsdal/cloud/services/auth/token.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/token.pyi +27 -0
- amsdal/configs/__init__.py +0 -0
- amsdal/configs/__init__.pyi +0 -0
- amsdal/configs/constants.py +33 -0
- amsdal/configs/constants.pyi +22 -0
- amsdal/configs/main.py +274 -0
- amsdal/configs/main.pyi +178 -0
- amsdal/context/__init__.py +0 -0
- amsdal/context/__init__.pyi +0 -0
- amsdal/context/manager.py +69 -0
- amsdal/context/manager.pyi +50 -0
- amsdal/contrib/__init__.cpython-311-darwin.so +0 -0
- amsdal/contrib/__init__.pyi +0 -0
- amsdal/contrib/app_config.py +7 -0
- amsdal/contrib/app_config.pyi +6 -0
- amsdal/contrib/auth/__init__.py +0 -0
- amsdal/contrib/auth/__init__.pyi +0 -0
- amsdal/contrib/auth/app.py +27 -0
- amsdal/contrib/auth/app.pyi +15 -0
- amsdal/contrib/auth/decorators/__init__.py +35 -0
- amsdal/contrib/auth/decorators/__init__.pyi +6 -0
- amsdal/contrib/auth/errors.py +7 -0
- amsdal/contrib/auth/errors.pyi +4 -0
- amsdal/contrib/auth/fixtures/basic_permissions.json +64 -0
- amsdal/contrib/auth/lifecycle/__init__.py +0 -0
- amsdal/contrib/auth/lifecycle/__init__.pyi +0 -0
- amsdal/contrib/auth/lifecycle/consumer.py +394 -0
- amsdal/contrib/auth/lifecycle/consumer.pyi +108 -0
- amsdal/contrib/auth/migrations/0000_initial.py +87 -0
- amsdal/contrib/auth/models/__init__.py +0 -0
- amsdal/contrib/auth/models/login_session.py +118 -0
- amsdal/contrib/auth/models/permission.py +23 -0
- amsdal/contrib/auth/models/user.py +106 -0
- amsdal/contrib/auth/settings.py +36 -0
- amsdal/contrib/auth/settings.pyi +26 -0
- amsdal/contrib/frontend_configs/__init__.py +0 -0
- amsdal/contrib/frontend_configs/__init__.pyi +0 -0
- amsdal/contrib/frontend_configs/app.py +24 -0
- amsdal/contrib/frontend_configs/app.pyi +19 -0
- amsdal/contrib/frontend_configs/constants.py +1 -0
- amsdal/contrib/frontend_configs/constants.pyi +1 -0
- amsdal/contrib/frontend_configs/conversion/__init__.py +5 -0
- amsdal/contrib/frontend_configs/conversion/__init__.pyi +3 -0
- amsdal/contrib/frontend_configs/conversion/convert.py +310 -0
- amsdal/contrib/frontend_configs/conversion/convert.pyi +22 -0
- amsdal/contrib/frontend_configs/lifecycle/__init__.py +0 -0
- amsdal/contrib/frontend_configs/lifecycle/__init__.pyi +0 -0
- amsdal/contrib/frontend_configs/lifecycle/consumer.py +306 -0
- amsdal/contrib/frontend_configs/lifecycle/consumer.pyi +98 -0
- amsdal/contrib/frontend_configs/migrations/0000_initial.py +227 -0
- 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/models/__init__.py +0 -0
- amsdal/contrib/frontend_configs/models/frontend_activator_config.py +22 -0
- amsdal/contrib/frontend_configs/models/frontend_config_async_validator.py +11 -0
- amsdal/contrib/frontend_configs/models/frontend_config_control_action.py +110 -0
- amsdal/contrib/frontend_configs/models/frontend_config_group_validator.py +21 -0
- amsdal/contrib/frontend_configs/models/frontend_config_option.py +12 -0
- amsdal/contrib/frontend_configs/models/frontend_config_skip_none_base.py +17 -0
- amsdal/contrib/frontend_configs/models/frontend_config_slider_option.py +13 -0
- amsdal/contrib/frontend_configs/models/frontend_config_text_mask.py +14 -0
- amsdal/contrib/frontend_configs/models/frontend_config_validator.py +28 -0
- amsdal/contrib/frontend_configs/models/frontend_control_config.py +108 -0
- amsdal/contrib/frontend_configs/models/frontend_model_config.py +14 -0
- amsdal/contrib/frontend_configs/utils.py +29 -0
- amsdal/contrib/frontend_configs/utils.pyi +17 -0
- amsdal/errors.py +31 -0
- amsdal/errors.pyi +12 -0
- amsdal/fixtures/__init__.cpython-311-darwin.so +0 -0
- amsdal/fixtures/__init__.pyi +0 -0
- amsdal/fixtures/manager.cpython-311-darwin.so +0 -0
- amsdal/fixtures/manager.pyi +170 -0
- amsdal/fixtures/utils.cpython-311-darwin.so +0 -0
- amsdal/fixtures/utils.pyi +9 -0
- amsdal/manager.cpython-311-darwin.so +0 -0
- amsdal/manager.pyi +265 -0
- amsdal/mixins/__init__.cpython-311-darwin.so +0 -0
- amsdal/mixins/__init__.pyi +0 -0
- amsdal/mixins/class_versions_mixin.cpython-311-darwin.so +0 -0
- amsdal/mixins/class_versions_mixin.pyi +12 -0
- amsdal/models/__init__.py +19 -0
- amsdal/models/core/__init__.py +0 -0
- amsdal/models/core/class_object.py +38 -0
- amsdal/models/core/class_property.py +26 -0
- amsdal/models/core/file.py +243 -0
- amsdal/models/core/fixture.py +25 -0
- amsdal/models/core/option.py +11 -0
- amsdal/models/core/storage_metadata.py +15 -0
- amsdal/models/core/validator.py +12 -0
- amsdal/models/mixins.py +31 -0
- amsdal/models/types/__init__.py +0 -0
- amsdal/models/types/object.py +26 -0
- amsdal/py.typed +0 -0
- amsdal/queryset/__init__.py +21 -0
- amsdal/queryset/__init__.pyi +6 -0
- amsdal/schemas/__init__.py +0 -0
- amsdal/schemas/__init__.pyi +0 -0
- amsdal/schemas/core/class_object/model.json +51 -0
- amsdal/schemas/core/class_object/properties/display_name.py +9 -0
- amsdal/schemas/core/class_property/model.json +41 -0
- amsdal/schemas/core/file/hooks/pre_create.py +24 -0
- amsdal/schemas/core/file/hooks/pre_update.py +24 -0
- amsdal/schemas/core/file/model.json +23 -0
- amsdal/schemas/core/file/properties/from_file.py +34 -0
- amsdal/schemas/core/file/properties/mimetype.py +13 -0
- amsdal/schemas/core/file/properties/str.py +6 -0
- amsdal/schemas/core/file/properties/to_file.py +24 -0
- amsdal/schemas/core/file/properties/validate_data.py +31 -0
- amsdal/schemas/core/fixture/model.json +35 -0
- amsdal/schemas/core/option/model.json +19 -0
- amsdal/schemas/core/storage_metadata/model.json +52 -0
- amsdal/schemas/core/validator/model.json +19 -0
- amsdal/schemas/interfaces.py +25 -0
- amsdal/schemas/interfaces.pyi +20 -0
- amsdal/schemas/manager.cpython-311-darwin.so +0 -0
- amsdal/schemas/manager.py +0 -0
- amsdal/schemas/manager.pyi +0 -0
- amsdal/schemas/mixins/__init__.py +0 -0
- amsdal/schemas/mixins/__init__.pyi +0 -0
- amsdal/schemas/mixins/check_dependencies_mixin.py +130 -0
- amsdal/schemas/mixins/check_dependencies_mixin.pyi +45 -0
- amsdal/schemas/mixins/verify_schemas_mixin.py +96 -0
- amsdal/schemas/mixins/verify_schemas_mixin.pyi +33 -0
- amsdal/schemas/repository.py +84 -0
- amsdal/schemas/repository.pyi +22 -0
- amsdal/schemas/types/anything/model.json +7 -0
- amsdal/schemas/types/array/model.json +7 -0
- amsdal/schemas/types/binary/model.json +7 -0
- amsdal/schemas/types/boolean/model.json +17 -0
- amsdal/schemas/types/date/model.json +7 -0
- amsdal/schemas/types/datetime/model.json +7 -0
- amsdal/schemas/types/dictionary/model.json +8 -0
- amsdal/schemas/types/number/model.json +8 -0
- amsdal/schemas/types/object/model.json +53 -0
- amsdal/schemas/types/string/model.json +8 -0
- amsdal/schemas/utils.py +16 -0
- amsdal/schemas/utils.pyi +10 -0
- 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-311-darwin.so +0 -0
- amsdal/services/transaction_execution.pyi +93 -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/transactions/__init__.py +13 -0
- amsdal/transactions/__init__.pyi +4 -0
- amsdal/utils/__init__.py +0 -0
- amsdal/utils/__init__.pyi +0 -0
- amsdal/utils/contrib_paths.py +23 -0
- amsdal/utils/contrib_paths.pyi +14 -0
- amsdal/utils/rollback/__init__.py +440 -0
- amsdal/utils/rollback/__init__.pyi +38 -0
- amsdal/utils/tests/__init__.py +0 -0
- amsdal/utils/tests/enums.py +16 -0
- amsdal/utils/tests/factories.py +49 -0
- amsdal/utils/tests/helpers.py +331 -0
- amsdal/utils/tests/migrations.py +157 -0
- amsdal-0.5.23.dist-info/METADATA +373 -0
- amsdal-0.5.23.dist-info/RECORD +252 -0
- amsdal-0.5.23.dist-info/WHEEL +5 -0
- amsdal-0.5.23.dist-info/licenses/LICENSE.txt +107 -0
- amsdal-0.5.23.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from amsdal.configs.main import settings as settings
|
|
4
|
+
from amsdal.errors import TransactionNotFoundError as TransactionNotFoundError
|
|
5
|
+
from amsdal_utils.utils.singleton import Singleton
|
|
6
|
+
from collections.abc import Callable as Callable, Generator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
logger: Incomplete
|
|
11
|
+
|
|
12
|
+
def is_transaction(statement: ast.AST) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Determines if a given AST statement is a transaction function.
|
|
15
|
+
|
|
16
|
+
This function checks if the provided AST statement is an asynchronous or synchronous function
|
|
17
|
+
definition that is decorated with the `transaction` decorator.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
statement (ast.AST): The AST statement to check.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
bool: True if the statement is a transaction function, False otherwise.
|
|
24
|
+
"""
|
|
25
|
+
def annotation_is_model(annotation: Any) -> bool: ...
|
|
26
|
+
|
|
27
|
+
class TransactionExecutionService(metaclass=Singleton):
|
|
28
|
+
"""
|
|
29
|
+
Service for executing transactions.
|
|
30
|
+
|
|
31
|
+
This class provides methods to execute transactions, load transaction functions,
|
|
32
|
+
and handle asynchronous transactions. It ensures that transactions are executed
|
|
33
|
+
with the correct arguments and handles any necessary preprocessing of arguments.
|
|
34
|
+
"""
|
|
35
|
+
_transactions: dict[str, Callable[..., Any]]
|
|
36
|
+
def __init__(self) -> None: ...
|
|
37
|
+
def execute_transaction(self, transaction_name: str, args: dict[str, Any], *, load_references: bool = True) -> Any:
|
|
38
|
+
"""
|
|
39
|
+
Executes a transaction with the given name and arguments.
|
|
40
|
+
|
|
41
|
+
This method retrieves the transaction function by its name, processes the arguments,
|
|
42
|
+
and executes the transaction. It handles both synchronous and asynchronous transactions
|
|
43
|
+
and performs necessary preprocessing of arguments, such as loading references.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
transaction_name (str): The name of the transaction to execute.
|
|
47
|
+
args (dict[str, Any]): The arguments to pass to the transaction function.
|
|
48
|
+
load_references (bool, optional): Whether to load references in the arguments. Defaults to True.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Any: The result of the transaction execution.
|
|
52
|
+
"""
|
|
53
|
+
async def async_execute_transaction(self, transaction_name: str, args: dict[str, Any], *, load_references: bool = True) -> Any:
|
|
54
|
+
"""
|
|
55
|
+
Executes a transaction with the given name and arguments.
|
|
56
|
+
|
|
57
|
+
This method retrieves the transaction function by its name, processes the arguments,
|
|
58
|
+
and executes the transaction. It handles both synchronous and asynchronous transactions
|
|
59
|
+
and performs necessary preprocessing of arguments, such as loading references.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
transaction_name (str): The name of the transaction to execute.
|
|
63
|
+
args (dict[str, Any]): The arguments to pass to the transaction function.
|
|
64
|
+
load_references (bool, optional): Whether to load references in the arguments. Defaults to True.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Any: The result of the transaction execution.
|
|
68
|
+
"""
|
|
69
|
+
def get_transaction_func(self, transaction_name: str) -> Callable[..., Any]:
|
|
70
|
+
"""
|
|
71
|
+
Retrieves the transaction function by its name.
|
|
72
|
+
|
|
73
|
+
This method checks if the transaction function is already loaded in the `_transactions` dictionary.
|
|
74
|
+
If not, it attempts to load the transaction function from the available transaction definitions.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
transaction_name (str): The name of the transaction function to retrieve.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Callable[..., Any]: The transaction function corresponding to the given name.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
TransactionNotFoundError: If the transaction function with the specified name is not found.
|
|
84
|
+
"""
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _run_async_transaction(transaction_func: Callable[..., Any], args: dict[str, Any]) -> Any: ...
|
|
87
|
+
def _load_transaction(self, transaction_name: str) -> Callable[..., Any]: ...
|
|
88
|
+
@classmethod
|
|
89
|
+
def _get_transaction_definitions(cls) -> Generator[tuple[ast.FunctionDef | ast.AsyncFunctionDef, Path], None, None]: ...
|
|
90
|
+
@classmethod
|
|
91
|
+
def _iterate_module(cls, module_path: Path) -> Generator[tuple[ast.FunctionDef | ast.AsyncFunctionDef, Path], None, None]: ...
|
|
92
|
+
@classmethod
|
|
93
|
+
def _iterate_file(cls, file_path: Path) -> Generator[tuple[ast.FunctionDef | ast.AsyncFunctionDef, Path], None, None]: ...
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from amsdal_models.storage.base import Storage
|
|
2
|
+
|
|
3
|
+
from amsdal.configs.main import settings
|
|
4
|
+
|
|
5
|
+
_DEFAULT_STORAGE = None
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_default_storage(storage: Storage) -> None:
|
|
9
|
+
global _DEFAULT_STORAGE # noqa: PLW0603
|
|
10
|
+
_DEFAULT_STORAGE = storage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def default_storage() -> Storage:
|
|
14
|
+
global _DEFAULT_STORAGE # noqa: PLW0603
|
|
15
|
+
|
|
16
|
+
if _DEFAULT_STORAGE is None:
|
|
17
|
+
# Determine backend from settings
|
|
18
|
+
class_path = settings.DEFAULT_FILE_STORAGE
|
|
19
|
+
_DEFAULT_STORAGE = Storage.from_storage_spec({'storage_class': class_path})
|
|
20
|
+
return _DEFAULT_STORAGE
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from amsdal.configs.main import settings as settings
|
|
3
|
+
from amsdal_models.storage.base import Storage
|
|
4
|
+
|
|
5
|
+
_DEFAULT_STORAGE: Incomplete
|
|
6
|
+
|
|
7
|
+
def set_default_storage(storage: Storage) -> None: ...
|
|
8
|
+
def default_storage() -> Storage: ...
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import IO
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import BinaryIO
|
|
8
|
+
|
|
9
|
+
from amsdal_models.storage.base import Storage
|
|
10
|
+
from amsdal_models.storage.errors import StorageError
|
|
11
|
+
from amsdal_models.storage.helpers import build_storage_address
|
|
12
|
+
from amsdal_models.storage.types import FileProtocol
|
|
13
|
+
from amsdal_utils.config.manager import AmsdalConfigManager
|
|
14
|
+
|
|
15
|
+
CHUNK_SIZE = 8 * 1024
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FileSystemStorage(Storage):
|
|
19
|
+
"""
|
|
20
|
+
Simple filesystem-based storage backend.
|
|
21
|
+
|
|
22
|
+
- base_dir: root directory for stored files
|
|
23
|
+
- base_url: URL prefix for building public URLs (optional)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
keeps_local_copy = False
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
base_dir: str | os.PathLike[Any] | None = None,
|
|
31
|
+
base_url: str | None = None,
|
|
32
|
+
*,
|
|
33
|
+
serialize_base_dir: bool = True,
|
|
34
|
+
serialize_base_url: bool = True,
|
|
35
|
+
) -> None:
|
|
36
|
+
from amsdal.configs.main import settings
|
|
37
|
+
|
|
38
|
+
# If AMSDAL is configured to run in async mode, ensure aiofiles is installed.
|
|
39
|
+
if AmsdalConfigManager().get_config().async_mode:
|
|
40
|
+
try:
|
|
41
|
+
import aiofiles # noqa: F401
|
|
42
|
+
except ImportError as e:
|
|
43
|
+
msg = (
|
|
44
|
+
"AMSDAL is configured to run in async mode, but the 'aiofiles' package is not installed.\n"
|
|
45
|
+
'Please install it to enable async file operations.\n\n'
|
|
46
|
+
'Example:\n'
|
|
47
|
+
' pip install aiofiles\n\n'
|
|
48
|
+
f'Original error: {e}'
|
|
49
|
+
)
|
|
50
|
+
raise ImportError(msg) from e
|
|
51
|
+
|
|
52
|
+
_base_dir = base_dir or settings.MEDIA_ROOT
|
|
53
|
+
_base_url = base_url or settings.MEDIA_URL
|
|
54
|
+
|
|
55
|
+
self.base_dir = Path(_base_dir).resolve()
|
|
56
|
+
self.base_url = _base_url
|
|
57
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
self.serialize_base_dir = serialize_base_dir
|
|
59
|
+
self.serialize_base_url = serialize_base_url
|
|
60
|
+
|
|
61
|
+
def save(self, file: FileProtocol, content: BinaryIO) -> str:
|
|
62
|
+
suggested = file.filename
|
|
63
|
+
final_name = self._get_available_name(suggested)
|
|
64
|
+
full_path = self._full_path(final_name)
|
|
65
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
|
|
67
|
+
with full_path.open('wb') as f:
|
|
68
|
+
# Ensure reading from start
|
|
69
|
+
with suppress(Exception):
|
|
70
|
+
if hasattr(content, 'seek'):
|
|
71
|
+
content.seek(0)
|
|
72
|
+
|
|
73
|
+
chunk = content.read(CHUNK_SIZE)
|
|
74
|
+
|
|
75
|
+
while chunk:
|
|
76
|
+
f.write(chunk)
|
|
77
|
+
chunk = content.read(CHUNK_SIZE)
|
|
78
|
+
|
|
79
|
+
file.storage_address = build_storage_address(self, final_name)
|
|
80
|
+
return final_name
|
|
81
|
+
|
|
82
|
+
def open(self, file: FileProtocol, mode: str = 'rb') -> IO[Any]:
|
|
83
|
+
name = self._name_for(file)
|
|
84
|
+
full_path = self._full_path(name)
|
|
85
|
+
|
|
86
|
+
if not full_path.exists():
|
|
87
|
+
msg = f'File not found: {name}'
|
|
88
|
+
raise StorageError(msg)
|
|
89
|
+
return full_path.open(mode)
|
|
90
|
+
|
|
91
|
+
def delete(self, file: FileProtocol) -> None:
|
|
92
|
+
name = self._name_for(file)
|
|
93
|
+
try:
|
|
94
|
+
self._full_path(name).unlink(missing_ok=True)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
msg = f"Failed to delete '{name}': {e}"
|
|
97
|
+
raise StorageError(msg) from e
|
|
98
|
+
|
|
99
|
+
def exists(self, file: FileProtocol) -> bool:
|
|
100
|
+
name = self._name_for(file)
|
|
101
|
+
return self._full_path(name).exists()
|
|
102
|
+
|
|
103
|
+
def url(self, file: FileProtocol) -> str:
|
|
104
|
+
name = self._name_for(file)
|
|
105
|
+
if not self.base_url:
|
|
106
|
+
# Return file path as fallback
|
|
107
|
+
return self._full_path(name).as_uri()
|
|
108
|
+
|
|
109
|
+
prefix = self.base_url.rstrip('/')
|
|
110
|
+
name_posix = Path(name).as_posix().lstrip('/')
|
|
111
|
+
|
|
112
|
+
return f'{prefix}/{name_posix}'
|
|
113
|
+
|
|
114
|
+
# ---- async counterparts ----
|
|
115
|
+
async def asave(self, file: FileProtocol, content: BinaryIO) -> str:
|
|
116
|
+
import aiofiles.os
|
|
117
|
+
|
|
118
|
+
suggested = file.filename
|
|
119
|
+
final_name = self._get_available_name(suggested)
|
|
120
|
+
full_path = self._full_path(final_name)
|
|
121
|
+
# Ensure directory exists
|
|
122
|
+
await aiofiles.os.makedirs(str(full_path.parent), exist_ok=True)
|
|
123
|
+
|
|
124
|
+
# Ensure reading from start
|
|
125
|
+
with suppress(Exception):
|
|
126
|
+
if hasattr(content, 'seek'):
|
|
127
|
+
with suppress(Exception):
|
|
128
|
+
await asyncio.to_thread(content.seek, 0)
|
|
129
|
+
|
|
130
|
+
async with aiofiles.open(str(full_path), 'wb') as f: # type: ignore[call-arg]
|
|
131
|
+
while True:
|
|
132
|
+
chunk = await asyncio.to_thread(content.read, CHUNK_SIZE)
|
|
133
|
+
if not chunk:
|
|
134
|
+
break
|
|
135
|
+
await f.write(chunk)
|
|
136
|
+
|
|
137
|
+
file.storage_address = build_storage_address(self, final_name)
|
|
138
|
+
|
|
139
|
+
return final_name
|
|
140
|
+
|
|
141
|
+
async def aopen(self, file: FileProtocol, mode: str = 'rb') -> Any:
|
|
142
|
+
import aiofiles.ospath
|
|
143
|
+
|
|
144
|
+
name = self._name_for(file)
|
|
145
|
+
full_path = self._full_path(name)
|
|
146
|
+
|
|
147
|
+
if not await aiofiles.ospath.exists(str(full_path)):
|
|
148
|
+
msg = f'File not found: {name}'
|
|
149
|
+
raise StorageError(msg)
|
|
150
|
+
|
|
151
|
+
return aiofiles.open(str(full_path), mode) # type: ignore[call-overload]
|
|
152
|
+
|
|
153
|
+
async def adelete(self, file: FileProtocol) -> None:
|
|
154
|
+
import aiofiles.os
|
|
155
|
+
import aiofiles.ospath
|
|
156
|
+
|
|
157
|
+
name = self._name_for(file)
|
|
158
|
+
full_path = self._full_path(name)
|
|
159
|
+
|
|
160
|
+
if await aiofiles.ospath.exists(str(full_path)):
|
|
161
|
+
try:
|
|
162
|
+
await aiofiles.os.remove(str(full_path))
|
|
163
|
+
except Exception as e: # pragma: no cover - error path
|
|
164
|
+
msg = f"Failed to delete '{name}': {e}"
|
|
165
|
+
raise StorageError(msg) from e
|
|
166
|
+
|
|
167
|
+
async def aexists(self, file: FileProtocol) -> bool:
|
|
168
|
+
import aiofiles.ospath
|
|
169
|
+
|
|
170
|
+
name = self._name_for(file)
|
|
171
|
+
return await aiofiles.ospath.exists(str(self._full_path(name)))
|
|
172
|
+
|
|
173
|
+
async def aurl(self, file: FileProtocol) -> str:
|
|
174
|
+
# Pure computation; no disk I/O.
|
|
175
|
+
return self.url(file)
|
|
176
|
+
|
|
177
|
+
def _full_path(self, name: str) -> Path:
|
|
178
|
+
# Sanitize name to avoid path traversal
|
|
179
|
+
norm = Path(name).as_posix().lstrip('/')
|
|
180
|
+
|
|
181
|
+
return (self.base_dir / norm).resolve()
|
|
182
|
+
|
|
183
|
+
def _get_available_name(self, name: str) -> str:
|
|
184
|
+
# If file exists, add suffixes to avoid collision
|
|
185
|
+
candidate = Path(name)
|
|
186
|
+
base = candidate.stem
|
|
187
|
+
suffix = candidate.suffix
|
|
188
|
+
parent = candidate.parent.as_posix()
|
|
189
|
+
i = 0
|
|
190
|
+
final = candidate.as_posix()
|
|
191
|
+
|
|
192
|
+
# Check filesystem directly to avoid relying on public exists signature
|
|
193
|
+
while self._full_path(final).exists():
|
|
194
|
+
i += 1
|
|
195
|
+
new_name = f'{base}_{i}{suffix}'
|
|
196
|
+
final = str(Path(parent) / new_name) if parent and parent != '.' else new_name
|
|
197
|
+
return final
|
|
198
|
+
|
|
199
|
+
def _name_for(self, file: FileProtocol) -> str:
|
|
200
|
+
if getattr(file, 'storage_address', None) and getattr(file.storage_address, 'ref', None) is not None:
|
|
201
|
+
if getattr(file.storage_address.ref, 'object_id', None) is not None: # type: ignore[union-attr]
|
|
202
|
+
return str(file.storage_address.ref.object_id) # type: ignore[union-attr]
|
|
203
|
+
return file.filename
|
|
204
|
+
|
|
205
|
+
def _export_kwargs(self) -> dict[str, Any]:
|
|
206
|
+
kwargs = {}
|
|
207
|
+
|
|
208
|
+
if self.serialize_base_dir:
|
|
209
|
+
kwargs['base_dir'] = str(self.base_dir)
|
|
210
|
+
|
|
211
|
+
if self.serialize_base_url:
|
|
212
|
+
kwargs['base_url'] = self.base_url
|
|
213
|
+
|
|
214
|
+
return kwargs
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from amsdal_models.storage.base import Storage
|
|
4
|
+
from amsdal_models.storage.types import FileProtocol
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, BinaryIO, IO
|
|
7
|
+
|
|
8
|
+
CHUNK_SIZE: Incomplete
|
|
9
|
+
|
|
10
|
+
class FileSystemStorage(Storage):
|
|
11
|
+
"""
|
|
12
|
+
Simple filesystem-based storage backend.
|
|
13
|
+
|
|
14
|
+
- base_dir: root directory for stored files
|
|
15
|
+
- base_url: URL prefix for building public URLs (optional)
|
|
16
|
+
"""
|
|
17
|
+
keeps_local_copy: bool
|
|
18
|
+
base_dir: Incomplete
|
|
19
|
+
base_url: Incomplete
|
|
20
|
+
serialize_base_dir: Incomplete
|
|
21
|
+
serialize_base_url: Incomplete
|
|
22
|
+
def __init__(self, base_dir: str | os.PathLike[Any] | None = None, base_url: str | None = None, *, serialize_base_dir: bool = True, serialize_base_url: bool = True) -> None: ...
|
|
23
|
+
def save(self, file: FileProtocol, content: BinaryIO) -> str: ...
|
|
24
|
+
def open(self, file: FileProtocol, mode: str = 'rb') -> IO[Any]: ...
|
|
25
|
+
def delete(self, file: FileProtocol) -> None: ...
|
|
26
|
+
def exists(self, file: FileProtocol) -> bool: ...
|
|
27
|
+
def url(self, file: FileProtocol) -> str: ...
|
|
28
|
+
async def asave(self, file: FileProtocol, content: BinaryIO) -> str: ...
|
|
29
|
+
async def aopen(self, file: FileProtocol, mode: str = 'rb') -> Any: ...
|
|
30
|
+
async def adelete(self, file: FileProtocol) -> None: ...
|
|
31
|
+
async def aexists(self, file: FileProtocol) -> bool: ...
|
|
32
|
+
async def aurl(self, file: FileProtocol) -> str: ...
|
|
33
|
+
def _full_path(self, name: str) -> Path: ...
|
|
34
|
+
def _get_available_name(self, name: str) -> str: ...
|
|
35
|
+
def _name_for(self, file: FileProtocol) -> str: ...
|
|
36
|
+
def _export_kwargs(self) -> dict[str, Any]: ...
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from amsdal_data.transactions import async_transaction
|
|
2
|
+
from amsdal_data.transactions import transaction
|
|
3
|
+
from amsdal_data.transactions.background.schedule import SCHEDULE_TYPE
|
|
4
|
+
from amsdal_data.transactions.background.schedule import Crontab
|
|
5
|
+
from amsdal_data.transactions.background.schedule import ScheduleConfig
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'SCHEDULE_TYPE',
|
|
9
|
+
'Crontab',
|
|
10
|
+
'ScheduleConfig',
|
|
11
|
+
'async_transaction',
|
|
12
|
+
'transaction',
|
|
13
|
+
]
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from amsdal_data.transactions import async_transaction as async_transaction, transaction as transaction
|
|
2
|
+
from amsdal_data.transactions.background.schedule import Crontab as Crontab, SCHEDULE_TYPE as SCHEDULE_TYPE, ScheduleConfig as ScheduleConfig
|
|
3
|
+
|
|
4
|
+
__all__ = ['SCHEDULE_TYPE', 'Crontab', 'ScheduleConfig', 'async_transaction', 'transaction']
|
amsdal/utils/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_contrib_schemas_paths() -> Generator[Path, None, None]:
|
|
7
|
+
"""
|
|
8
|
+
Retrieves paths to contribution schemas.
|
|
9
|
+
|
|
10
|
+
This function iterates over the configured contributions in the settings and yields
|
|
11
|
+
the paths to the 'models' directories for each contribution.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Generator[Path, None, None]: A generator that yields paths to the 'models' directories
|
|
15
|
+
of the configured contributions.
|
|
16
|
+
"""
|
|
17
|
+
from amsdal.configs.main import settings
|
|
18
|
+
|
|
19
|
+
for contrib in settings.CONTRIB:
|
|
20
|
+
module_name, *_ = contrib.rsplit('.', 2)
|
|
21
|
+
module = import_module(module_name)
|
|
22
|
+
|
|
23
|
+
yield Path(module.__file__).parent / 'models' # type: ignore[arg-type]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def get_contrib_schemas_paths() -> Generator[Path, None, None]:
|
|
5
|
+
"""
|
|
6
|
+
Retrieves paths to contribution schemas.
|
|
7
|
+
|
|
8
|
+
This function iterates over the configured contributions in the settings and yields
|
|
9
|
+
the paths to the 'models' directories for each contribution.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
Generator[Path, None, None]: A generator that yields paths to the 'models' directories
|
|
13
|
+
of the configured contributions.
|
|
14
|
+
"""
|