amsdal 0.3.3__cp311-cp311-macosx_10_9_universal2.whl → 0.5.29__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.
- amsdal/Third-Party Materials - AMSDAL Dependencies - License Notices.md +56 -2
- amsdal/__about__.py +1 -1
- amsdal/__init__.py +20 -0
- amsdal/__init__.pyi +9 -0
- amsdal/__migrations__/0000_initial.py +23 -190
- 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/client.cpython-311-darwin.so +0 -0
- amsdal/cloud/constants.cpython-311-darwin.so +0 -0
- amsdal/cloud/enums.cpython-311-darwin.so +0 -0
- amsdal/cloud/models/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/models/base.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_allowlist_ip.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_basic_auth.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_dependency.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/add_secret.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/base.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/create_deploy.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/create_env.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/create_session.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_allowlist_ip.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_basic_auth.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_dependency.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_env.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_secret.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/destroy_deploy.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/expose_db.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/get_basic_auth_credentials.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/get_monitoring_info.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_dependencies.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_deploys.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_envs.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/list_secrets.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/manager.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/signup_action.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/actions/update_deploy.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/__init__.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/base.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/credentials.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/credentials.pyi +0 -1
- amsdal/cloud/services/auth/manager.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/signup_service.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/token.cpython-311-darwin.so +0 -0
- amsdal/cloud/services/auth/token.pyi +0 -1
- amsdal/configs/main.py +40 -20
- amsdal/configs/main.pyi +19 -18
- amsdal/contrib/__init__.cpython-311-darwin.so +0 -0
- amsdal/contrib/auth/errors.py +36 -0
- amsdal/contrib/auth/errors.pyi +12 -0
- amsdal/contrib/auth/fixtures/basic_permissions.json +64 -0
- amsdal/contrib/auth/lifecycle/consumer.py +13 -13
- amsdal/contrib/auth/lifecycle/consumer.pyi +3 -0
- amsdal/contrib/auth/migrations/0000_initial.py +69 -31
- 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 +235 -0
- amsdal/contrib/auth/models/mfa_device.py +86 -0
- amsdal/contrib/auth/models/permission.py +23 -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 +156 -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 +463 -0
- amsdal/contrib/auth/transactions/mfa_device_transactions.pyi +226 -0
- amsdal/contrib/auth/transactions/totp_transactions.py +206 -0
- amsdal/contrib/auth/transactions/totp_transactions.pyi +113 -0
- amsdal/contrib/auth/utils/__init__.py +0 -0
- amsdal/contrib/auth/utils/__init__.pyi +0 -0
- amsdal/contrib/auth/utils/mfa.py +257 -0
- amsdal/contrib/auth/utils/mfa.pyi +119 -0
- amsdal/contrib/frontend_configs/conversion/convert.py +85 -25
- amsdal/contrib/frontend_configs/conversion/convert.pyi +0 -1
- amsdal/contrib/frontend_configs/lifecycle/consumer.py +32 -13
- amsdal/contrib/frontend_configs/lifecycle/consumer.pyi +1 -1
- amsdal/contrib/frontend_configs/migrations/0000_initial.py +167 -195
- 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/__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_dashboard.py +51 -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 +110 -0
- amsdal/contrib/frontend_configs/models/frontend_model_config.py +14 -0
- amsdal/errors.py +0 -3
- amsdal/errors.pyi +0 -1
- amsdal/fixtures/__init__.cpython-311-darwin.so +0 -0
- amsdal/fixtures/manager.cpython-311-darwin.so +0 -0
- amsdal/fixtures/manager.pyi +73 -123
- 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 +9 -96
- amsdal/mixins/__init__.cpython-311-darwin.so +0 -0
- amsdal/mixins/class_versions_mixin.cpython-311-darwin.so +0 -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/queryset/__init__.py +21 -0
- amsdal/queryset/__init__.pyi +6 -0
- amsdal/schemas/core/class_object/model.json +20 -0
- amsdal/schemas/core/class_property/model.json +19 -0
- amsdal/schemas/core/file/properties/from_file.py +1 -1
- amsdal/schemas/core/file/properties/validate_data.py +3 -4
- amsdal/schemas/core/storage_metadata/model.json +52 -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 -116
- amsdal/schemas/manager.pyi +0 -65
- 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/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 +2 -1
- 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/rollback/__init__.py +99 -54
- amsdal/utils/rollback/__init__.pyi +6 -0
- amsdal/utils/tests/enums.py +0 -2
- amsdal/utils/tests/helpers.py +253 -231
- amsdal/utils/tests/migrations.py +157 -0
- {amsdal-0.3.3.dist-info → amsdal-0.5.29.dist-info}/METADATA +17 -10
- amsdal-0.5.29.dist-info/RECORD +276 -0
- {amsdal-0.3.3.dist-info → amsdal-0.5.29.dist-info}/WHEEL +1 -1
- amsdal/__migrations__/0001_datetime_type.py +0 -18
- amsdal/__migrations__/0002_fixture_order.py +0 -40
- amsdal/__migrations__/0003_schema_type_in_class_meta.py +0 -56
- amsdal/contrib/auth/models/login_session/hooks/pre_init.py +0 -68
- amsdal/contrib/auth/models/login_session/model.json +0 -23
- amsdal/contrib/auth/models/login_session/modifiers/display_name.py +0 -11
- amsdal/contrib/auth/models/permission/fixtures/basic_permissions.json +0 -62
- amsdal/contrib/auth/models/permission/model.json +0 -18
- amsdal/contrib/auth/models/permission/modifiers/display_name.py +0 -11
- amsdal/contrib/auth/models/user/hooks/post_init.py +0 -76
- amsdal/contrib/auth/models/user/hooks/pre_create.py +0 -8
- amsdal/contrib/auth/models/user/model.json +0 -25
- amsdal/contrib/auth/models/user/modifiers/display_name.py +0 -19
- amsdal/contrib/frontend_configs/models/frontend_activator_config/model.json +0 -11
- amsdal/contrib/frontend_configs/models/frontend_config_async_validator/model.json +0 -11
- amsdal/contrib/frontend_configs/models/frontend_config_group_validator/model.json +0 -52
- amsdal/contrib/frontend_configs/models/frontend_config_option/model.json +0 -15
- amsdal/contrib/frontend_configs/models/frontend_config_skip_none_base/model.json +0 -6
- amsdal/contrib/frontend_configs/models/frontend_config_skip_none_base/properties/model_dump.py +0 -13
- amsdal/contrib/frontend_configs/models/frontend_config_slider_option/model.json +0 -19
- amsdal/contrib/frontend_configs/models/frontend_config_text_mask/model.json +0 -26
- amsdal/contrib/frontend_configs/models/frontend_config_validator/model.json +0 -41
- amsdal/contrib/frontend_configs/models/frontend_control_config/model.json +0 -250
- amsdal/contrib/frontend_configs/models/frontend_model_config/fixtures/permissions.json +0 -24
- amsdal/contrib/frontend_configs/models/frontend_model_config/model.json +0 -17
- amsdal/contrib/frontend_configs/models/frontent_config_control_action/model.json +0 -54
- amsdal/contrib/frontend_configs/models/frontent_config_control_action/properties/action_validate.py +0 -33
- amsdal/migration/__init__.cpython-311-darwin.so +0 -0
- amsdal/migration/base_migration_schemas.cpython-311-darwin.so +0 -0
- amsdal/migration/base_migration_schemas.pyi +0 -120
- amsdal/migration/data_classes.cpython-311-darwin.so +0 -0
- amsdal/migration/data_classes.pyi +0 -172
- amsdal/migration/executors/__init__.cpython-311-darwin.so +0 -0
- amsdal/migration/executors/base.cpython-311-darwin.so +0 -0
- amsdal/migration/executors/base.pyi +0 -118
- amsdal/migration/executors/default_executor.cpython-311-darwin.so +0 -0
- amsdal/migration/executors/default_executor.pyi +0 -184
- amsdal/migration/executors/state_executor.cpython-311-darwin.so +0 -0
- amsdal/migration/executors/state_executor.pyi +0 -78
- amsdal/migration/file_migration_executor.cpython-311-darwin.so +0 -0
- amsdal/migration/file_migration_executor.pyi +0 -68
- amsdal/migration/file_migration_generator.cpython-311-darwin.so +0 -0
- amsdal/migration/file_migration_generator.pyi +0 -139
- amsdal/migration/file_migration_store.cpython-311-darwin.so +0 -0
- amsdal/migration/file_migration_store.pyi +0 -61
- amsdal/migration/file_migration_writer.cpython-311-darwin.so +0 -0
- amsdal/migration/file_migration_writer.pyi +0 -73
- amsdal/migration/migrations.cpython-311-darwin.so +0 -0
- amsdal/migration/migrations.pyi +0 -166
- amsdal/migration/migrations_loader.cpython-311-darwin.so +0 -0
- amsdal/migration/migrations_loader.pyi +0 -32
- amsdal/migration/schemas_loaders.cpython-311-darwin.so +0 -0
- amsdal/migration/schemas_loaders.pyi +0 -37
- amsdal/migration/templates/data_migration.tmpl +0 -18
- amsdal/migration/templates/dict_validator.tmpl +0 -4
- amsdal/migration/templates/migration.tmpl +0 -6
- amsdal/migration/templates/model_class.tmpl +0 -8
- amsdal/migration/templates/model_class_layout.tmpl +0 -24
- amsdal/migration/templates/options_validator.tmpl +0 -4
- amsdal/migration/utils.cpython-311-darwin.so +0 -0
- amsdal/migration/utils.pyi +0 -58
- amsdal/mixins/build_mixin.cpython-311-darwin.so +0 -0
- amsdal/mixins/build_mixin.pyi +0 -78
- amsdal/schemas/core/class_object_meta/model.json +0 -59
- amsdal/schemas/core/class_property_meta/model.json +0 -23
- amsdal/services/__init__.cpython-311-darwin.so +0 -0
- amsdal-0.3.3.dist-info/RECORD +0 -257
- amsdal-0.3.3.dist-info/licenses/LICENSE.txt +0 -107
- /amsdal/{migration → contrib/auth/services}/__init__.pyi +0 -0
- /amsdal/{migration/executors → contrib/auth/transactions}/__init__.pyi +0 -0
- {amsdal-0.3.3.dist-info → amsdal-0.5.29.dist-info/licenses}/LICENSE.txt +0 -0
- {amsdal-0.3.3.dist-info → amsdal-0.5.29.dist-info}/top_level.txt +0 -0
|
@@ -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']
|
|
@@ -4,7 +4,7 @@ from amsdal_data.application import DataApplication
|
|
|
4
4
|
from amsdal_data.transactions.decorators import async_transaction
|
|
5
5
|
from amsdal_data.transactions.decorators import transaction
|
|
6
6
|
from amsdal_data.transactions.errors import AmsdalTransactionError
|
|
7
|
-
from amsdal_models.classes.
|
|
7
|
+
from amsdal_models.classes.class_manager import ClassManager
|
|
8
8
|
from amsdal_models.querysets.executor import LAKEHOUSE_DB_ALIAS
|
|
9
9
|
|
|
10
10
|
|
|
@@ -28,14 +28,21 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
28
28
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
29
29
|
where=glue.Conditions(
|
|
30
30
|
glue.Condition(
|
|
31
|
-
|
|
31
|
+
left=glue.FieldReferenceExpression(
|
|
32
|
+
field_reference=glue.FieldReference(field=glue.Field(name='updated_at'), table_name='Metadata'),
|
|
33
|
+
),
|
|
32
34
|
lookup=glue.FieldLookup.GT,
|
|
33
|
-
|
|
35
|
+
right=glue.Value(timestamp),
|
|
34
36
|
),
|
|
35
37
|
glue.Condition(
|
|
36
|
-
|
|
38
|
+
left=glue.FieldReferenceExpression(
|
|
39
|
+
field_reference=glue.FieldReference(
|
|
40
|
+
field=glue.Field(name='prior_version'),
|
|
41
|
+
table_name='Metadata',
|
|
42
|
+
),
|
|
43
|
+
),
|
|
37
44
|
lookup=glue.FieldLookup.ISNULL,
|
|
38
|
-
|
|
45
|
+
right=glue.Value(True),
|
|
39
46
|
),
|
|
40
47
|
),
|
|
41
48
|
)
|
|
@@ -48,14 +55,21 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
48
55
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
49
56
|
where=glue.Conditions(
|
|
50
57
|
glue.Condition(
|
|
51
|
-
|
|
58
|
+
left=glue.FieldReferenceExpression(
|
|
59
|
+
field_reference=glue.FieldReference(field=glue.Field(name='updated_at'), table_name='Metadata'),
|
|
60
|
+
),
|
|
52
61
|
lookup=glue.FieldLookup.GT,
|
|
53
|
-
|
|
62
|
+
right=glue.Value(timestamp),
|
|
54
63
|
),
|
|
55
64
|
glue.Condition(
|
|
56
|
-
|
|
65
|
+
left=glue.FieldReferenceExpression(
|
|
66
|
+
field_reference=glue.FieldReference(
|
|
67
|
+
field=glue.Field(name='prior_version'),
|
|
68
|
+
table_name='Metadata',
|
|
69
|
+
),
|
|
70
|
+
),
|
|
57
71
|
lookup=glue.FieldLookup.ISNULL,
|
|
58
|
-
|
|
72
|
+
right=glue.Value(False),
|
|
59
73
|
),
|
|
60
74
|
),
|
|
61
75
|
)
|
|
@@ -83,9 +97,12 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
83
97
|
_parent_field.child.child.parent = _parent_field.child # type: ignore[union-attr]
|
|
84
98
|
_conditions.append(
|
|
85
99
|
glue.Condition(
|
|
86
|
-
|
|
100
|
+
left=glue.FieldReferenceExpression(
|
|
101
|
+
field_reference=glue.FieldReference(field=_parent_field, table_name='Metadata'),
|
|
102
|
+
output_type=str,
|
|
103
|
+
),
|
|
87
104
|
lookup=glue.FieldLookup.EQ,
|
|
88
|
-
|
|
105
|
+
right=glue.Value(transaction_id, output_type=str),
|
|
89
106
|
)
|
|
90
107
|
)
|
|
91
108
|
|
|
@@ -94,9 +111,14 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
94
111
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
95
112
|
where=glue.Conditions(
|
|
96
113
|
glue.Condition(
|
|
97
|
-
|
|
114
|
+
left=glue.FieldReferenceExpression(
|
|
115
|
+
field_reference=glue.FieldReference(
|
|
116
|
+
field=glue.Field(name='updated_at'),
|
|
117
|
+
table_name='Metadata',
|
|
118
|
+
),
|
|
119
|
+
),
|
|
98
120
|
lookup=glue.FieldLookup.LTE,
|
|
99
|
-
|
|
121
|
+
right=glue.Value(timestamp),
|
|
100
122
|
),
|
|
101
123
|
glue.Conditions(*_conditions, connector=glue.FilterConnector.OR),
|
|
102
124
|
),
|
|
@@ -108,9 +130,7 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
108
130
|
|
|
109
131
|
for m in metadatas_to_delete:
|
|
110
132
|
class_name = m.data['class_schema_reference']['ref']['object_id']
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
model_class = class_manager.import_model_class(class_name, schema_type)
|
|
133
|
+
model_class = class_manager.import_class(class_name)
|
|
114
134
|
obj = (
|
|
115
135
|
model_class.objects.filter(_address__object_id=m.data['object_id'])
|
|
116
136
|
.using(LAKEHOUSE_DB_ALIAS)
|
|
@@ -123,8 +143,7 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
123
143
|
obj.delete()
|
|
124
144
|
|
|
125
145
|
for object_id, class_name in ids_to_revert:
|
|
126
|
-
|
|
127
|
-
model_class = class_manager.import_model_class(class_name, schema_type)
|
|
146
|
+
model_class = class_manager.import_class(class_name)
|
|
128
147
|
|
|
129
148
|
obj = (
|
|
130
149
|
model_class.objects.filter(_address__object_id=object_id)
|
|
@@ -133,13 +152,14 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
133
152
|
.first()
|
|
134
153
|
.execute()
|
|
135
154
|
)
|
|
136
|
-
old_obj = (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
155
|
+
old_obj = obj.previous_version() # type: ignore[union-attr]
|
|
156
|
+
# old_obj = (
|
|
157
|
+
# model_class.objects.filter(_address__object_id=object_id, _metadata__updated_at__lte=timestamp)
|
|
158
|
+
# .using(LAKEHOUSE_DB_ALIAS)
|
|
159
|
+
# .order_by('-_metadata__updated_at')
|
|
160
|
+
# .first()
|
|
161
|
+
# .execute()
|
|
162
|
+
# )
|
|
143
163
|
|
|
144
164
|
if obj and old_obj:
|
|
145
165
|
for field, value in old_obj.model_dump().items():
|
|
@@ -180,9 +200,12 @@ def rollback_transaction(transaction_id: str) -> None:
|
|
|
180
200
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
181
201
|
where=glue.Conditions(
|
|
182
202
|
glue.Condition(
|
|
183
|
-
|
|
203
|
+
left=glue.FieldReferenceExpression(
|
|
204
|
+
field_reference=glue.FieldReference(field=_parent_field, table_name='Metadata'),
|
|
205
|
+
output_type=str,
|
|
206
|
+
),
|
|
184
207
|
lookup=glue.FieldLookup.EQ,
|
|
185
|
-
|
|
208
|
+
right=glue.Value(transaction_id, output_type=str),
|
|
186
209
|
)
|
|
187
210
|
),
|
|
188
211
|
order_by=[
|
|
@@ -222,14 +245,21 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
222
245
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
223
246
|
where=glue.Conditions(
|
|
224
247
|
glue.Condition(
|
|
225
|
-
|
|
248
|
+
left=glue.FieldReferenceExpression(
|
|
249
|
+
field_reference=glue.FieldReference(field=glue.Field(name='updated_at'), table_name='Metadata'),
|
|
250
|
+
),
|
|
226
251
|
lookup=glue.FieldLookup.GT,
|
|
227
|
-
|
|
252
|
+
right=glue.Value(timestamp),
|
|
228
253
|
),
|
|
229
254
|
glue.Condition(
|
|
230
|
-
|
|
255
|
+
left=glue.FieldReferenceExpression(
|
|
256
|
+
field_reference=glue.FieldReference(
|
|
257
|
+
field=glue.Field(name='prior_version'),
|
|
258
|
+
table_name='Metadata',
|
|
259
|
+
),
|
|
260
|
+
),
|
|
231
261
|
lookup=glue.FieldLookup.ISNULL,
|
|
232
|
-
|
|
262
|
+
right=glue.Value(True),
|
|
233
263
|
),
|
|
234
264
|
),
|
|
235
265
|
)
|
|
@@ -242,14 +272,20 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
242
272
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
243
273
|
where=glue.Conditions(
|
|
244
274
|
glue.Condition(
|
|
245
|
-
|
|
275
|
+
left=glue.FieldReferenceExpression(
|
|
276
|
+
field_reference=glue.FieldReference(field=glue.Field(name='updated_at'), table_name='Metadata'),
|
|
277
|
+
),
|
|
246
278
|
lookup=glue.FieldLookup.GT,
|
|
247
|
-
|
|
279
|
+
right=glue.Value(timestamp),
|
|
248
280
|
),
|
|
249
281
|
glue.Condition(
|
|
250
|
-
|
|
282
|
+
left=glue.FieldReferenceExpression(
|
|
283
|
+
field_reference=glue.FieldReference(
|
|
284
|
+
field=glue.Field(name='prior_version'), table_name='Metadata'
|
|
285
|
+
),
|
|
286
|
+
),
|
|
251
287
|
lookup=glue.FieldLookup.ISNULL,
|
|
252
|
-
|
|
288
|
+
right=glue.Value(False),
|
|
253
289
|
),
|
|
254
290
|
),
|
|
255
291
|
)
|
|
@@ -277,9 +313,12 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
277
313
|
_parent_field.child.child.parent = _parent_field.child # type: ignore[union-attr]
|
|
278
314
|
_conditions.append(
|
|
279
315
|
glue.Condition(
|
|
280
|
-
|
|
316
|
+
left=glue.FieldReferenceExpression(
|
|
317
|
+
field_reference=glue.FieldReference(field=_parent_field, table_name='Metadata'),
|
|
318
|
+
output_type=str,
|
|
319
|
+
),
|
|
281
320
|
lookup=glue.FieldLookup.EQ,
|
|
282
|
-
|
|
321
|
+
right=glue.Value(transaction_id, output_type=str),
|
|
283
322
|
)
|
|
284
323
|
)
|
|
285
324
|
|
|
@@ -288,9 +327,14 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
288
327
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
289
328
|
where=glue.Conditions(
|
|
290
329
|
glue.Condition(
|
|
291
|
-
|
|
330
|
+
left=glue.FieldReferenceExpression(
|
|
331
|
+
field_reference=glue.FieldReference(
|
|
332
|
+
field=glue.Field(name='updated_at'),
|
|
333
|
+
table_name='Metadata',
|
|
334
|
+
),
|
|
335
|
+
),
|
|
292
336
|
lookup=glue.FieldLookup.LTE,
|
|
293
|
-
|
|
337
|
+
right=glue.Value(timestamp),
|
|
294
338
|
),
|
|
295
339
|
glue.Conditions(*_conditions, connector=glue.FilterConnector.OR),
|
|
296
340
|
),
|
|
@@ -302,9 +346,7 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
302
346
|
|
|
303
347
|
for m in metadatas_to_delete:
|
|
304
348
|
class_name = m.data['class_schema_reference']['ref']['object_id']
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
model_class = class_manager.import_model_class(class_name, schema_type)
|
|
349
|
+
model_class = class_manager.import_class(class_name)
|
|
308
350
|
obj = await (
|
|
309
351
|
model_class.objects.filter(_address__object_id=m.data['object_id'])
|
|
310
352
|
.using(LAKEHOUSE_DB_ALIAS)
|
|
@@ -317,8 +359,7 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
317
359
|
await obj.adelete()
|
|
318
360
|
|
|
319
361
|
for object_id, class_name in ids_to_revert:
|
|
320
|
-
|
|
321
|
-
model_class = class_manager.import_model_class(class_name, schema_type)
|
|
362
|
+
model_class = class_manager.import_class(class_name)
|
|
322
363
|
|
|
323
364
|
obj = await (
|
|
324
365
|
model_class.objects.filter(_address__object_id=object_id)
|
|
@@ -327,16 +368,17 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
327
368
|
.first()
|
|
328
369
|
.aexecute()
|
|
329
370
|
)
|
|
330
|
-
old_obj = await (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)
|
|
371
|
+
old_obj = await obj.aprevious_version() # type: ignore[union-attr]
|
|
372
|
+
# old_obj = await (
|
|
373
|
+
# model_class.objects.filter(_address__object_id=object_id, _metadata__updated_at__lte=timestamp)
|
|
374
|
+
# .using(LAKEHOUSE_DB_ALIAS)
|
|
375
|
+
# .order_by('-_metadata__updated_at')
|
|
376
|
+
# .first()
|
|
377
|
+
# .aexecute()
|
|
378
|
+
# )
|
|
337
379
|
|
|
338
380
|
if obj and old_obj:
|
|
339
|
-
for field, value in old_obj.
|
|
381
|
+
for field, value in (await old_obj.amodel_dump()).items():
|
|
340
382
|
setattr(obj, field, value)
|
|
341
383
|
|
|
342
384
|
await obj.asave()
|
|
@@ -373,9 +415,12 @@ async def async_rollback_transaction(transaction_id: str) -> None:
|
|
|
373
415
|
table=glue.SchemaReference(name='Metadata', version=glue.Version.LATEST),
|
|
374
416
|
where=glue.Conditions(
|
|
375
417
|
glue.Condition(
|
|
376
|
-
|
|
418
|
+
left=glue.FieldReferenceExpression(
|
|
419
|
+
field_reference=glue.FieldReference(field=_parent_field, table_name='Metadata'),
|
|
420
|
+
output_type=str,
|
|
421
|
+
),
|
|
377
422
|
lookup=glue.FieldLookup.EQ,
|
|
378
|
-
|
|
423
|
+
right=glue.Value(transaction_id, output_type=str),
|
|
379
424
|
)
|
|
380
425
|
),
|
|
381
426
|
order_by=[
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from amsdal_data.transactions.decorators import async_transaction, transaction
|
|
2
|
+
|
|
3
|
+
@transaction
|
|
1
4
|
def rollback_to_timestamp(timestamp: float) -> None:
|
|
2
5
|
"""
|
|
3
6
|
Rollback the data to the given timestamp
|
|
@@ -6,6 +9,7 @@ def rollback_to_timestamp(timestamp: float) -> None:
|
|
|
6
9
|
Returns:
|
|
7
10
|
None
|
|
8
11
|
"""
|
|
12
|
+
@transaction
|
|
9
13
|
def rollback_transaction(transaction_id: str) -> None:
|
|
10
14
|
"""
|
|
11
15
|
Rollback the data to the point in time before the given transaction
|
|
@@ -14,6 +18,7 @@ def rollback_transaction(transaction_id: str) -> None:
|
|
|
14
18
|
Returns:
|
|
15
19
|
None
|
|
16
20
|
"""
|
|
21
|
+
@async_transaction
|
|
17
22
|
async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
18
23
|
"""
|
|
19
24
|
Rollback the data to the given timestamp
|
|
@@ -22,6 +27,7 @@ async def async_rollback_to_timestamp(timestamp: float) -> None:
|
|
|
22
27
|
Returns:
|
|
23
28
|
None
|
|
24
29
|
"""
|
|
30
|
+
@async_transaction
|
|
25
31
|
async def async_rollback_transaction(transaction_id: str) -> None:
|
|
26
32
|
"""
|
|
27
33
|
Rollback the data to the point in time before the given transaction
|