mainsequence 4.2.37__tar.gz → 4.2.39__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.
Files changed (128) hide show
  1. {mainsequence-4.2.37/mainsequence.egg-info → mainsequence-4.2.39}/PKG-INFO +1 -1
  2. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +6 -0
  3. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +6 -0
  4. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/migrations.py +16 -4
  5. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/sqlalchemy_contracts.py +73 -1
  6. {mainsequence-4.2.37 → mainsequence-4.2.39/mainsequence.egg-info}/PKG-INFO +1 -1
  7. {mainsequence-4.2.37 → mainsequence-4.2.39}/pyproject.toml +1 -1
  8. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_meta_table_migrations.py +13 -1
  9. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_meta_tables_sqlalchemy_contracts.py +81 -0
  10. {mainsequence-4.2.37 → mainsequence-4.2.39}/LICENSE +0 -0
  11. {mainsequence-4.2.37 → mainsequence-4.2.39}/README.md +0 -0
  12. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/AGENTS.md +0 -0
  13. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  14. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  15. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  16. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  17. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  18. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  19. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  20. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  21. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  22. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  23. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  24. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  25. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  26. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  27. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  28. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  29. {mainsequence-4.2.37 → mainsequence-4.2.39}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  30. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/__init__.py +0 -0
  31. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/__main__.py +0 -0
  32. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/bootstrap.py +0 -0
  33. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/__init__.py +0 -0
  34. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/api.py +0 -0
  35. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/browser_auth.py +0 -0
  36. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/cli.py +0 -0
  37. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/config.py +0 -0
  38. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/docker_utils.py +0 -0
  39. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/doctor.py +0 -0
  40. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/local_ops.py +0 -0
  41. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/migrations.py +0 -0
  42. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/model_filters.py +0 -0
  43. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/project_status.py +0 -0
  44. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/pydantic_cli.py +0 -0
  45. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/sdk_utils.py +0 -0
  46. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/ssh_utils.py +0 -0
  47. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/cli/ui.py +0 -0
  48. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/__init__.py +0 -0
  49. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/agent_runtime_models.py +0 -0
  50. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/base.py +0 -0
  51. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/client.py +0 -0
  52. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/command_center/__init__.py +0 -0
  53. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/command_center/app_component.py +0 -0
  54. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/command_center/connections.py +0 -0
  55. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/command_center/data_models.py +0 -0
  56. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/command_center/workspace.py +0 -0
  57. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  58. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/compute_validation.py +0 -0
  59. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  60. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  61. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  62. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  63. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/dtype_codec.py +0 -0
  64. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/exceptions.py +0 -0
  65. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/fastapi/__init__.py +0 -0
  66. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/fastapi/auth.py +0 -0
  67. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/metatables/__init__.py +0 -0
  68. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/metatables/core.py +0 -0
  69. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/models_foundry.py +0 -0
  70. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/models_helpers.py +0 -0
  71. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/models_user.py +0 -0
  72. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/client/utils.py +0 -0
  73. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/defaults.py +0 -0
  74. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/instrumentation/__init__.py +0 -0
  75. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/instrumentation/utils.py +0 -0
  76. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/logconf.py +0 -0
  77. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/__init__.py +0 -0
  78. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/__main__.py +0 -0
  79. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  80. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  81. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  82. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  83. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  84. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  85. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  86. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  87. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  88. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  89. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/future_registry.py +0 -0
  90. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/hashing.py +0 -0
  91. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  92. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/meta_tables/schema_names.py +0 -0
  93. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence/runtime_flags.py +0 -0
  94. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence.egg-info/SOURCES.txt +0 -0
  95. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence.egg-info/dependency_links.txt +0 -0
  96. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence.egg-info/entry_points.txt +0 -0
  97. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence.egg-info/requires.txt +0 -0
  98. {mainsequence-4.2.37 → mainsequence-4.2.39}/mainsequence.egg-info/top_level.txt +0 -0
  99. {mainsequence-4.2.37 → mainsequence-4.2.39}/setup.cfg +0 -0
  100. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_auth_precedence.py +0 -0
  101. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_build_operations_hashing.py +0 -0
  102. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_cli.py +0 -0
  103. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_cli_browser_auth.py +0 -0
  104. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_cli_migrations.py +0 -0
  105. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_client.py +0 -0
  106. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_command_center_app_component_models.py +0 -0
  107. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_command_center_data_models.py +0 -0
  108. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_command_center_models.py +0 -0
  109. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  110. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_data_node_storage_dimension_queries.py +0 -0
  111. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_data_node_update_flow.py +0 -0
  112. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_dependency_extras.py +0 -0
  113. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_duckdb_interface_dimensions.py +0 -0
  114. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_filter_normalization.py +0 -0
  115. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_logconf.py +0 -0
  116. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_meta_tables_client_models.py +0 -0
  117. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_models_user_request_bound_auth.py +0 -0
  118. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_pod_project_resolution.py +0 -0
  119. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_project_batch_jobs_from_file.py +0 -0
  120. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_run_configuration.py +0 -0
  121. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_schema_names.py +0 -0
  122. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_secret_client_model.py +0 -0
  123. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_source_table_configuration.py +0 -0
  124. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_sqlite_interface_dimensions.py +0 -0
  125. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_update_runner_uid_runtime.py +0 -0
  126. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_update_statistics.py +0 -0
  127. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_update_uid_guards.py +0 -0
  128. {mainsequence-4.2.37 → mainsequence-4.2.39}/tests/test_workspace_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.2.37
