amsdal 0.5.7__cp312-cp312-macosx_10_13_universal2.whl → 0.5.9__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/__about__.py +1 -1
- amsdal/__migrations__/0003_update_class_file.py +91 -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 +5 -0
- amsdal/configs/main.pyi +3 -0
- amsdal/contrib/__init__.cpython-312-darwin.so +0 -0
- amsdal/contrib/auth/lifecycle/consumer.pyi +1 -1
- amsdal/fixtures/__init__.cpython-312-darwin.so +0 -0
- amsdal/fixtures/manager.cpython-312-darwin.so +0 -0
- amsdal/fixtures/manager.pyi +1 -1
- amsdal/fixtures/utils.cpython-312-darwin.so +0 -0
- amsdal/manager.cpython-312-darwin.so +0 -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_property.py +1 -0
- amsdal/models/core/file.py +175 -81
- amsdal/schemas/core/file/properties/validate_data.py +2 -3
- amsdal/schemas/manager.cpython-312-darwin.so +0 -0
- amsdal/services/__init__.cpython-312-darwin.so +0 -0
- amsdal/services/transaction_execution.cpython-312-darwin.so +0 -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/tests/migrations.py +45 -67
- {amsdal-0.5.7.dist-info → amsdal-0.5.9.dist-info}/METADATA +1 -1
- {amsdal-0.5.7.dist-info → amsdal-0.5.9.dist-info}/RECORD +67 -62
- {amsdal-0.5.7.dist-info → amsdal-0.5.9.dist-info}/WHEEL +0 -0
- {amsdal-0.5.7.dist-info → amsdal-0.5.9.dist-info}/licenses/LICENSE.txt +0 -0
- {amsdal-0.5.7.dist-info → amsdal-0.5.9.dist-info}/top_level.txt +0 -0
Binary file
|
Binary file
|
@@ -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.AMSDAL_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.AMSDAL_MEDIA_ROOT
|
53
|
+
_base_url = base_url or settings.AMSDAL_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]: ...
|
amsdal/utils/tests/migrations.py
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
import contextlib
|
2
|
+
from asyncio import iscoroutine
|
2
3
|
|
3
4
|
from amsdal_data.connections.historical.schema_version_manager import AsyncHistoricalSchemaVersionManager
|
4
5
|
from amsdal_data.connections.historical.schema_version_manager import HistoricalSchemaVersionManager
|
5
6
|
from amsdal_models.migration import migrations
|
6
7
|
from amsdal_models.migration.executors.default_executor import DefaultAsyncMigrationExecutor
|
7
8
|
from amsdal_models.migration.executors.default_executor import DefaultMigrationExecutor
|
8
|
-
from amsdal_models.migration.file_migration_executor import
|
9
|
+
from amsdal_models.migration.file_migration_executor import AsyncFileMigrationExecutorManager
|
10
|
+
from amsdal_models.migration.file_migration_executor import FileMigrationExecutorManager
|
9
11
|
from amsdal_models.migration.file_migration_generator import SimpleFileMigrationGenerator
|
12
|
+
from amsdal_models.migration.file_migration_store import AsyncFileMigrationStore
|
13
|
+
from amsdal_models.migration.file_migration_store import FileMigrationStore
|
10
14
|
from amsdal_models.migration.file_migration_writer import FileMigrationWriter
|
11
|
-
from amsdal_models.migration.migrations import MigrateData
|
12
15
|
from amsdal_models.migration.migrations import MigrationSchemas
|
13
16
|
from amsdal_models.migration.migrations_loader import MigrationsLoader
|
14
|
-
from amsdal_models.migration.utils import contrib_to_module_root_path
|
15
17
|
from amsdal_models.schemas.class_schema_loader import ClassSchemaLoader
|
16
18
|
from amsdal_utils.models.enums import ModuleType
|
17
19
|
|
@@ -22,29 +24,28 @@ from amsdal.configs.main import settings
|
|
22
24
|
def migrate() -> None:
|
23
25
|
schemas = MigrationSchemas()
|
24
26
|
executor = DefaultMigrationExecutor(schemas, use_foreign_keys=True)
|
27
|
+
store = FileMigrationStore(settings.migrations_root_path)
|
28
|
+
store.init_migration_table()
|
29
|
+
|
30
|
+
class UserMigrationsLoader(MigrationsLoader):
|
31
|
+
def __init__(self):
|
32
|
+
super().__init__(settings.migrations_root_path, ModuleType.USER)
|
33
|
+
self._migrations_files = []
|
25
34
|
|
26
35
|
with contextlib.suppress(Exception):
|
27
36
|
HistoricalSchemaVersionManager().object_classes # noqa: B018
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
# migrate core and contrib due to applied migrations
|
39
|
+
executor_manager = FileMigrationExecutorManager(
|
40
|
+
core_migrations_path=CORE_MIGRATIONS_PATH,
|
41
|
+
app_migrations_loader=UserMigrationsLoader(),
|
42
|
+
executor=executor,
|
43
|
+
store=store,
|
44
|
+
contrib=settings.CONTRIBS,
|
35
45
|
)
|
46
|
+
executor_manager.execute()
|
36
47
|
|
37
|
-
for
|
38
|
-
contrib_root_path = contrib_to_module_root_path(contrib)
|
39
|
-
_migrate_per_loader(
|
40
|
-
executor,
|
41
|
-
MigrationsLoader(
|
42
|
-
migrations_dir=contrib_root_path / settings.MIGRATIONS_DIRECTORY_NAME,
|
43
|
-
module_type=ModuleType.CONTRIB,
|
44
|
-
module_name=contrib,
|
45
|
-
),
|
46
|
-
)
|
47
|
-
|
48
|
+
# always apply migrations for user models
|
48
49
|
user_schema_loader = ClassSchemaLoader(
|
49
50
|
settings.USER_MODELS_MODULE,
|
50
51
|
class_filter=lambda cls: cls.__module_type__ == ModuleType.USER,
|
@@ -85,46 +86,31 @@ def migrate() -> None:
|
|
85
86
|
executor.flush_buffer()
|
86
87
|
|
87
88
|
|
88
|
-
def _migrate_per_loader(executor: DefaultMigrationExecutor, loader: MigrationsLoader) -> None:
|
89
|
-
for _migration in loader:
|
90
|
-
migration_class = SimpleFileMigrationExecutorManager.get_migration_class(_migration)
|
91
|
-
migration_class_instance = migration_class()
|
92
|
-
|
93
|
-
for _operation in migration_class_instance.operations:
|
94
|
-
if isinstance(_operation, MigrateData):
|
95
|
-
executor.flush_buffer()
|
96
|
-
|
97
|
-
_operation.forward(executor)
|
98
|
-
|
99
|
-
executor.flush_buffer()
|
100
|
-
|
101
|
-
|
102
89
|
async def async_migrate() -> None:
|
103
90
|
schemas = MigrationSchemas()
|
104
91
|
executor = DefaultAsyncMigrationExecutor(schemas)
|
92
|
+
store = AsyncFileMigrationStore(settings.migrations_root_path)
|
93
|
+
await store.init_migration_table()
|
94
|
+
|
95
|
+
class UserMigrationsLoader(MigrationsLoader):
|
96
|
+
def __init__(self):
|
97
|
+
super().__init__(settings.migrations_root_path, ModuleType.USER)
|
98
|
+
self._migrations_files = []
|
105
99
|
|
106
100
|
with contextlib.suppress(Exception):
|
107
101
|
await AsyncHistoricalSchemaVersionManager().object_classes
|
108
102
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
103
|
+
# migrate core and contrib due to applied migrations
|
104
|
+
executor_manager = AsyncFileMigrationExecutorManager(
|
105
|
+
core_migrations_path=CORE_MIGRATIONS_PATH,
|
106
|
+
app_migrations_loader=UserMigrationsLoader(),
|
107
|
+
executor=executor,
|
108
|
+
store=store,
|
109
|
+
contrib=settings.CONTRIBS,
|
115
110
|
)
|
111
|
+
await executor_manager.execute()
|
116
112
|
|
117
|
-
for
|
118
|
-
contrib_root_path = contrib_to_module_root_path(contrib)
|
119
|
-
await _async_migrate_per_loader(
|
120
|
-
executor,
|
121
|
-
MigrationsLoader(
|
122
|
-
migrations_dir=contrib_root_path / settings.MIGRATIONS_DIRECTORY_NAME,
|
123
|
-
module_type=ModuleType.CONTRIB,
|
124
|
-
module_name=contrib,
|
125
|
-
),
|
126
|
-
)
|
127
|
-
|
113
|
+
# always apply migrations for user models
|
128
114
|
user_schema_loader = ClassSchemaLoader(
|
129
115
|
settings.USER_MODELS_MODULE,
|
130
116
|
class_filter=lambda cls: cls.__module_type__ == ModuleType.USER,
|
@@ -145,7 +131,10 @@ async def async_migrate() -> None:
|
|
145
131
|
new_schema=_operation_data.new_schema.model_dump(),
|
146
132
|
)
|
147
133
|
|
148
|
-
_operation.forward(executor)
|
134
|
+
forward_result = _operation.forward(executor)
|
135
|
+
|
136
|
+
if iscoroutine(forward_result):
|
137
|
+
await forward_result
|
149
138
|
|
150
139
|
for object_schema in _cycle_schemas:
|
151
140
|
for _operation_data in SimpleFileMigrationGenerator.build_operations(
|
@@ -160,20 +149,9 @@ async def async_migrate() -> None:
|
|
160
149
|
new_schema=_operation_data.new_schema.model_dump(),
|
161
150
|
)
|
162
151
|
|
163
|
-
_operation.forward(executor)
|
164
|
-
|
165
|
-
await executor.flush_buffer()
|
166
|
-
|
167
|
-
|
168
|
-
async def _async_migrate_per_loader(executor: DefaultAsyncMigrationExecutor, loader: MigrationsLoader) -> None:
|
169
|
-
for _migration in loader:
|
170
|
-
migration_class = SimpleFileMigrationExecutorManager.get_migration_class(_migration)
|
171
|
-
migration_class_instance = migration_class()
|
152
|
+
forward_result = _operation.forward(executor)
|
172
153
|
|
173
|
-
|
174
|
-
|
175
|
-
await executor.flush_buffer()
|
154
|
+
if iscoroutine(forward_result):
|
155
|
+
await forward_result
|
176
156
|
|
177
|
-
|
178
|
-
|
179
|
-
await executor.flush_buffer()
|
157
|
+
await executor.flush_buffer()
|