mainsequence 4.2.25__tar.gz → 4.2.37__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.25/mainsequence.egg-info → mainsequence-4.2.37}/PKG-INFO +1 -1
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +20 -14
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +46 -42
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/cli.py +0 -3
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/migrations.py +82 -25
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/metatables/core.py +91 -206
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/__init__.py +11 -6
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/migrations.py +158 -420
- mainsequence-4.2.37/mainsequence/meta_tables/schema_names.py +304 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/sqlalchemy_contracts.py +238 -986
- {mainsequence-4.2.25 → mainsequence-4.2.37/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence.egg-info/SOURCES.txt +2 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/pyproject.toml +1 -1
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_cli.py +0 -3
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_cli_migrations.py +171 -24
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_meta_table_migrations.py +331 -245
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_meta_tables_client_models.py +159 -219
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_meta_tables_sqlalchemy_contracts.py +329 -315
- mainsequence-4.2.37/tests/test_schema_names.py +104 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/LICENSE +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/README.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/__main__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/base.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/client.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/metatables/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/client/utils.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/defaults.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/logconf.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/setup.cfg +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_client.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_data_node_update_flow.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_filter_normalization.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_logconf.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_run_configuration.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_secret_client_model.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.2.25 → mainsequence-4.2.37}/tests/test_workspace_snapshot.py +0 -0
|
@@ -139,7 +139,10 @@ import datetime
|
|
|
139
139
|
from sqlalchemy import DateTime, Float, MetaData
|
|
140
140
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
141
141
|
|
|
142
|
-
from mainsequence.meta_tables import PlatformTimeIndexMetaData
|
|
142
|
+
from mainsequence.meta_tables import PlatformTimeIndexMetaData, schema_table_name
|
|
143
|
+
|
|
144
|
+
PROJECT_NAME = "<project_name>"
|
|
145
|
+
PRICES_TABLE_NAME = schema_table_name(PROJECT_NAME, "prices")
|
|
143
146
|
|
|
144
147
|
|
|
145
148
|
class Base(DeclarativeBase):
|
|
@@ -147,6 +150,7 @@ class Base(DeclarativeBase):
|
|
|
147
150
|
|
|
148
151
|
|
|
149
152
|
class PricesTable(PlatformTimeIndexMetaData, Base):
|
|
153
|
+
__tablename__ = PRICES_TABLE_NAME
|
|
150
154
|
__metatable_namespace__ = "<domain_namespace>"
|
|
151
155
|
__metatable_identifier__ = "<project_name>.<table_identifier>"
|
|
152
156
|
__metatable_extra_hash_components__ = {"storage_name": "<stable_storage_name>"}
|
|
@@ -378,23 +382,24 @@ changing it changes the dependency graph and update identity.
|
|
|
378
382
|
|
|
379
383
|
Do not construct dependency graphs dynamically inside `update()`.
|
|
380
384
|
|
|
381
|
-
### 8. Foreign Keys Belong To
|
|
385
|
+
### 8. Foreign Keys Belong To SQLAlchemy And Alembic
|
|
382
386
|
|
|
383
387
|
For new code, model foreign keys on the `PlatformTimeIndexMetaData` storage
|
|
384
|
-
class, or route the storage
|
|
385
|
-
|
|
386
|
-
`
|
|
387
|
-
|
|
388
|
-
|
|
388
|
+
class, or route the storage work to the MetaTable skill. When a DataNode storage
|
|
389
|
+
table needs a platform-managed FK, use normal SQLAlchemy `ForeignKey(...)` /
|
|
390
|
+
`ForeignKeyConstraint(...)` metadata on the storage class. Prefer
|
|
391
|
+
project-prefixed SQLAlchemy table names for explicit FK string targets so
|
|
392
|
+
projects sharing one schema do not collide. Generate those names with
|
|
393
|
+
`schema_table_name(project_or_app, concept)` from `mainsequence.meta_tables`.
|
|
389
394
|
|
|
390
|
-
Do not ask users to
|
|
391
|
-
|
|
392
|
-
|
|
395
|
+
Do not ask users to put FK target `MetaTable.uid` values into DataNode config or
|
|
396
|
+
MetaTable registration contracts. Alembic, SQLAlchemy, and the database own FK
|
|
397
|
+
DDL and physical FK constraint names.
|
|
393
398
|
|
|
394
399
|
Registration of the storage class follows the MetaTable migration lifecycle.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
400
|
+
The migration provider reserves the MetaTable rows, Alembic renders/applies FK
|
|
401
|
+
DDL from SQLAlchemy metadata, and catalog finalization refreshes the MetaTable
|
|
402
|
+
binding after upgrade.
|
|
398
403
|
|
|
399
404
|
Do not add DataNode configuration fields just to mutate storage metadata.
|
|
400
405
|
|
|
@@ -404,7 +409,8 @@ Production-quality table identifiers, descriptions, labels, column docs, and
|
|
|
404
409
|
foreign-key metadata belong to the storage class/MetaTable registration path.
|
|
405
410
|
Prefix explicit table identifiers, explicit physical table names, and Alembic
|
|
406
411
|
version table names with the project or package name rather than using bare
|
|
407
|
-
names that can collide across projects.
|
|
412
|
+
names that can collide across projects. Use `schema_table_name(...)` for
|
|
413
|
+
authored SQLAlchemy table names, including DataNode storage tables.
|
|
408
414
|
|
|
409
415
|
Do not put schema or published table metadata on the DataNode configuration.
|
|
410
416
|
|
|
@@ -17,7 +17,7 @@ This skill is for schema-driven application tables registered through TS Manager
|
|
|
17
17
|
- choose `platform_managed` or `external_registered` management mode
|
|
18
18
|
- register platform-managed tables through the model class API
|
|
19
19
|
- build registration requests from resolved SQLAlchemy metadata when inspection is useful
|
|
20
|
-
- define indexes and foreign keys in
|
|
20
|
+
- define indexes and foreign keys in SQLAlchemy metadata for Alembic-owned DDL
|
|
21
21
|
- design governed compiled SQL read and write operations
|
|
22
22
|
- design provider-based Alembic contract evolution for MetaTables
|
|
23
23
|
- run the documented `mainsequence migrations ...` lifecycle for Alembic-backed MetaTable changes
|
|
@@ -122,11 +122,15 @@ Keep the application table model as the authoring source for the neutral table c
|
|
|
122
122
|
|
|
123
123
|
Do not hand-build contract fragments when the SQLAlchemy helper can derive them.
|
|
124
124
|
|
|
125
|
-
### 2. Use
|
|
125
|
+
### 2. Use explicit project-prefixed table names
|
|
126
126
|
|
|
127
127
|
For `platform_managed`, inherit from `PlatformManagedMetaTable`.
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
Declare an explicit project-prefixed SQLAlchemy `__tablename__`. Use
|
|
130
|
+
`schema_table_name(project_or_app, concept)` from `mainsequence.meta_tables` to
|
|
131
|
+
generate that name. The mixin derives only the logical `storage_hash` from
|
|
132
|
+
storage-relevant configuration and table shape; it must not use that hash as the
|
|
133
|
+
SQLAlchemy table name.
|
|
130
134
|
|
|
131
135
|
When a platform-managed table must support in-place contract migrations from its
|
|
132
136
|
first version, use Alembic. Keep the SDK model as a normal
|
|
@@ -155,12 +159,21 @@ or experiment isolation.
|
|
|
155
159
|
Prefix explicit table identifiers, explicit physical table names, and Alembic
|
|
156
160
|
version table names with the project or package name. Bare names such as
|
|
157
161
|
`Account`, `Asset`, or `alembic_version` can collide across projects sharing an
|
|
158
|
-
organization or database schema.
|
|
162
|
+
organization or database schema. Prefer `schema_table_name(...)` over
|
|
163
|
+
hand-built f-strings so project/app prefixes, bounded length, and separators are
|
|
164
|
+
consistent.
|
|
159
165
|
|
|
160
166
|
Register through the class API:
|
|
161
167
|
|
|
162
168
|
```python
|
|
169
|
+
from mainsequence.meta_tables import PlatformManagedMetaTable, schema_table_name
|
|
170
|
+
|
|
171
|
+
PROJECT_NAME = "sdk_examples"
|
|
172
|
+
ACCOUNT_TABLE_NAME = schema_table_name(PROJECT_NAME, "account")
|
|
173
|
+
|
|
174
|
+
|
|
163
175
|
class Account(PlatformManagedMetaTable, Base):
|
|
176
|
+
__tablename__ = ACCOUNT_TABLE_NAME
|
|
164
177
|
__metatable_namespace__ = "sdk-examples"
|
|
165
178
|
__metatable_identifier__ = "sdk_examples.Account"
|
|
166
179
|
__metatable_extra_hash_components__ = {"storage_name": "account"}
|
|
@@ -215,28 +228,25 @@ Do not add generic labels such as `"meta-table"` or `"platform-managed"` to exam
|
|
|
215
228
|
Do not add a `MAINSEQUENCE_META_TABLE_REGISTER` toggle in platform-managed
|
|
216
229
|
examples. Platform-managed examples should be migration-first.
|
|
217
230
|
|
|
218
|
-
### 3.
|
|
219
|
-
|
|
220
|
-
Foreign-key contracts reference the target `MetaTable` UID.
|
|
231
|
+
### 3. Keep foreign keys in SQLAlchemy/Alembic metadata
|
|
221
232
|
|
|
222
|
-
For `PlatformManagedMetaTable`, define foreign keys with
|
|
223
|
-
`
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
uses the target `MetaTable.uid` in the child FK contract.
|
|
233
|
+
For `PlatformManagedMetaTable`, define foreign keys with normal SQLAlchemy
|
|
234
|
+
`ForeignKey(...)` / `ForeignKeyConstraint(...)` metadata. Do not write explicit
|
|
235
|
+
target `MetaTable.uid` maps in the platform-managed path. Migration is the
|
|
236
|
+
lifecycle path: the SDK reserves provider MetaTable rows, Alembic renders and
|
|
237
|
+
applies FK/index DDL from SQLAlchemy metadata, and finalization refreshes the
|
|
238
|
+
catalog after upgrade.
|
|
229
239
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
Prefer project-prefixed SQLAlchemy table names for explicit FK string targets.
|
|
241
|
+
Alembic, SQLAlchemy, and the database own physical FK/index names unless the
|
|
242
|
+
project explicitly names them in SQLAlchemy.
|
|
233
243
|
|
|
234
244
|
Use this pattern:
|
|
235
245
|
|
|
236
246
|
```python
|
|
237
247
|
account_uid: Mapped[uuid.UUID] = mapped_column(
|
|
238
248
|
Uuid,
|
|
239
|
-
|
|
249
|
+
ForeignKey("public.sdk_examples__account.uid", ondelete="RESTRICT"),
|
|
240
250
|
nullable=False,
|
|
241
251
|
)
|
|
242
252
|
```
|
|
@@ -253,21 +263,19 @@ migration = AlembicMetaTableMigration(
|
|
|
253
263
|
)
|
|
254
264
|
```
|
|
255
265
|
|
|
256
|
-
Migration tooling
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
a clear error for recursive registration cycles.
|
|
266
|
+
Migration tooling reserves provider MetaTable rows in the provider-declared
|
|
267
|
+
model order. Include related parent and child models in the same selected
|
|
268
|
+
provider when Alembic must create or evolve their FK DDL.
|
|
260
269
|
|
|
261
|
-
For `external_registered`,
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
For `external_registered`, register each external table contract directly. Do
|
|
271
|
+
not encode FK target MetaTable UIDs in the child registration request; FKs are
|
|
272
|
+
database DDL/introspection metadata, not SDK registration contract fields:
|
|
264
273
|
|
|
265
274
|
```python
|
|
266
275
|
account_meta_table = MetaTable.register(account_request)
|
|
267
276
|
asset_request = external_registered_registration_request_from_sqlalchemy_model(
|
|
268
277
|
Asset,
|
|
269
278
|
data_source_uid=data_source_uid,
|
|
270
|
-
target_meta_tables={Account: account_meta_table},
|
|
271
279
|
)
|
|
272
280
|
asset_meta_table = MetaTable.register(asset_request)
|
|
273
281
|
```
|
|
@@ -292,21 +300,19 @@ For contract evolution, define or update one selected
|
|
|
292
300
|
- set `package`, `migration_namespace`, `script_location`, and `target_metadata`
|
|
293
301
|
- set `alembic_registry` to an `AlembicVersionMetaTable` subclass
|
|
294
302
|
- list the post-apply catalog scope in `metatable_models`
|
|
295
|
-
- generate
|
|
303
|
+
- generate revisions, apply migrations, and refresh catalog bindings
|
|
296
304
|
through `mainsequence migrations ...` commands
|
|
297
305
|
|
|
298
306
|
`alembic_version_meta_table_uid` is the UID of the catalog binding for Alembic's
|
|
299
307
|
version table. It is not the UID of the table being migrated.
|
|
300
308
|
|
|
301
|
-
Application MetaTable catalog sync resolves existing rows by
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
`[project].name` in `pyproject.toml` plus
|
|
305
|
-
`<model.__module__>.<model.__qualname__>`. Pin an explicit identifier when a
|
|
306
|
-
class is renamed or moved but must keep the same platform identity.
|
|
309
|
+
Application MetaTable catalog sync resolves existing rows by the authored
|
|
310
|
+
SQLAlchemy table name used by the provider model. Keep that table name stable
|
|
311
|
+
when a class is renamed or moved but must keep the same platform identity.
|
|
307
312
|
When declaring an explicit identifier, explicit physical table name, or Alembic
|
|
308
313
|
version table name, prefix it with the project or package name rather than using
|
|
309
|
-
a bare table name.
|
|
314
|
+
a bare table name. Use `schema_table_name(project_or_app, concept)` for the
|
|
315
|
+
physical table and Alembic version table names.
|
|
310
316
|
|
|
311
317
|
Do not ask users to construct backend migration payloads, call low-level
|
|
312
318
|
migration request models, or use SDK helper functions directly. The backend
|
|
@@ -359,9 +365,8 @@ When reviewing an existing MetaTable workflow, look for:
|
|
|
359
365
|
- backend-managed examples that use namespace environment variables instead of a plain `sdk-examples` namespace
|
|
360
366
|
- duplicate schema sources outside SQLAlchemy table metadata
|
|
361
367
|
- external tables registered with unstable physical names
|
|
362
|
-
- platform-managed examples that
|
|
363
|
-
|
|
364
|
-
- external child registrations that do not map foreign-key targets to registered parent `MetaTable.uid` values
|
|
368
|
+
- platform-managed examples that try to sequence parent registration through FK metadata
|
|
369
|
+
- external child registrations that try to encode FK target MetaTable UIDs in registration contracts
|
|
365
370
|
- contract changes attempted through normal registration instead of an Alembic migration
|
|
366
371
|
- migration work that asks users to define backend payloads, artifact rows, or SDK request objects
|
|
367
372
|
- compiled SQL operations without complete table scope
|
|
@@ -376,9 +381,9 @@ Do not claim success until you have checked:
|
|
|
376
381
|
- the table has an intention-rich `__metatable_description__`
|
|
377
382
|
- every mapped column has an intention-rich `info.description`
|
|
378
383
|
- indexes are intentional
|
|
379
|
-
- foreign keys
|
|
384
|
+
- foreign keys are present in SQLAlchemy metadata for Alembic when required
|
|
380
385
|
- management mode is correct
|
|
381
|
-
-
|
|
386
|
+
- authored physical names are explicit, project-prefixed SQLAlchemy table names
|
|
382
387
|
- registration returns a `MetaTable.uid`
|
|
383
388
|
- compiled SQL operations declare table scope
|
|
384
389
|
- migrations use Alembic-rendered SQL
|
|
@@ -391,9 +396,8 @@ Do not claim success until you have checked:
|
|
|
391
396
|
For related tables, also check:
|
|
392
397
|
|
|
393
398
|
- aliases are readable
|
|
394
|
-
- platform-managed child
|
|
395
|
-
|
|
396
|
-
- external child registration requests map FK targets to the registered parent UIDs
|
|
399
|
+
- platform-managed child tables and parent tables are included in the selected migration provider
|
|
400
|
+
- FK target strings use stable project-prefixed SQLAlchemy table names where explicit strings are authored
|
|
397
401
|
- query results still match the expected response contract
|
|
398
402
|
|
|
399
403
|
## This Skill Must Stop And Escalate When
|
|
@@ -7038,9 +7038,6 @@ def _meta_table_detail_impl(meta_table_uid: str, timeout: int | None) -> None:
|
|
|
7038
7038
|
("Contract Version", str(meta_table.get("contract_version") or "-")),
|
|
7039
7039
|
("Table Contract", _format_json_value(meta_table.get("table_contract"))),
|
|
7040
7040
|
("Columns", _format_json_value(meta_table.get("columns"))),
|
|
7041
|
-
("Indexes", _format_json_value(meta_table.get("indexes_meta"))),
|
|
7042
|
-
("Foreign Keys", _format_json_value(meta_table.get("foreign_keys"))),
|
|
7043
|
-
("Incoming FKs", _format_json_value(meta_table.get("incoming_fks"))),
|
|
7044
7041
|
("Introspection", _format_json_value(meta_table.get("introspection_snapshot"))),
|
|
7045
7042
|
],
|
|
7046
7043
|
)
|
|
@@ -16,6 +16,7 @@ import typer
|
|
|
16
16
|
from mainsequence.client.metatables import (
|
|
17
17
|
DynamicTableDataSource,
|
|
18
18
|
DynamicTableDataSourceMigrationConnectionRequest,
|
|
19
|
+
TimeIndexMetaData,
|
|
19
20
|
)
|
|
20
21
|
from mainsequence.meta_tables.migrations import (
|
|
21
22
|
AlembicMetaTableMigration,
|
|
@@ -26,7 +27,8 @@ from mainsequence.meta_tables.migrations import (
|
|
|
26
27
|
|
|
27
28
|
migrations = typer.Typer(help="Alembic-owned MetaTable migration commands")
|
|
28
29
|
REGISTER_ENDPOINT = "/orm/api/ts_manager/meta_table/register/"
|
|
29
|
-
|
|
30
|
+
METATABLE_COLLECTION_ENDPOINT = "/orm/api/ts_manager/meta_table/"
|
|
31
|
+
DYNAMIC_TABLE_COLLECTION_ENDPOINT = "/orm/api/ts_manager/dynamic_table/"
|
|
30
32
|
FINALIZE_MANAGED_ENDPOINT = "/orm/api/ts_manager/meta_table/finalize-managed/"
|
|
31
33
|
ALEMBIC_PROVIDER_RESET_ENDPOINT = "/orm/api/ts_manager/meta_table/alembic-provider-reset/"
|
|
32
34
|
|
|
@@ -339,6 +341,20 @@ def _metatable_message(
|
|
|
339
341
|
return " ".join(parts)
|
|
340
342
|
|
|
341
343
|
|
|
344
|
+
def _collection_create_endpoint_for_items(items: Sequence[Any]) -> str:
|
|
345
|
+
if items and all(_item_value(item, "time_index_name") not in (None, "") for item in items):
|
|
346
|
+
return DYNAMIC_TABLE_COLLECTION_ENDPOINT
|
|
347
|
+
return METATABLE_COLLECTION_ENDPOINT
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _collection_create_endpoint_for_item(item: Any) -> str:
|
|
351
|
+
if isinstance(item, TimeIndexMetaData):
|
|
352
|
+
return DYNAMIC_TABLE_COLLECTION_ENDPOINT
|
|
353
|
+
if _item_value(item, "time_index_name") not in (None, ""):
|
|
354
|
+
return DYNAMIC_TABLE_COLLECTION_ENDPOINT
|
|
355
|
+
return METATABLE_COLLECTION_ENDPOINT
|
|
356
|
+
|
|
357
|
+
|
|
342
358
|
def _emit_metatable_registration(model: type[Any], item: Any) -> None:
|
|
343
359
|
_emit_progress(
|
|
344
360
|
_metatable_message(
|
|
@@ -354,6 +370,7 @@ def _emit_metatable_reservation_request(
|
|
|
354
370
|
models: Sequence[type[Any]],
|
|
355
371
|
tables: Sequence[Any],
|
|
356
372
|
) -> None:
|
|
373
|
+
endpoint = _collection_create_endpoint_for_items(tables)
|
|
357
374
|
table_names = []
|
|
358
375
|
for model, table in zip(models, tables, strict=True):
|
|
359
376
|
table_names.append(
|
|
@@ -365,7 +382,7 @@ def _emit_metatable_reservation_request(
|
|
|
365
382
|
)
|
|
366
383
|
)
|
|
367
384
|
_emit_status(
|
|
368
|
-
f"Sending POST {
|
|
385
|
+
f"Sending POST {endpoint} collection-create request for {len(tables)} "
|
|
369
386
|
f"MetaTables table_names={','.join(table_names)}"
|
|
370
387
|
)
|
|
371
388
|
|
|
@@ -373,7 +390,7 @@ def _emit_metatable_reservation_request(
|
|
|
373
390
|
def _emit_metatable_reservation(model: type[Any], item: Any) -> None:
|
|
374
391
|
_emit_progress(
|
|
375
392
|
_metatable_message(
|
|
376
|
-
endpoint=
|
|
393
|
+
endpoint=_collection_create_endpoint_for_item(item),
|
|
377
394
|
action="reserved",
|
|
378
395
|
model=model,
|
|
379
396
|
item=item,
|
|
@@ -400,8 +417,6 @@ def _prepare_alembic_config(
|
|
|
400
417
|
timeout: float | None,
|
|
401
418
|
ttl_seconds: int,
|
|
402
419
|
alembic_output: _AlembicOutput,
|
|
403
|
-
stage_existing_schema_management: bool = True,
|
|
404
|
-
require_existing_contract_match: bool = True,
|
|
405
420
|
prepare_provider_metatables: bool = True,
|
|
406
421
|
) -> tuple[Any, Any]:
|
|
407
422
|
_emit_status("Ensuring Alembic registry MetaTable...")
|
|
@@ -414,8 +429,6 @@ def _prepare_alembic_config(
|
|
|
414
429
|
_emit_status("Preparing platform-managed MetaTable reservations...")
|
|
415
430
|
prepared = migration.prepare_for_alembic(
|
|
416
431
|
timeout=timeout,
|
|
417
|
-
stage_existing_schema_management=stage_existing_schema_management,
|
|
418
|
-
require_existing_contract_match=require_existing_contract_match,
|
|
419
432
|
on_metatable_reservation_request=_emit_metatable_reservation_request,
|
|
420
433
|
on_metatable_reservation_status=_emit_status,
|
|
421
434
|
on_metatable_reserved=_emit_metatable_reservation,
|
|
@@ -435,20 +448,16 @@ def _prepare_alembic_config(
|
|
|
435
448
|
)
|
|
436
449
|
_emit_status(f"Loading DynamicTableDataSource uid={prepared.data_source_uid}...")
|
|
437
450
|
data_source = DynamicTableDataSource.get_by_uid(prepared.data_source_uid)
|
|
438
|
-
_emit_status(
|
|
439
|
-
"Requesting scoped migration connection "
|
|
440
|
-
f"meta_table_count={len(prepared.meta_table_uids)} ttl_seconds={ttl_seconds}..."
|
|
441
|
-
)
|
|
451
|
+
_emit_status(f"Requesting migration connection ttl_seconds={ttl_seconds}...")
|
|
442
452
|
connection = data_source.issue_migration_connection(
|
|
443
453
|
DynamicTableDataSourceMigrationConnectionRequest(
|
|
444
454
|
package=migration.package,
|
|
445
455
|
migration_namespace=migration.migration_namespace,
|
|
446
|
-
meta_table_uids=prepared.meta_table_uids,
|
|
447
456
|
ttl_seconds=ttl_seconds,
|
|
448
457
|
),
|
|
449
458
|
timeout=timeout,
|
|
450
459
|
)
|
|
451
|
-
_emit_status("
|
|
460
|
+
_emit_status("Migration connection acquired.")
|
|
452
461
|
_emit_status("Building Alembic config...")
|
|
453
462
|
config = alembic_config_for_provider(
|
|
454
463
|
migration,
|
|
@@ -461,6 +470,44 @@ def _prepare_alembic_config(
|
|
|
461
470
|
return prepared, config
|
|
462
471
|
|
|
463
472
|
|
|
473
|
+
def _build_revision_alembic_config(
|
|
474
|
+
migration: AlembicMetaTableMigration,
|
|
475
|
+
*,
|
|
476
|
+
sqlalchemy_url: str | None,
|
|
477
|
+
requires_database: bool,
|
|
478
|
+
timeout: float | None,
|
|
479
|
+
ttl_seconds: int,
|
|
480
|
+
alembic_output: _AlembicOutput,
|
|
481
|
+
) -> Any:
|
|
482
|
+
_emit_status("Building local Alembic config for revision...")
|
|
483
|
+
owner_role_name = None
|
|
484
|
+
if requires_database and sqlalchemy_url in (None, ""):
|
|
485
|
+
data_source_uid = migration._resolve_provider_data_source_uid()
|
|
486
|
+
_emit_status(f"Loading DynamicTableDataSource uid={data_source_uid} for revision...")
|
|
487
|
+
data_source = DynamicTableDataSource.get_by_uid(data_source_uid)
|
|
488
|
+
_emit_status(f"Requesting revision migration connection ttl_seconds={ttl_seconds}...")
|
|
489
|
+
connection = data_source.issue_migration_connection(
|
|
490
|
+
DynamicTableDataSourceMigrationConnectionRequest(
|
|
491
|
+
package=migration.package,
|
|
492
|
+
migration_namespace=migration.migration_namespace,
|
|
493
|
+
ttl_seconds=ttl_seconds,
|
|
494
|
+
),
|
|
495
|
+
timeout=timeout,
|
|
496
|
+
)
|
|
497
|
+
_emit_status("Revision migration connection acquired.")
|
|
498
|
+
sqlalchemy_url = connection.uri
|
|
499
|
+
owner_role_name = connection.owner_role_name
|
|
500
|
+
config = alembic_config_for_provider(
|
|
501
|
+
migration,
|
|
502
|
+
sqlalchemy_url=sqlalchemy_url or "postgresql://",
|
|
503
|
+
owner_role_name=owner_role_name,
|
|
504
|
+
stdout=alembic_output,
|
|
505
|
+
output_buffer=alembic_output,
|
|
506
|
+
)
|
|
507
|
+
_emit_status("Local Alembic config built.")
|
|
508
|
+
return config
|
|
509
|
+
|
|
510
|
+
|
|
464
511
|
def _prepared_physical_table_refs(prepared: Any) -> list[tuple[str | None, str]]:
|
|
465
512
|
refs: list[tuple[str | None, str]] = []
|
|
466
513
|
for item in getattr(prepared, "reserved_tables", []) or []:
|
|
@@ -544,11 +591,11 @@ def _assert_autogenerate_baseline_visible(prepared: Any, config: Any) -> None:
|
|
|
544
591
|
sample = ",".join(_format_table_ref(ref) for ref in table_refs[:5])
|
|
545
592
|
raise RuntimeError(
|
|
546
593
|
"Refusing to autogenerate a migration because this provider already has "
|
|
547
|
-
f"Alembic head(s) {','.join(script_heads)}, but the
|
|
594
|
+
f"Alembic head(s) {','.join(script_heads)}, but the migration "
|
|
548
595
|
"connection cannot see any provider physical tables. Alembic would emit a "
|
|
549
596
|
"duplicate initial create-all migration instead of a schema diff. Apply the "
|
|
550
597
|
"existing baseline with `mainsequence migrations upgrade ... head`, or fix "
|
|
551
|
-
"the
|
|
598
|
+
"the migration connection/table visibility before running revision "
|
|
552
599
|
f"again. checked_sample={sample}"
|
|
553
600
|
)
|
|
554
601
|
|
|
@@ -626,8 +673,6 @@ def current(
|
|
|
626
673
|
timeout=timeout,
|
|
627
674
|
ttl_seconds=ttl_seconds,
|
|
628
675
|
alembic_output=alembic_output,
|
|
629
|
-
stage_existing_schema_management=False,
|
|
630
|
-
require_existing_contract_match=False,
|
|
631
676
|
prepare_provider_metatables=False,
|
|
632
677
|
)
|
|
633
678
|
_emit_alembic_script_context(config)
|
|
@@ -653,7 +698,7 @@ def revision(
|
|
|
653
698
|
autogenerate: bool = typer.Option(
|
|
654
699
|
True,
|
|
655
700
|
"--autogenerate/--no-autogenerate",
|
|
656
|
-
help="Use Alembic autogenerate against the
|
|
701
|
+
help="Use Alembic autogenerate against the local database URL and provider metadata.",
|
|
657
702
|
),
|
|
658
703
|
provider: str | None = typer.Option(
|
|
659
704
|
None,
|
|
@@ -662,8 +707,22 @@ def revision(
|
|
|
662
707
|
),
|
|
663
708
|
rev_id: str | None = typer.Option(None, "--rev-id", help="Explicit Alembic revision id."),
|
|
664
709
|
head: str = typer.Option("head", "--head", help="Alembic head to base the revision on."),
|
|
665
|
-
timeout: float | None = typer.Option(
|
|
666
|
-
|
|
710
|
+
timeout: float | None = typer.Option(
|
|
711
|
+
None,
|
|
712
|
+
"--timeout",
|
|
713
|
+
help="Request timeout for the revision migration connection when --sqlalchemy-url is omitted.",
|
|
714
|
+
),
|
|
715
|
+
ttl_seconds: int = typer.Option(
|
|
716
|
+
900,
|
|
717
|
+
"--ttl-seconds",
|
|
718
|
+
min=1,
|
|
719
|
+
help="TTL for the revision migration connection when --sqlalchemy-url is omitted.",
|
|
720
|
+
),
|
|
721
|
+
sqlalchemy_url: str | None = typer.Option(
|
|
722
|
+
None,
|
|
723
|
+
"--sqlalchemy-url",
|
|
724
|
+
help="Local database URL for Alembic autogenerate. Required when --autogenerate is used.",
|
|
725
|
+
),
|
|
667
726
|
json_output: bool = typer.Option(False, "--json", help="Emit JSON."),
|
|
668
727
|
) -> None:
|
|
669
728
|
"""Create a normal Alembic revision for the selected provider."""
|
|
@@ -676,16 +735,15 @@ def revision(
|
|
|
676
735
|
migration,
|
|
677
736
|
alembic_output=alembic_output,
|
|
678
737
|
)
|
|
679
|
-
|
|
738
|
+
config = _build_revision_alembic_config(
|
|
680
739
|
migration,
|
|
740
|
+
sqlalchemy_url=sqlalchemy_url,
|
|
741
|
+
requires_database=autogenerate,
|
|
681
742
|
timeout=timeout,
|
|
682
743
|
ttl_seconds=ttl_seconds,
|
|
683
744
|
alembic_output=alembic_output,
|
|
684
|
-
stage_existing_schema_management=False,
|
|
685
745
|
)
|
|
686
746
|
_emit_alembic_script_context(config, target_revision=head)
|
|
687
|
-
if autogenerate:
|
|
688
|
-
_assert_autogenerate_baseline_visible(prepared, config)
|
|
689
747
|
_emit_status(f"Starting Alembic revision now rev_id={resolved_rev_id}...")
|
|
690
748
|
with _forward_alembic_logging():
|
|
691
749
|
script = command.revision(
|
|
@@ -702,7 +760,6 @@ def revision(
|
|
|
702
760
|
"path": getattr(script, "path", None),
|
|
703
761
|
"package": migration.package,
|
|
704
762
|
"migration_namespace": migration.migration_namespace,
|
|
705
|
-
"meta_table_uids": prepared.meta_table_uids,
|
|
706
763
|
},
|
|
707
764
|
json_output=json_output,
|
|
708
765
|
)
|