3
+ Version: 4.2.39
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -191,6 +191,12 @@ MetaTable migration provider and run `mainsequence migrations upgrade --provider
191
191
  ... head`. Do not call `PricesTable.register()` directly and do not rely on
192
192
  DataNode construction to register storage tables.
193
193
 
194
+ `__index_names__` is the full DataNode storage grain. `PlatformTimeIndexMetaData`
195
+ automatically adds a SQLAlchemy unique index over that tuple before Alembic
196
+ autogenerate runs. Do not manually repeat the full grain unique index in
197
+ `__table_args__`; add ordinary SQLAlchemy `Index(...)` entries only for
198
+ additional lookup/performance paths.
199
+
194
200
  `PlatformTimeIndexMetaData.register()` remains SDK plumbing for the migration
195
201
  workflow. Do not manually attach an existing UID, reconstruct a generic
196
202
  `MetaTable`, or use manual bind helpers as an authoring step.
@@ -292,6 +292,12 @@ SQLAlchemy class and calling normal registration again. Shape-addressed
292
292
  foreign keys, or constraints change, so new code cannot reliably recover the
293
293
  previous shape-derived table.
294
294
 
295
+ Do not modify Alembic revision files that have already been implemented/applied.
296
+ MetaTable migrations are database-backed history: once a revision may exist in a
297
+ database `alembic_version` table, changing that file corrupts the relationship
298
+ between source history and deployed state. For any follow-up schema change,
299
+ create a new Alembic revision on top of the current head.
300
+
295
301
  For contract evolution, define or update one selected
296
302
  `AlembicMetaTableMigration` provider:
297
303
 
@@ -534,9 +534,15 @@ class AlembicMetaTableMigration:
534
534
  table_name = target_table_names[model]
535
535
  existing_meta_table = existing_by_table_name.get(table_name)
536
536
  if existing_meta_table is not None:
537
- _bind_model_to_existing_metatable(model, existing_meta_table)
538
- reserved_by_model[model] = existing_meta_table
539
- continue
537
+ if _meta_table_provisioning_status(existing_meta_table) == "active":
538
+ _bind_model_to_existing_metatable(model, existing_meta_table)
539
+ reserved_by_model[model] = existing_meta_table
540
+ continue
541
+ if on_metatable_reservation_status is not None:
542
+ on_metatable_reservation_status(
543
+ "Restaging existing reserved MetaTable "
544
+ f"table_name={table_name} with current provider contract."
545
+ )
540
546
  else:
