mainsequence 4.2.38__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.38/mainsequence.egg-info → mainsequence-4.2.39}/PKG-INFO +1 -1
  2. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +6 -0
  3. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/sqlalchemy_contracts.py +73 -1
  4. {mainsequence-4.2.38 → mainsequence-4.2.39/mainsequence.egg-info}/PKG-INFO +1 -1
  5. {mainsequence-4.2.38 → mainsequence-4.2.39}/pyproject.toml +1 -1
  6. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_meta_tables_sqlalchemy_contracts.py +81 -0
  7. {mainsequence-4.2.38 → mainsequence-4.2.39}/LICENSE +0 -0
  8. {mainsequence-4.2.38 → mainsequence-4.2.39}/README.md +0 -0
  9. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/AGENTS.md +0 -0
  10. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  11. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  12. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  13. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  14. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  15. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  16. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  17. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  18. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  19. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  20. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  21. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  22. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  23. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  24. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  25. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  26. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  27. {mainsequence-4.2.38 → mainsequence-4.2.39}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  28. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/__init__.py +0 -0
  29. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/__main__.py +0 -0
  30. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/bootstrap.py +0 -0
  31. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/__init__.py +0 -0
  32. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/api.py +0 -0
  33. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/browser_auth.py +0 -0
  34. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/cli.py +0 -0
  35. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/config.py +0 -0
  36. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/docker_utils.py +0 -0
  37. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/doctor.py +0 -0
  38. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/local_ops.py +0 -0
  39. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/migrations.py +0 -0
  40. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/model_filters.py +0 -0
  41. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/project_status.py +0 -0
  42. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/pydantic_cli.py +0 -0
  43. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/sdk_utils.py +0 -0
  44. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/ssh_utils.py +0 -0
  45. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/cli/ui.py +0 -0
  46. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/__init__.py +0 -0
  47. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/agent_runtime_models.py +0 -0
  48. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/base.py +0 -0
  49. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/client.py +0 -0
  50. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/command_center/__init__.py +0 -0
  51. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/command_center/app_component.py +0 -0
  52. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/command_center/connections.py +0 -0
  53. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/command_center/data_models.py +0 -0
  54. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/command_center/workspace.py +0 -0
  55. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  56. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/compute_validation.py +0 -0
  57. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  58. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  59. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  60. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  61. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/dtype_codec.py +0 -0
  62. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/exceptions.py +0 -0
  63. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/fastapi/__init__.py +0 -0
  64. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/fastapi/auth.py +0 -0
  65. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/metatables/__init__.py +0 -0
  66. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/metatables/core.py +0 -0
  67. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/models_foundry.py +0 -0
  68. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/models_helpers.py +0 -0
  69. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/models_user.py +0 -0
  70. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/client/utils.py +0 -0
  71. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/defaults.py +0 -0
  72. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/instrumentation/__init__.py +0 -0
  73. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/instrumentation/utils.py +0 -0
  74. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/logconf.py +0 -0
  75. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/__init__.py +0 -0
  76. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/__main__.py +0 -0
  77. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  78. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  79. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  80. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  81. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  82. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  83. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  84. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  85. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  86. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  87. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/future_registry.py +0 -0
  88. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/hashing.py +0 -0
  89. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/migrations.py +0 -0
  90. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  91. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/meta_tables/schema_names.py +0 -0
  92. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence/runtime_flags.py +0 -0
  93. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence.egg-info/SOURCES.txt +0 -0
  94. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence.egg-info/dependency_links.txt +0 -0
  95. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence.egg-info/entry_points.txt +0 -0
  96. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence.egg-info/requires.txt +0 -0
  97. {mainsequence-4.2.38 → mainsequence-4.2.39}/mainsequence.egg-info/top_level.txt +0 -0
  98. {mainsequence-4.2.38 → mainsequence-4.2.39}/setup.cfg +0 -0
  99. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_auth_precedence.py +0 -0
  100. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_build_operations_hashing.py +0 -0
  101. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_cli.py +0 -0
  102. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_cli_browser_auth.py +0 -0
  103. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_cli_migrations.py +0 -0
  104. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_client.py +0 -0
  105. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_command_center_app_component_models.py +0 -0
  106. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_command_center_data_models.py +0 -0
  107. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_command_center_models.py +0 -0
  108. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  109. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_data_node_storage_dimension_queries.py +0 -0
  110. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_data_node_update_flow.py +0 -0
  111. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_dependency_extras.py +0 -0
  112. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_duckdb_interface_dimensions.py +0 -0
  113. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_filter_normalization.py +0 -0
  114. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_logconf.py +0 -0
  115. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_meta_table_migrations.py +0 -0
  116. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_meta_tables_client_models.py +0 -0
  117. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_models_user_request_bound_auth.py +0 -0
  118. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_pod_project_resolution.py +0 -0
  119. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_project_batch_jobs_from_file.py +0 -0
  120. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_run_configuration.py +0 -0
  121. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_schema_names.py +0 -0
  122. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_secret_client_model.py +0 -0
  123. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_source_table_configuration.py +0 -0
  124. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_sqlite_interface_dimensions.py +0 -0
  125. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_update_runner_uid_runtime.py +0 -0
  126. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_update_statistics.py +0 -0
  127. {mainsequence-4.2.38 → mainsequence-4.2.39}/tests/test_update_uid_guards.py +0 -0
  128. {mainsequence-4.2.38 → 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.38
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.
@@ -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.38
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.38"
7
+ version = "4.2.39"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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