mainsequence 4.2.2__tar.gz → 4.2.14__tar.gz
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.
- {mainsequence-4.2.2/mainsequence.egg-info → mainsequence-4.2.14}/PKG-INFO +1 -1
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +1 -1
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +6 -10
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/cli.py +1 -2
- mainsequence-4.2.14/mainsequence/cli/migrations.py +514 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/metatables/__init__.py +0 -9
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/metatables/core.py +251 -13
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/__init__.py +7 -10
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/build_operations.py +1 -1
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/persist_managers.py +2 -4
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/migrations.py +470 -182
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/sqlalchemy_contracts.py +82 -45
- {mainsequence-4.2.2 → mainsequence-4.2.14/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence.egg-info/SOURCES.txt +0 -1
- {mainsequence-4.2.2 → mainsequence-4.2.14}/pyproject.toml +1 -1
- mainsequence-4.2.14/tests/test_cli_migrations.py +336 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_meta_table_migrations.py +257 -368
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_meta_tables_client_models.py +224 -104
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_meta_tables_sqlalchemy_contracts.py +21 -11
- mainsequence-4.2.2/mainsequence/cli/migrations.py +0 -421
- mainsequence-4.2.2/mainsequence/client/metatables/migrations.py +0 -149
- mainsequence-4.2.2/tests/test_cli_migrations.py +0 -644
- {mainsequence-4.2.2 → mainsequence-4.2.14}/LICENSE +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/README.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/__main__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/base.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/client.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/client/utils.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/defaults.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/logconf.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/setup.cfg +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_cli.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_client.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_data_node_update_flow.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_filter_normalization.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_logconf.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_run_configuration.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_secret_client_model.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.2.2 → mainsequence-4.2.14}/tests/test_workspace_snapshot.py +0 -0
{mainsequence-4.2.2 → mainsequence-4.2.14}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md
RENAMED
|
@@ -184,7 +184,7 @@ class PricesTable(PlatformTimeIndexMetaData, Base):
|
|
|
184
184
|
|
|
185
185
|
Storage registration is migration-first. Add the storage model to the
|
|
186
186
|
MetaTable migration provider and run `mainsequence migrations upgrade --provider
|
|
187
|
-
...
|
|
187
|
+
... head`. Do not call `PricesTable.register()` directly and do not rely on
|
|
188
188
|
DataNode construction to register storage tables.
|
|
189
189
|
|
|
190
190
|
`PlatformTimeIndexMetaData.register()` remains SDK plumbing for the migration
|
|
@@ -113,9 +113,7 @@ The only migration workflow to recommend is the Main Sequence CLI lifecycle:
|
|
|
113
113
|
```bash
|
|
114
114
|
mainsequence migrations current --provider mainsequence_migrations:migration
|
|
115
115
|
mainsequence migrations revision --provider mainsequence_migrations:migration
|
|
116
|
-
mainsequence migrations
|
|
117
|
-
mainsequence migrations upgrade --provider mainsequence_migrations:migration --to head --dry-run
|
|
118
|
-
mainsequence migrations upgrade --provider mainsequence_migrations:migration --to head
|
|
116
|
+
mainsequence migrations upgrade --provider mainsequence_migrations:migration head
|
|
119
117
|
```
|
|
120
118
|
|
|
121
119
|
### 1. SQLAlchemy metadata is the authoring source
|
|
@@ -189,8 +187,8 @@ class Account(PlatformManagedMetaTable, Base):
|
|
|
189
187
|
Registration metadata belongs on the class. Do not call `Account.register()`
|
|
190
188
|
directly for platform-managed models. Add platform-managed models to the
|
|
191
189
|
selected `AlembicMetaTableMigration.metatable_models` list and let
|
|
192
|
-
`mainsequence migrations upgrade --provider ...
|
|
193
|
-
bind them.
|
|
190
|
+
`mainsequence migrations upgrade --provider ... head` reserve, migrate, refresh,
|
|
191
|
+
and bind them.
|
|
194
192
|
|
|
195
193
|
For platform-managed migration registration, the data source is resolved from
|
|
196
194
|
the active Main Sequence project/session, the same way DataNode does. Do not
|
|
@@ -310,13 +308,11 @@ request shape is reference material in the tutorial; the user-facing path is:
|
|
|
310
308
|
```bash
|
|
311
309
|
mainsequence migrations current --provider mainsequence_migrations:migration
|
|
312
310
|
mainsequence migrations revision --provider mainsequence_migrations:migration
|
|
313
|
-
mainsequence migrations
|
|
314
|
-
mainsequence migrations upgrade --provider mainsequence_migrations:migration --to head --dry-run
|
|
315
|
-
mainsequence migrations upgrade --provider mainsequence_migrations:migration --to head
|
|
311
|
+
mainsequence migrations upgrade --provider mainsequence_migrations:migration head
|
|
316
312
|
```
|
|
317
313
|
|
|
318
|
-
|
|
319
|
-
|
|
314
|
+
All migration commands prepare the provider, reserve provider-scoped
|
|
315
|
+
platform-managed MetaTables, bind backend names, and call Alembic directly.
|
|
320
316
|
`revision` accepts optional `-m/--message`; if omitted, the CLI uses
|
|
321
317
|
`migration`. `revision --autogenerate` is optional and requires an explicit
|
|
322
318
|
`--sqlalchemy-url` for the baseline database.
|
|
@@ -337,7 +337,6 @@ connection = typer.Typer(help="Connection commands")
|
|
|
337
337
|
organization = typer.Typer(help="Organization commands")
|
|
338
338
|
organization_teams_group = typer.Typer(help="Organization team commands")
|
|
339
339
|
meta_table_group = typer.Typer(help="MetaTable table-storage commands")
|
|
340
|
-
migrations_root_group = migrations_group
|
|
341
340
|
data_node_storage_group = typer.Typer(help="DataNode update/read-helper commands")
|
|
342
341
|
project = typer.Typer(help="Project commands (remote + local operations)")
|
|
343
342
|
project_list_group = typer.Typer(help="List-related project commands")
|
|
@@ -377,7 +376,6 @@ app.add_typer(organization, name="organization")
|
|
|
377
376
|
app.add_typer(skills, name="skills")
|
|
378
377
|
app.add_typer(meta_table_group, name="meta-table")
|
|
379
378
|
app.add_typer(meta_table_group, name="meta_table")
|
|
380
|
-
app.add_typer(migrations_root_group, name="migrations")
|
|
381
379
|
app.add_typer(data_node_storage_group, name="data-node")
|
|
382
380
|
app.add_typer(data_node_storage_group, name="data_node")
|
|
383
381
|
app.add_typer(data_node_storage_group, name="data-node-storage", hidden=True)
|
|
@@ -391,6 +389,7 @@ project.add_typer(project_jobs_group, name="jobs")
|
|
|
391
389
|
project_jobs_group.add_typer(project_job_runs_group, name="runs")
|
|
392
390
|
app.add_typer(settings, name="settings")
|
|
393
391
|
app.add_typer(sdk, name="sdk")
|
|
392
|
+
app.add_typer(migrations_group, name="migrations")
|
|
394
393
|
|
|
395
394
|
|
|
396
395
|
@app.callback()
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from mainsequence.client.metatables import (
|
|
14
|
+
DynamicTableDataSource,
|
|
15
|
+
DynamicTableDataSourceMigrationConnectionRequest,
|
|
16
|
+
)
|
|
17
|
+
from mainsequence.meta_tables.migrations import (
|
|
18
|
+
AlembicMetaTableMigration,
|
|
19
|
+
alembic_config_for_provider,
|
|
20
|
+
load_alembic_metatable_migration_provider,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
migrations = typer.Typer(help="Alembic-owned MetaTable migration commands")
|
|
24
|
+
REGISTER_ENDPOINT = "/orm/api/ts_manager/meta_table/register/"
|
|
25
|
+
RESERVE_MANAGED_ENDPOINT = "/orm/api/ts_manager/meta_table/reserve-managed/"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _AlembicOutput:
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
self._chunks: list[str] = []
|
|
31
|
+
|
|
32
|
+
def write(self, data: str) -> int:
|
|
33
|
+
text = str(data)
|
|
34
|
+
self._chunks.append(text)
|
|
35
|
+
sys.stderr.write(text)
|
|
36
|
+
sys.stderr.flush()
|
|
37
|
+
return len(text)
|
|
38
|
+
|
|
39
|
+
def flush(self) -> None:
|
|
40
|
+
sys.stderr.flush()
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def has_visible_output(self) -> bool:
|
|
44
|
+
return any(chunk.strip() for chunk in self._chunks)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _emit_status(message: str) -> None:
|
|
48
|
+
print(f"[mainsequence migrations] {message}", file=sys.stderr, flush=True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _emit_progress(message: str) -> None:
|
|
52
|
+
print(message, file=sys.stderr, flush=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _load_migration(provider: str | None) -> AlembicMetaTableMigration:
|
|
56
|
+
provider_label = provider or "<default>"
|
|
57
|
+
_emit_status(f"Loading migration provider {provider_label}...")
|
|
58
|
+
try:
|
|
59
|
+
migration = load_alembic_metatable_migration_provider(provider)
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
raise typer.BadParameter(str(exc), param_hint="--provider") from exc
|
|
62
|
+
_emit_status(
|
|
63
|
+
"Loaded migration provider "
|
|
64
|
+
f"package={migration.package} migration_namespace={migration.migration_namespace}"
|
|
65
|
+
)
|
|
66
|
+
return migration
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _load_alembic_command(command_name: str) -> Any:
|
|
70
|
+
_emit_status(f"Importing Alembic command module for {command_name}...")
|
|
71
|
+
try:
|
|
72
|
+
from alembic import command
|
|
73
|
+
except ImportError as exc:
|
|
74
|
+
raise typer.BadParameter("Alembic is required for migration commands.") from exc
|
|
75
|
+
_emit_status(f"Imported Alembic command module for {command_name}.")
|
|
76
|
+
return command
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _jsonable(value: Any) -> Any:
|
|
80
|
+
if hasattr(value, "model_dump"):
|
|
81
|
+
return value.model_dump(mode="json")
|
|
82
|
+
if dataclasses.is_dataclass(value):
|
|
83
|
+
return dataclasses.asdict(value)
|
|
84
|
+
if isinstance(value, dict):
|
|
85
|
+
return {str(key): _jsonable(item) for key, item in value.items()}
|
|
86
|
+
if isinstance(value, (list, tuple, set)):
|
|
87
|
+
return [_jsonable(item) for item in value]
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _emit(payload: Any, *, json_output: bool = False) -> None:
|
|
92
|
+
if json_output or _json_output_enabled():
|
|
93
|
+
typer.echo(json.dumps(_jsonable(payload), indent=2, ensure_ascii=False))
|
|
94
|
+
return
|
|
95
|
+
if isinstance(payload, str):
|
|
96
|
+
typer.echo(payload)
|
|
97
|
+
return
|
|
98
|
+
for key, value in _jsonable(payload).items():
|
|
99
|
+
typer.echo(f"{key}: {value}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _json_output_enabled() -> bool:
|
|
103
|
+
ctx = click.get_current_context(silent=True)
|
|
104
|
+
if ctx is None:
|
|
105
|
+
return False
|
|
106
|
+
root = ctx.find_root()
|
|
107
|
+
obj = getattr(root, "obj", None) or {}
|
|
108
|
+
return bool(obj.get("json_output"))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _item_value(item: Any, key: str) -> Any:
|
|
112
|
+
if isinstance(item, Mapping):
|
|
113
|
+
return item.get(key)
|
|
114
|
+
return getattr(item, key, None)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _contract_physical_table_name(item: Any) -> Any:
|
|
118
|
+
contract = _item_value(item, "table_contract")
|
|
119
|
+
if contract is None:
|
|
120
|
+
return None
|
|
121
|
+
physical = contract.get("physical") if isinstance(contract, Mapping) else getattr(contract, "physical", None)
|
|
122
|
+
if physical is None:
|
|
123
|
+
return None
|
|
124
|
+
if isinstance(physical, Mapping):
|
|
125
|
+
return physical.get("table_name")
|
|
126
|
+
return getattr(physical, "table_name", None)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _meta_table_uid(item: Any) -> str | None:
|
|
130
|
+
if item is None:
|
|
131
|
+
return None
|
|
132
|
+
if isinstance(item, Mapping):
|
|
133
|
+
uid = item.get("meta_table_uid") or item.get("uid")
|
|
134
|
+
else:
|
|
135
|
+
uid = getattr(item, "meta_table_uid", None) or getattr(item, "uid", None)
|
|
136
|
+
if uid in (None, ""):
|
|
137
|
+
return None
|
|
138
|
+
return str(uid)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _include_alembic_registry_in_scope(
|
|
142
|
+
migration: AlembicMetaTableMigration,
|
|
143
|
+
prepared: Any,
|
|
144
|
+
registry_meta_table: Any,
|
|
145
|
+
) -> None:
|
|
146
|
+
registry_meta_table = registry_meta_table or migration.alembic_registry.get_meta_table()
|
|
147
|
+
registry_uid = _meta_table_uid(registry_meta_table)
|
|
148
|
+
if registry_uid in (None, ""):
|
|
149
|
+
return
|
|
150
|
+
prepared.meta_table_uids = list(
|
|
151
|
+
dict.fromkeys([registry_uid, *list(prepared.meta_table_uids)])
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _metatable_message(
|
|
156
|
+
*,
|
|
157
|
+
endpoint: str,
|
|
158
|
+
action: str,
|
|
159
|
+
model: type[Any],
|
|
160
|
+
item: Any,
|
|
161
|
+
) -> str:
|
|
162
|
+
model_name = getattr(model, "__name__", repr(model))
|
|
163
|
+
identifier = (
|
|
164
|
+
_item_value(item, "identifier")
|
|
165
|
+
or getattr(model, "__metatable_identifier__", None)
|
|
166
|
+
or model_name
|
|
167
|
+
)
|
|
168
|
+
uid = _item_value(item, "meta_table_uid") or _item_value(item, "uid")
|
|
169
|
+
physical_table_name = (
|
|
170
|
+
_item_value(item, "physical_table_name")
|
|
171
|
+
or _contract_physical_table_name(item)
|
|
172
|
+
)
|
|
173
|
+
provisioning_status = _item_value(item, "provisioning_status")
|
|
174
|
+
created = _item_value(item, "created")
|
|
175
|
+
matched_by = _item_value(item, "matched_by")
|
|
176
|
+
|
|
177
|
+
parts = [
|
|
178
|
+
f"POST {endpoint}",
|
|
179
|
+
f"{action} MetaTable identifier={identifier}",
|
|
180
|
+
]
|
|
181
|
+
if model_name != identifier:
|
|
182
|
+
parts.append(f"model={model_name}")
|
|
183
|
+
if uid not in (None, ""):
|
|
184
|
+
parts.append(f"uid={uid}")
|
|
185
|
+
if physical_table_name not in (None, ""):
|
|
186
|
+
parts.append(f"physical_table={physical_table_name}")
|
|
187
|
+
if provisioning_status not in (None, ""):
|
|
188
|
+
parts.append(f"provisioning_status={provisioning_status}")
|
|
189
|
+
if created is not None:
|
|
190
|
+
parts.append(f"created={created}")
|
|
191
|
+
if matched_by not in (None, ""):
|
|
192
|
+
parts.append(f"matched_by={matched_by}")
|
|
193
|
+
return " ".join(parts)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _emit_metatable_registration(model: type[Any], item: Any) -> None:
|
|
197
|
+
_emit_progress(
|
|
198
|
+
_metatable_message(
|
|
199
|
+
endpoint=REGISTER_ENDPOINT,
|
|
200
|
+
action="registered",
|
|
201
|
+
model=model,
|
|
202
|
+
item=item,
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _emit_metatable_reservation_request(
|
|
208
|
+
models: Sequence[type[Any]],
|
|
209
|
+
tables: Sequence[Any],
|
|
210
|
+
) -> None:
|
|
211
|
+
identifiers = []
|
|
212
|
+
for model, table in zip(models, tables, strict=True):
|
|
213
|
+
identifiers.append(
|
|
214
|
+
str(
|
|
215
|
+
_item_value(table, "identifier")
|
|
216
|
+
or getattr(model, "__metatable_identifier__", None)
|
|
217
|
+
or getattr(model, "__name__", repr(model))
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
_emit_status(
|
|
221
|
+
f"Sending POST {RESERVE_MANAGED_ENDPOINT} request for {len(tables)} "
|
|
222
|
+
f"MetaTables identifiers={','.join(identifiers)}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _emit_metatable_reservation(model: type[Any], item: Any) -> None:
|
|
227
|
+
_emit_progress(
|
|
228
|
+
_metatable_message(
|
|
229
|
+
endpoint=RESERVE_MANAGED_ENDPOINT,
|
|
230
|
+
action="reserved",
|
|
231
|
+
model=model,
|
|
232
|
+
item=item,
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _prepare_alembic_config(
|
|
238
|
+
migration: AlembicMetaTableMigration,
|
|
239
|
+
*,
|
|
240
|
+
timeout: float | None,
|
|
241
|
+
ttl_seconds: int,
|
|
242
|
+
alembic_output: _AlembicOutput,
|
|
243
|
+
) -> tuple[Any, Any]:
|
|
244
|
+
_emit_status("Ensuring Alembic registry MetaTable...")
|
|
245
|
+
registry_meta_table = migration.ensure_alembic_registry(
|
|
246
|
+
timeout=timeout,
|
|
247
|
+
on_metatable_registered=_emit_metatable_registration,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
_emit_status("Preparing platform-managed MetaTable reservations...")
|
|
251
|
+
prepared = migration.prepare_for_alembic(
|
|
252
|
+
timeout=timeout,
|
|
253
|
+
on_metatable_reservation_request=_emit_metatable_reservation_request,
|
|
254
|
+
on_metatable_reservation_status=_emit_status,
|
|
255
|
+
on_metatable_reserved=_emit_metatable_reservation,
|
|
256
|
+
)
|
|
257
|
+
_include_alembic_registry_in_scope(migration, prepared, registry_meta_table)
|
|
258
|
+
_emit_status(
|
|
259
|
+
"Prepared migration scope "
|
|
260
|
+
f"data_source_uid={prepared.data_source_uid} "
|
|
261
|
+
f"meta_table_count={len(prepared.meta_table_uids)}"
|
|
262
|
+
)
|
|
263
|
+
_emit_status(f"Loading DynamicTableDataSource uid={prepared.data_source_uid}...")
|
|
264
|
+
data_source = DynamicTableDataSource.get_by_uid(prepared.data_source_uid)
|
|
265
|
+
_emit_status(
|
|
266
|
+
"Requesting scoped migration connection "
|
|
267
|
+
f"meta_table_count={len(prepared.meta_table_uids)} ttl_seconds={ttl_seconds}..."
|
|
268
|
+
)
|
|
269
|
+
connection = data_source.issue_migration_connection(
|
|
270
|
+
DynamicTableDataSourceMigrationConnectionRequest(
|
|
271
|
+
package=migration.package,
|
|
272
|
+
migration_namespace=migration.migration_namespace,
|
|
273
|
+
meta_table_uids=prepared.meta_table_uids,
|
|
274
|
+
ttl_seconds=ttl_seconds,
|
|
275
|
+
),
|
|
276
|
+
timeout=timeout,
|
|
277
|
+
)
|
|
278
|
+
_emit_status("Scoped migration connection acquired.")
|
|
279
|
+
_emit_status("Building Alembic config...")
|
|
280
|
+
config = alembic_config_for_provider(
|
|
281
|
+
migration,
|
|
282
|
+
sqlalchemy_url=connection.uri,
|
|
283
|
+
owner_role_name=connection.owner_role_name or prepared.owner_role_name,
|
|
284
|
+
stdout=alembic_output,
|
|
285
|
+
output_buffer=alembic_output,
|
|
286
|
+
)
|
|
287
|
+
_emit_status("Alembic config built.")
|
|
288
|
+
return prepared, config
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _next_sequential_revision_id(
|
|
292
|
+
migration: AlembicMetaTableMigration,
|
|
293
|
+
*,
|
|
294
|
+
alembic_output: _AlembicOutput,
|
|
295
|
+
) -> str:
|
|
296
|
+
_emit_status("Importing Alembic ScriptDirectory for revision id scan...")
|
|
297
|
+
try:
|
|
298
|
+
from alembic.script import ScriptDirectory
|
|
299
|
+
except ImportError as exc:
|
|
300
|
+
raise RuntimeError("Alembic is required for revision generation.") from exc
|
|
301
|
+
_emit_status("Imported Alembic ScriptDirectory.")
|
|
302
|
+
|
|
303
|
+
_emit_status("Scanning Alembic revision directory for next sequential id...")
|
|
304
|
+
script = ScriptDirectory.from_config(
|
|
305
|
+
alembic_config_for_provider(
|
|
306
|
+
migration,
|
|
307
|
+
sqlalchemy_url="postgresql://",
|
|
308
|
+
stdout=alembic_output,
|
|
309
|
+
output_buffer=alembic_output,
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
heads = list(script.get_heads())
|
|
313
|
+
if len(heads) > 1:
|
|
314
|
+
raise typer.BadParameter(
|
|
315
|
+
"Sequential revision IDs require a single Alembic head. Pass --rev-id "
|
|
316
|
+
"explicitly for branched histories.",
|
|
317
|
+
param_hint="--rev-id",
|
|
318
|
+
)
|
|
319
|
+
if heads and not re.fullmatch(r"\d{4,}", str(heads[0])):
|
|
320
|
+
raise typer.BadParameter(
|
|
321
|
+
"Sequential revision IDs require the current Alembic head to be numeric. "
|
|
322
|
+
"Pass --rev-id explicitly for non-numeric histories.",
|
|
323
|
+
param_hint="--rev-id",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
numeric_revisions: list[int] = []
|
|
327
|
+
for revision in script.walk_revisions():
|
|
328
|
+
revision_id = str(revision.revision)
|
|
329
|
+
if re.fullmatch(r"\d{4,}", revision_id):
|
|
330
|
+
numeric_revisions.append(int(revision_id))
|
|
331
|
+
next_revision_id = f"{max(numeric_revisions, default=0) + 1:04d}"
|
|
332
|
+
_emit_status(f"Next Alembic revision id is {next_revision_id}.")
|
|
333
|
+
return next_revision_id
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@migrations.command("current")
|
|
337
|
+
def current(
|
|
338
|
+
provider: str | None = typer.Option(
|
|
339
|
+
None,
|
|
340
|
+
"--provider",
|
|
341
|
+
help="Migration provider reference, for example msm.migrations:migration.",
|
|
342
|
+
),
|
|
343
|
+
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
|
344
|
+
timeout: float | None = typer.Option(None, "--timeout"),
|
|
345
|
+
ttl_seconds: int = typer.Option(900, "--ttl-seconds", min=1),
|
|
346
|
+
) -> None:
|
|
347
|
+
"""Read current Alembic revision through a scoped migration credential."""
|
|
348
|
+
|
|
349
|
+
command = _load_alembic_command("current")
|
|
350
|
+
migration = _load_migration(provider)
|
|
351
|
+
alembic_output = _AlembicOutput()
|
|
352
|
+
_, config = _prepare_alembic_config(
|
|
353
|
+
migration,
|
|
354
|
+
timeout=timeout,
|
|
355
|
+
ttl_seconds=ttl_seconds,
|
|
356
|
+
alembic_output=alembic_output,
|
|
357
|
+
)
|
|
358
|
+
_emit_status("Starting Alembic current now...")
|
|
359
|
+
command.current(config, verbose=verbose)
|
|
360
|
+
if not alembic_output.has_visible_output:
|
|
361
|
+
_emit_status(
|
|
362
|
+
"Alembic current produced no revision output. The version table is "
|
|
363
|
+
"empty or Alembic found no current revision for this migration scope."
|
|
364
|
+
)
|
|
365
|
+
_emit_status("Alembic current finished.")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@migrations.command("revision")
|
|
369
|
+
def revision(
|
|
370
|
+
message: str | None = typer.Option(
|
|
371
|
+
None,
|
|
372
|
+
"--message",
|
|
373
|
+
"-m",
|
|
374
|
+
help="Alembic revision message. Defaults to 'migration'.",
|
|
375
|
+
),
|
|
376
|
+
autogenerate: bool = typer.Option(
|
|
377
|
+
True,
|
|
378
|
+
"--autogenerate/--no-autogenerate",
|
|
379
|
+
help="Use Alembic autogenerate against the reserved MetaTable metadata.",
|
|
380
|
+
),
|
|
381
|
+
provider: str | None = typer.Option(
|
|
382
|
+
None,
|
|
383
|
+
"--provider",
|
|
384
|
+
help="Migration provider reference, for example msm.migrations:migration.",
|
|
385
|
+
),
|
|
386
|
+
rev_id: str | None = typer.Option(None, "--rev-id", help="Explicit Alembic revision id."),
|
|
387
|
+
head: str = typer.Option("head", "--head", help="Alembic head to base the revision on."),
|
|
388
|
+
timeout: float | None = typer.Option(None, "--timeout"),
|
|
389
|
+
ttl_seconds: int = typer.Option(900, "--ttl-seconds", min=1),
|
|
390
|
+
json_output: bool = typer.Option(False, "--json", help="Emit JSON."),
|
|
391
|
+
) -> None:
|
|
392
|
+
"""Create a normal Alembic revision for the selected provider."""
|
|
393
|
+
|
|
394
|
+
command = _load_alembic_command("revision")
|
|
395
|
+
migration = _load_migration(provider)
|
|
396
|
+
resolved_message = (message or "").strip() or "migration"
|
|
397
|
+
alembic_output = _AlembicOutput()
|
|
398
|
+
resolved_rev_id = rev_id or _next_sequential_revision_id(
|
|
399
|
+
migration,
|
|
400
|
+
alembic_output=alembic_output,
|
|
401
|
+
)
|
|
402
|
+
prepared, config = _prepare_alembic_config(
|
|
403
|
+
migration,
|
|
404
|
+
timeout=timeout,
|
|
405
|
+
ttl_seconds=ttl_seconds,
|
|
406
|
+
alembic_output=alembic_output,
|
|
407
|
+
)
|
|
408
|
+
_emit_status(f"Starting Alembic revision now rev_id={resolved_rev_id}...")
|
|
409
|
+
script = command.revision(
|
|
410
|
+
config,
|
|
411
|
+
message=resolved_message,
|
|
412
|
+
autogenerate=autogenerate,
|
|
413
|
+
rev_id=resolved_rev_id,
|
|
414
|
+
head=head,
|
|
415
|
+
)
|
|
416
|
+
_emit_status("Alembic revision finished.")
|
|
417
|
+
_emit(
|
|
418
|
+
{
|
|
419
|
+
"revision": getattr(script, "revision", None),
|
|
420
|
+
"path": getattr(script, "path", None),
|
|
421
|
+
"package": migration.package,
|
|
422
|
+
"migration_namespace": migration.migration_namespace,
|
|
423
|
+
"meta_table_uids": prepared.meta_table_uids,
|
|
424
|
+
},
|
|
425
|
+
json_output=json_output,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
@migrations.command("upgrade")
|
|
430
|
+
def upgrade(
|
|
431
|
+
target_revision: str = typer.Argument("head", help="Target Alembic revision."),
|
|
432
|
+
provider: str | None = typer.Option(
|
|
433
|
+
None,
|
|
434
|
+
"--provider",
|
|
435
|
+
help="Migration provider reference, for example msm.migrations:migration.",
|
|
436
|
+
),
|
|
437
|
+
timeout: float | None = typer.Option(None, "--timeout"),
|
|
438
|
+
ttl_seconds: int = typer.Option(900, "--ttl-seconds", min=1),
|
|
439
|
+
json_output: bool = typer.Option(False, "--json", help="Emit JSON."),
|
|
440
|
+
) -> None:
|
|
441
|
+
"""Run Alembic upgrade directly and refresh MetaTable catalog rows."""
|
|
442
|
+
|
|
443
|
+
command = _load_alembic_command("upgrade")
|
|
444
|
+
migration = _load_migration(provider)
|
|
445
|
+
alembic_output = _AlembicOutput()
|
|
446
|
+
prepared, config = _prepare_alembic_config(
|
|
447
|
+
migration,
|
|
448
|
+
timeout=timeout,
|
|
449
|
+
ttl_seconds=ttl_seconds,
|
|
450
|
+
alembic_output=alembic_output,
|
|
451
|
+
)
|
|
452
|
+
_emit_status(f"Starting Alembic upgrade now target={target_revision}...")
|
|
453
|
+
command.upgrade(config, target_revision)
|
|
454
|
+
_emit_status("Refreshing MetaTable catalog after upgrade...")
|
|
455
|
+
registered = migration.refresh_metatable_catalog(
|
|
456
|
+
timeout=timeout,
|
|
457
|
+
on_metatable_registered=_emit_metatable_registration,
|
|
458
|
+
)
|
|
459
|
+
_emit_status("MetaTable catalog refresh finished.")
|
|
460
|
+
_emit(
|
|
461
|
+
{
|
|
462
|
+
"ok": True,
|
|
463
|
+
"revision": target_revision,
|
|
464
|
+
"package": migration.package,
|
|
465
|
+
"migration_namespace": migration.migration_namespace,
|
|
466
|
+
"meta_table_uids": prepared.meta_table_uids,
|
|
467
|
+
"registered_count": len(registered),
|
|
468
|
+
},
|
|
469
|
+
json_output=json_output,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@migrations.command("downgrade")
|
|
474
|
+
def downgrade(
|
|
475
|
+
target_revision: str = typer.Argument(..., help="Target Alembic downgrade revision."),
|
|
476
|
+
provider: str | None = typer.Option(
|
|
477
|
+
None,
|
|
478
|
+
"--provider",
|
|
479
|
+
help="Migration provider reference, for example msm.migrations:migration.",
|
|
480
|
+
),
|
|
481
|
+
timeout: float | None = typer.Option(None, "--timeout"),
|
|
482
|
+
ttl_seconds: int = typer.Option(900, "--ttl-seconds", min=1),
|
|
483
|
+
json_output: bool = typer.Option(False, "--json", help="Emit JSON."),
|
|
484
|
+
) -> None:
|
|
485
|
+
"""Run Alembic downgrade directly and refresh MetaTable catalog rows."""
|
|
486
|
+
|
|
487
|
+
command = _load_alembic_command("downgrade")
|
|
488
|
+
migration = _load_migration(provider)
|
|
489
|
+
alembic_output = _AlembicOutput()
|
|
490
|
+
prepared, config = _prepare_alembic_config(
|
|
491
|
+
migration,
|
|
492
|
+
timeout=timeout,
|
|
493
|
+
ttl_seconds=ttl_seconds,
|
|
494
|
+
alembic_output=alembic_output,
|
|
495
|
+
)
|
|
496
|
+
_emit_status(f"Starting Alembic downgrade now target={target_revision}...")
|
|
497
|
+
command.downgrade(config, target_revision)
|
|
498
|
+
_emit_status("Refreshing MetaTable catalog after downgrade...")
|
|
499
|
+
registered = migration.refresh_metatable_catalog(
|
|
500
|
+
timeout=timeout,
|
|
501
|
+
on_metatable_registered=_emit_metatable_registration,
|
|
502
|
+
)
|
|
503
|
+
_emit_status("MetaTable catalog refresh finished.")
|
|
504
|
+
_emit(
|
|
505
|
+
{
|
|
506
|
+
"ok": True,
|
|
507
|
+
"revision": target_revision,
|
|
508
|
+
"package": migration.package,
|
|
509
|
+
"migration_namespace": migration.migration_namespace,
|
|
510
|
+
"meta_table_uids": prepared.meta_table_uids,
|
|
511
|
+
"registered_count": len(registered),
|
|
512
|
+
},
|
|
513
|
+
json_output=json_output,
|
|
514
|
+
)
|
|
@@ -4,14 +4,9 @@ import sys
|
|
|
4
4
|
import types
|
|
5
5
|
|
|
6
6
|
from . import core as core
|
|
7
|
-
from . import migrations as migrations
|
|
8
7
|
from .core import * # noqa: F403
|
|
9
|
-
from .migrations import * # noqa: F403
|
|
10
|
-
|
|
11
|
-
migrations._bind_meta_table_migration_methods(core.MetaTable)
|
|
12
8
|
|
|
13
9
|
__all__ = [
|
|
14
|
-
*migrations.__all__,
|
|
15
10
|
*core.__all__,
|
|
16
11
|
]
|
|
17
12
|
|
|
@@ -19,8 +14,6 @@ __all__ = [
|
|
|
19
14
|
def __getattr__(name: str):
|
|
20
15
|
if hasattr(core, name):
|
|
21
16
|
return getattr(core, name)
|
|
22
|
-
if hasattr(migrations, name):
|
|
23
|
-
return getattr(migrations, name)
|
|
24
17
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
25
18
|
|
|
26
19
|
|
|
@@ -28,8 +21,6 @@ class _MetaTablesModule(types.ModuleType):
|
|
|
28
21
|
def __setattr__(self, name: str, value):
|
|
29
22
|
if hasattr(core, name):
|
|
30
23
|
setattr(core, name, value)
|
|
31
|
-
if hasattr(migrations, name):
|
|
32
|
-
setattr(migrations, name, value)
|
|
33
24
|
super().__setattr__(name, value)
|
|
34
25
|
|
|
35
26
|
|