541
547
  bound_meta_table = _bound_meta_table_for_model(model)
542
548
  if bound_meta_table is not None:
@@ -1055,6 +1061,13 @@ def _meta_table_physical_table_name(meta_table: MetaTable) -> str | None:
1055
1061
  return str(physical_table_name)
1056
1062
 
1057
1063
 
1064
+ def _meta_table_provisioning_status(meta_table: MetaTable) -> str | None:
1065
+ provisioning_status = meta_table.provisioning_status
1066
+ if provisioning_status in (None, ""):
1067
+ return None
1068
+ return str(provisioning_status)
1069
+
1070
+
1058
1071
  def _meta_table_uid(meta_table: MetaTable) -> str | None:
1059
1072
  uid = meta_table.uid
1060
1073
  if uid in (None, ""):
@@ -1066,7 +1079,6 @@ def _finalize_table_failed(item: ManagedMetaTableFinalizeTableResult) -> bool:
1066
1079
  return (
1067
1080
  item.provisioning_status != "active"
1068
1081
  or item.physical_table_exists is False
1069
- or item.finalized is False
1070
1082
  or item.error not in (None, "", {})
1071
1083
  )
1072
1084
 
@@ -28,6 +28,7 @@ from mainsequence.client.metatables import (
28
28
  )
29
29
 
30
30
  from .hashing import build_meta_table_configured_storage_hash
31
+ from .schema_names import schema_index_name
31
32
 
32
33
  DEFAULT_PLATFORM_MANAGED_PROVISIONING = {
33
34
  "create_table": True,
@@ -322,7 +323,9 @@ class PlatformTimeIndexMetaData(PlatformManagedMetaTable):
322
323
 
323
324
  from sqlalchemy import Table
324
325
 
325
- return Table(str(name), metadata, *table_items, **kwargs)
326
+ table = Table(str(name), metadata, *table_items, **kwargs)
327
+ _ensure_time_index_unique_grain_index(table=table, index_names=index_names)
328
+ return table
326
329
 
327
330
  @classmethod
328
331
  def build_registration_request(
@@ -1391,6 +1394,75 @@ def _validate_time_index_contract(
1391
1394
  )
1392
1395
 
1393
1396
 
1397
+ def _ensure_time_index_unique_grain_index(
1398
+ *,
1399
+ table: Any,
1400
+ index_names: Sequence[str],
1401
+ ) -> None:
1402
+ grain_names = [str(name) for name in index_names]
1403
+ if _has_unique_grain_enforcement(table=table, index_names=grain_names):
1404
+ return
1405
+
1406
+ from sqlalchemy import Index
1407
+
1408
+ Index(
1409
+ schema_index_name(_table_name(table), grain_names, unique=True),
1410
+ *(table.c[name] for name in grain_names),
1411
+ unique=True,
1412
+ )
1413
+
1414
+
1415
+ def _has_unique_grain_enforcement(
1416
+ *,
1417
+ table: Any,
1418
+ index_names: Sequence[str],
1419
+ ) -> bool:
1420
+ for index in _iter_indexes(table):
1421
+ if bool(getattr(index, "unique", False)) and _grain_columns_match(
1422
+ _index_column_names(index),
1423
+ index_names,
1424
+ ):
1425
+ return True
1426
+
1427
+ for constraint in _iter_unique_constraints(table):
1428
+ if _grain_columns_match(_constraint_column_names(constraint), index_names):
1429
+ return True
1430
+
1431
+ return False
1432
+
1433
+
1434
+ def _grain_columns_match(existing_names: Sequence[str], index_names: Sequence[str]) -> bool:
1435
+ existing = [str(name) for name in existing_names]
1436
+ expected = [str(name) for name in index_names]
1437
+ if len(existing) != len(expected):
1438
+ return False
1439
+ return set(existing) == set(expected)
1440
+
1441
+
1442
+ def _index_column_names(index: Any) -> list[str]:
1443
+ return [_storage_item_name(item) for item in _iter_index_items(index)]
1444
+
1445
+
1446
+ def _iter_unique_constraints(table: Any) -> list[Any]:
1447
+ from sqlalchemy import UniqueConstraint
1448
+
1449
+ constraints = getattr(table, "constraints", None)
1450
+ if constraints is None:
1451
+ return []
1452
+ return [
1453
+ constraint
1454
+ for constraint in constraints
1455
+ if isinstance(constraint, UniqueConstraint)
1456
+ ]
1457
+
1458
+
1459
+ def _constraint_column_names(constraint: Any) -> list[str]:
1460
+ columns = getattr(constraint, "columns", None)
1461
+ if columns is None:
1462
+ return []
1463
+ return [_storage_item_name(column) for column in columns]
1464
+
1465
+
1394
1466
  def _storage_identity_from_parts(
1395
1467
  *,
1396
1468
  columns: Sequence[Any],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.2.37
3
+ Version: 4.2.39
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "4.2.37"
7
+ version = "4.2.39"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1069,6 +1069,7 @@ def test_prepare_for_alembic_reserves_existing_table_name_with_provider_identity
1069
1069
  def fake_bulk_create(rows, *, timeout=None, on_status=None):
1070
1070
  reserved_payloads.extend(rows)
1071
1071
  assert [table["identifier"] for table in rows] == [
1072
+ "example_assets__account",
1072
1073
  "example_assets__asset",
1073
1074
  ]
1074
1075
  assert all(table["migration_package"] == "sample" for table in rows)
@@ -1079,11 +1080,17 @@ def test_prepare_for_alembic_reserves_existing_table_name_with_provider_identity
1079
1080
  assert not hasattr(rows[0]["table_contract"], "foreign_keys")
1080
1081
  assert all("schema_management" not in table for table in rows)
1081
1082
  return [
1083
+ _reserved_metatable(
1084
+ uid="aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
1085
+ identifier="example_assets__account",
1086
+ physical_table_name="example_assets__account",
1087
+ storage_hash=rows[0]["storage_hash"],
1088
+ ),
1082
1089
  _reserved_metatable(
1083
1090
  uid="bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1084
1091
  identifier="example_assets__asset",
1085
1092
  physical_table_name="example_assets__asset",
1086
- storage_hash=rows[0]["storage_hash"],
1093
+ storage_hash=rows[1]["storage_hash"],
1087
1094
  ),
1088
1095
  ]
1089
1096
 
@@ -1106,6 +1113,7 @@ def test_prepare_for_alembic_reserves_existing_table_name_with_provider_identity
1106
1113
 
1107
1114
  assert len(filter_calls) == 1
1108
1115
  assert [payload["identifier"] for payload in reserved_payloads] == [
1116
+ "example_assets__account",
1109
1117
  "example_assets__asset",
1110
1118
  ]
1111
1119
  assert "schema_management" not in reserved_payloads[0]
@@ -1115,6 +1123,10 @@ def test_prepare_for_alembic_reserves_existing_table_name_with_provider_identity
1115
1123
  ]
1116
1124
  assert Account.__table__.name == "example_assets__account"
1117
1125
  assert Asset.__table__.name == "example_assets__asset"
1126
+ assert (
1127
+ "Restaging existing reserved MetaTable table_name=example_assets__account "
1128
+ "with current provider contract."
1129
+ ) in reservation_statuses
1118
1130
 
1119
1131
 
1120
1132
  def test_prepare_for_alembic_reserves_already_staged_existing_rows(monkeypatch):
@@ -22,6 +22,8 @@ from mainsequence.meta_tables import (
22
22
  external_registered_registration_request_from_sqlalchemy_model,
23
23
  platform_managed_migration_registration_context,
24
24
  platform_managed_registration_request_from_sqlalchemy_model,
25
+ schema_index_name,
26
+ sqlalchemy_naming_convention,
25
27
  table_contract_from_sqlalchemy_model,
26
28
  time_indexed_registration_request_from_sqlalchemy_model,
27
29
  )
@@ -1535,6 +1537,85 @@ def test_platform_managed_metatable_preserves_authored_tablename_with_sqlalchemy
1535
1537
  assert not hasattr(request.table_contract, "foreign_keys")
1536
1538
 
1537
1539
 
1540
+ def test_time_index_metadata_generates_unique_grain_index_with_schema_name():
1541
+ pytest.importorskip("sqlalchemy")
1542
+
1543
+ from sqlalchemy import DateTime, Float, MetaData, String
1544
+ from sqlalchemy.orm import DeclarativeBase, mapped_column
1545
+
1546
+ class Base(DeclarativeBase):
1547
+ metadata = MetaData(naming_convention=sqlalchemy_naming_convention())
1548
+
1549
+ class Prices(PlatformTimeIndexMetaData, Base):
1550
+ __tablename__ = "ms_markets__prices__mainsequence_examples"
1551
+ __metatable_namespace__ = "mainsequence.examples"
1552
+ __time_index_name__ = "time_index"
1553
+ __index_names__ = ["time_index", "asset_identifier"]
1554
+
1555
+ time_index: Mapped[datetime.datetime] = mapped_column(
1556
+ DateTime(timezone=True),
1557
+ nullable=False,
1558
+ )
1559
+ asset_identifier: Mapped[str] = mapped_column(String(255), nullable=False)
1560
+ close: Mapped[float] = mapped_column(Float, nullable=True)
1561
+
1562
+ grain_indexes = [
1563
+ index
1564
+ for index in Prices.__table__.indexes
1565
+ if index.unique
1566
+ and [column.name for column in index.columns] == ["time_index", "asset_identifier"]
1567
+ ]
1568
+
1569
+ assert len(grain_indexes) == 1
1570
+ assert grain_indexes[0].name == schema_index_name(
1571
+ "ms_markets__prices__mainsequence_examples",
1572
+ ["time_index", "asset_identifier"],
1573
+ unique=True,
1574
+ )
1575
+
1576
+
1577
+ def test_time_index_metadata_reuses_existing_unique_grain_constraint():
1578
+ pytest.importorskip("sqlalchemy")
1579
+
1580
+ from sqlalchemy import DateTime, Float, MetaData, String, UniqueConstraint
1581
+ from sqlalchemy.orm import DeclarativeBase, mapped_column
1582
+
1583
+ class Base(DeclarativeBase):
1584
+ metadata = MetaData(naming_convention=sqlalchemy_naming_convention())
1585
+
1586
+ class Prices(PlatformTimeIndexMetaData, Base):
1587
+ __tablename__ = "ms_markets__prices__mainsequence_examples"
1588
+ __table_args__ = (
1589
+ UniqueConstraint(
1590
+ "asset_identifier",
1591
+ "time_index",
1592
+ name="uix_custom_asset_time",
1593
+ ),
1594
+ )
1595
+ __metatable_namespace__ = "mainsequence.examples"
1596
+ __time_index_name__ = "time_index"
1597
+ __index_names__ = ["time_index", "asset_identifier"]
1598
+
1599
+ time_index: Mapped[datetime.datetime] = mapped_column(
1600
+ DateTime(timezone=True),
1601
+ nullable=False,
1602
+ )
1603
+ asset_identifier: Mapped[str] = mapped_column(String(255), nullable=False)
1604
+ close: Mapped[float] = mapped_column(Float, nullable=True)
1605
+
1606
+ assert not Prices.__table__.indexes
1607
+ unique_constraints = [
1608
+ constraint
1609
+ for constraint in Prices.__table__.constraints
1610
+ if isinstance(constraint, UniqueConstraint)
1611
+ ]
1612
+ assert len(unique_constraints) == 1
1613
+ assert [column.name for column in unique_constraints[0].columns] == [
1614
+ "asset_identifier",
1615
+ "time_index",
1616
+ ]
1617
+
1618
+
1538
1619
  def test_platform_managed_register_preserves_authored_sqlalchemy_table_name(
1539
1620
  monkeypatch,
1540
1621
  ):
File without changes
File without changes
File without changes