mainsequence 4.4.25__tar.gz → 4.4.26__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 (161) hide show
  1. {mainsequence-4.4.25 → mainsequence-4.4.26}/PKG-INFO +1 -1
  2. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/migrations.py +6 -0
  3. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/metatables/core.py +105 -3
  4. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/data_nodes.py +59 -3
  5. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/persist_managers.py +96 -8
  6. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/__init__.py +182 -66
  7. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/sqlalchemy_contracts.py +60 -0
  8. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence.egg-info/PKG-INFO +1 -1
  9. {mainsequence-4.4.25 → mainsequence-4.4.26}/pyproject.toml +1 -1
  10. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_data_access_mixin_dimension_audit.py +3 -0
  11. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_meta_table_migrations.py +37 -11
  12. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_meta_tables_client_models.py +40 -0
  13. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_meta_tables_sqlalchemy_contracts.py +9 -1
  14. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_run_configuration.py +2 -1
  15. {mainsequence-4.4.25 → mainsequence-4.4.26}/LICENSE +0 -0
  16. {mainsequence-4.4.25 → mainsequence-4.4.26}/README.md +0 -0
  17. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/AGENTS.md +0 -0
  18. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  19. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  20. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  21. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  22. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  23. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/widgets/app_components/SKILL.md +0 -0
  24. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/widgets/tables/SKILL.md +0 -0
  25. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/widgets/tabular_transform/SKILL.md +0 -0
  26. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  27. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  28. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  29. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  30. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  31. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  32. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  33. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  34. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  35. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  36. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  37. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  38. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  39. {mainsequence-4.4.25 → mainsequence-4.4.26}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  40. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/__init__.py +0 -0
  41. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/__main__.py +0 -0
  42. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/bootstrap.py +0 -0
  43. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/__init__.py +0 -0
  44. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/api.py +0 -0
  45. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/browser_auth.py +0 -0
  46. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/cli.py +0 -0
  47. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/config.py +0 -0
  48. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/docker_utils.py +0 -0
  49. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/doctor.py +0 -0
  50. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/local_ops.py +0 -0
  51. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/model_filters.py +0 -0
  52. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/project_status.py +0 -0
  53. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/pydantic_cli.py +0 -0
  54. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/sdk_utils.py +0 -0
  55. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/ssh_utils.py +0 -0
  56. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/cli/ui.py +0 -0
  57. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/__init__.py +0 -0
  58. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/agent_runtime_models.py +0 -0
  59. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/base.py +0 -0
  60. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/client.py +0 -0
  61. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/__init__.py +0 -0
  62. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/app_component.py +0 -0
  63. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/connections.py +0 -0
  64. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/contracts/__init__.py +0 -0
  65. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/contracts/adapter_from_api.py +0 -0
  66. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/contracts/response_mapping.py +0 -0
  67. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/contracts/table_visuals.py +0 -0
  68. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/contracts/tabular.py +0 -0
  69. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/contracts/ui.py +0 -0
  70. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/data_models.py +0 -0
  71. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/providers/__init__.py +0 -0
  72. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/providers/adapter_from_api.py +0 -0
  73. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/widgets/__init__.py +0 -0
  74. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/widgets/bindings.py +0 -0
  75. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/widgets/connection_query.py +0 -0
  76. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/widgets/registry.py +0 -0
  77. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/widgets/table.py +0 -0
  78. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/widgets/tabular_transform.py +0 -0
  79. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/workspace.py +0 -0
  80. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  81. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/workspaces/__init__.py +0 -0
  82. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/workspaces/documents.py +0 -0
  83. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/command_center/workspaces/mounted_widgets.py +0 -0
  84. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/compute_validation.py +0 -0
  85. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  86. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  87. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  88. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  89. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/dtype_codec.py +0 -0
  90. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/exceptions.py +0 -0
  91. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/fastapi/__init__.py +0 -0
  92. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/fastapi/auth.py +0 -0
  93. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/metatables/__init__.py +0 -0
  94. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/models_foundry.py +0 -0
  95. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/models_helpers.py +0 -0
  96. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/models_user.py +0 -0
  97. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/client/utils.py +0 -0
  98. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/defaults.py +0 -0
  99. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/instrumentation/__init__.py +0 -0
  100. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/instrumentation/utils.py +0 -0
  101. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/logconf.py +0 -0
  102. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/__init__.py +0 -0
  103. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/__main__.py +0 -0
  104. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  105. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  106. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  107. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  108. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  109. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  110. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  111. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  112. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/future_registry.py +0 -0
  113. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/hashing.py +0 -0
  114. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  115. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/env.py +0 -0
  116. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/provider.py +0 -0
  117. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/registry.py +0 -0
  118. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  119. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  120. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  121. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  122. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  123. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/meta_tables/schema_names.py +0 -0
  124. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/runtime_flags.py +0 -0
  125. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence/scaffold_skills.py +0 -0
  126. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence.egg-info/SOURCES.txt +0 -0
  127. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence.egg-info/dependency_links.txt +0 -0
  128. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence.egg-info/entry_points.txt +0 -0
  129. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence.egg-info/requires.txt +0 -0
  130. {mainsequence-4.4.25 → mainsequence-4.4.26}/mainsequence.egg-info/top_level.txt +0 -0
  131. {mainsequence-4.4.25 → mainsequence-4.4.26}/setup.cfg +0 -0
  132. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_auth_precedence.py +0 -0
  133. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_build_operations_hashing.py +0 -0
  134. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_cli.py +0 -0
  135. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_cli_browser_auth.py +0 -0
  136. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_cli_migrations.py +0 -0
  137. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_client.py +0 -0
  138. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_command_center_app_component_models.py +0 -0
  139. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_command_center_data_models.py +0 -0
  140. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_command_center_models.py +0 -0
  141. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_command_center_table_contracts.py +0 -0
  142. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_command_center_tabular_transform.py +0 -0
  143. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_data_node_storage_dimension_queries.py +0 -0
  144. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_data_node_update_flow.py +0 -0
  145. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_dependency_extras.py +0 -0
  146. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_duckdb_interface_dimensions.py +0 -0
  147. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_filter_normalization.py +0 -0
  148. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_instrumentation.py +0 -0
  149. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_logconf.py +0 -0
  150. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_models_user_request_bound_auth.py +0 -0
  151. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_pod_project_resolution.py +0 -0
  152. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_project_batch_jobs_from_file.py +0 -0
  153. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_scaffold_skills.py +0 -0
  154. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_schema_names.py +0 -0
  155. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_secret_client_model.py +0 -0
  156. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_source_table_configuration.py +0 -0
  157. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_sqlite_interface_dimensions.py +0 -0
  158. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_update_runner_uid_runtime.py +0 -0
  159. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_update_statistics.py +0 -0
  160. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_update_uid_guards.py +0 -0
  161. {mainsequence-4.4.25 → mainsequence-4.4.26}/tests/test_workspace_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.4.25
3
+ Version: 4.4.26
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
@@ -260,6 +260,9 @@ def _contract_physical_table_name(item: Any) -> Any:
260
260
 
261
261
 
262
262
  def _contract_physical_schema(item: Any) -> Any:
263
+ physical_schema = _item_value(item, "physical_schema")
264
+ if physical_schema not in (None, ""):
265
+ return physical_schema
263
266
  contract = _item_value(item, "table_contract")
264
267
  if contract is None:
265
268
  return None
@@ -323,6 +326,7 @@ def _metatable_message(
323
326
  or model_name
324
327
  )
325
328
  uid = _item_value(item, "meta_table_uid") or _item_value(item, "uid")
329
+ physical_schema = _contract_physical_schema(item)
326
330
  physical_table_name = _item_value(item, "physical_table_name") or _contract_physical_table_name(
327
331
  item
328
332
  )
@@ -341,6 +345,8 @@ def _metatable_message(
341
345
  parts.append(f"model={model_name}")
342
346
  if uid not in (None, ""):
343
347
  parts.append(f"uid={uid}")
348
+ if physical_schema not in (None, ""):
349
+ parts.append(f"physical_schema={physical_schema}")
344
350
  if physical_table_name not in (None, ""):
345
351
  parts.append(f"physical_table={physical_table_name}")
346
352
  if provisioning_status not in (None, ""):
@@ -229,6 +229,46 @@ def _normalize_contract_mapping(contract: Mapping[str, Any]) -> dict[str, Any]:
229
229
  return normalized
230
230
 
231
231
 
232
+ def _contract_physical_mapping(contract: Any) -> Any:
233
+ if isinstance(contract, Mapping):
234
+ return contract.get("physical")
235
+ return getattr(contract, "physical", None)
236
+
237
+
238
+ def _contract_physical_schema(contract: Any) -> str | None:
239
+ physical = _contract_physical_mapping(contract)
240
+ if isinstance(physical, Mapping):
241
+ value = (
242
+ physical.get("schema")
243
+ or physical.get("schema_")
244
+ or physical.get("physical_schema")
245
+ )
246
+ else:
247
+ value = getattr(physical, "schema_", None) or getattr(
248
+ physical,
249
+ "physical_schema",
250
+ None,
251
+ )
252
+ if value in (None, ""):
253
+ return None
254
+ return str(value)
255
+
256
+
257
+ def _set_contract_physical_schema(contract: Any, schema: str) -> Any:
258
+ if isinstance(contract, Mapping):
259
+ normalized = dict(contract)
260
+ physical = normalized.get("physical")
261
+ physical_mapping = dict(physical) if isinstance(physical, Mapping) else {}
262
+ physical_mapping["schema"] = schema
263
+ normalized["physical"] = physical_mapping
264
+ return normalized
265
+
266
+ physical = getattr(contract, "physical", None)
267
+ if physical is not None and hasattr(physical, "schema_"):
268
+ physical.schema_ = schema
269
+ return contract
270
+
271
+
232
272
  def _temporal_parameter_names(parameters: dict[str, Any] | list[Any]) -> set[str]:
233
273
  if isinstance(parameters, Mapping):
234
274
  items = parameters.items()
@@ -246,8 +286,10 @@ class MetaTablePhysicalContract(BasePydanticModel):
246
286
  default=None,
247
287
  alias="schema",
248
288
  serialization_alias="schema",
249
- exclude=True,
250
- description=("Input-only schema alias. MetaTable uses the data source default schema."),
289
+ description=(
290
+ "Physical database schema. This is part of MetaTable physical identity "
291
+ "together with data_source_uid and table_name."
292
+ ),
251
293
  )
252
294
  table_name: str | None = Field(
253
295
  default=None,
@@ -257,7 +299,7 @@ class MetaTablePhysicalContract(BasePydanticModel):
257
299
  ),
258
300
  )
259
301
 
260
- model_config = ConfigDict(populate_by_name=True)
302
+ model_config = ConfigDict(populate_by_name=True, serialize_by_alias=True)
261
303
 
262
304
 
263
305
  class MetaTableColumnContract(BasePydanticModel):
@@ -413,6 +455,10 @@ class MetaTableForeignKeyPayload(BasePydanticModel):
413
455
  None,
414
456
  description="Physical table name of the target MetaTable.",
415
457
  )
458
+ target_table_physical_schema: str | None = Field(
459
+ None,
460
+ description="Physical schema of the target MetaTable.",
461
+ )
416
462
  target_columns: list[str] = Field(
417
463
  default_factory=list,
418
464
  description="Target MetaTable physical column names.",
@@ -550,6 +596,14 @@ class MetaTableRequestFields(BasePydanticModel):
550
596
  ...,
551
597
  description="Public UID of the DynamicTableDataSource that owns this MetaTable.",
552
598
  )
599
+ physical_schema: str | None = Field(
600
+ None,
601
+ description=(
602
+ "Physical database schema. This is part of the MetaTable physical "
603
+ "identity and must match table_contract.physical.schema when both "
604
+ "are provided."
605
+ ),
606
+ )
553
607
  table_contract: MetaTableContract | dict[str, Any]
554
608
  identifier: str | None = Field(
555
609
  default=None,
@@ -575,6 +629,19 @@ class MetaTableRequestFields(BasePydanticModel):
575
629
  def _normalize_table_contract(self) -> MetaTableRequestFields:
576
630
  if isinstance(self.table_contract, Mapping):
577
631
  self.table_contract = _normalize_contract_mapping(self.table_contract)
632
+ contract_schema = _contract_physical_schema(self.table_contract)
633
+ if self.physical_schema not in (None, "") and contract_schema not in (None, ""):
634
+ if str(self.physical_schema) != str(contract_schema):
635
+ raise ValueError(
636
+ "physical_schema must match table_contract.physical.schema."
637
+ )
638
+ if self.physical_schema in (None, "") and contract_schema not in (None, ""):
639
+ self.physical_schema = str(contract_schema)
640
+ elif self.physical_schema not in (None, "") and contract_schema in (None, ""):
641
+ self.table_contract = _set_contract_physical_schema(
642
+ self.table_contract,
643
+ str(self.physical_schema),
644
+ )
578
645
  return self
579
646
 
580
647
  @model_validator(mode="before")
@@ -713,6 +780,10 @@ class ManagedMetaTableFinalizeTableResult(BasePydanticModel):
713
780
  None,
714
781
  description="Physical table name reconciled against the data source.",
715
782
  )
783
+ physical_schema: str | None = Field(
784
+ None,
785
+ description="Physical schema reconciled against the data source.",
786
+ )
716
787
  previous_provisioning_status: str = Field(
717
788
  ...,
718
789
  description="Provisioning status before the finalize-managed attempt.",
@@ -1200,6 +1271,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1200
1271
  "provisioning_status": ["exact", "in"],
1201
1272
  "schema_management_mode": ["exact", "in"],
1202
1273
  "migration_provider_key": ["exact", "in"],
1274
+ "physical_schema": ["exact", "in"],
1203
1275
  "physical_table_name": ["exact", "contains", "in"],
1204
1276
  "labels": ["exact", "in", "contains"],
1205
1277
  }
@@ -1208,6 +1280,8 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1208
1280
  "uid__in": "str",
1209
1281
  "data_source__uid": "uid",
1210
1282
  "data_source__uid__in": "uid",
1283
+ "physical_schema": "str",
1284
+ "physical_schema__in": "str",
1211
1285
  "labels": "str",
1212
1286
  "labels__in": "str",
1213
1287
  "labels__contains": "str",
@@ -1254,6 +1328,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1254
1328
  migration_provider_key: str | None = None
1255
1329
  alembic_version_meta_table_uid: str | None = None
1256
1330
  alembic_revision: str | None = None
1331
+ physical_schema: str | None = None
1257
1332
  physical_table_name: str
1258
1333
  table_contract: dict[str, Any] = Field(default_factory=dict)
1259
1334
  contract_version: str = "relational-table.v1"
@@ -1945,6 +2020,13 @@ class TimeIndexMetaTableRegistrationRequest(BasePydanticModel):
1945
2020
  model_config = ConfigDict(extra="forbid")
1946
2021
 
1947
2022
  data_source_uid: str = Field(..., description="Public uid of the storage data source")
2023
+ physical_schema: str | None = Field(
2024
+ None,
2025
+ description=(
2026
+ "Physical database schema. This must match table_contract.physical.schema "
2027
+ "when both are provided."
2028
+ ),
2029
+ )
1948
2030
  identifier: str | None = Field(
1949
2031
  None,
1950
2032
  description=(
@@ -2027,6 +2109,23 @@ class TimeIndexMetaTableRegistrationRequest(BasePydanticModel):
2027
2109
  data["table_contract"] = normalized_contract
2028
2110
  return data
2029
2111
 
2112
+ @model_validator(mode="after")
2113
+ def _normalize_physical_schema(self) -> TimeIndexMetaTableRegistrationRequest:
2114
+ contract_schema = _contract_physical_schema(self.table_contract)
2115
+ if self.physical_schema not in (None, "") and contract_schema not in (None, ""):
2116
+ if str(self.physical_schema) != str(contract_schema):
2117
+ raise ValueError(
2118
+ "physical_schema must match table_contract.physical.schema."
2119
+ )
2120
+ if self.physical_schema in (None, "") and contract_schema not in (None, ""):
2121
+ self.physical_schema = str(contract_schema)
2122
+ elif self.physical_schema not in (None, "") and contract_schema in (None, ""):
2123
+ self.table_contract = _set_contract_physical_schema(
2124
+ self.table_contract,
2125
+ str(self.physical_schema),
2126
+ )
2127
+ return self
2128
+
2030
2129
  @field_validator("cadence")
2031
2130
  @classmethod
2032
2131
  def _normalize_cadence(cls, value: str | None) -> str | None:
@@ -3025,6 +3124,7 @@ class TimeIndexMetaTable(MetaTable):
3025
3124
  "uid": ["in", "exact"],
3026
3125
  "data_source__uid": ["in", "exact"],
3027
3126
  "namespace": ["exact", "contains", "in", "isnull"],
3127
+ "physical_schema": ["exact", "in"],
3028
3128
  "physical_table_name": ["exact", "contains", "in"],
3029
3129
  "labels": ["exact", "in", "contains"],
3030
3130
  }
@@ -3033,6 +3133,8 @@ class TimeIndexMetaTable(MetaTable):
3033
3133
  "uid__in": "uid",
3034
3134
  "data_source__uid": "uid",
3035
3135
  "data_source__uid__in": "uid",
3136
+ "physical_schema": "str",
3137
+ "physical_schema__in": "str",
3036
3138
  "labels": "str",
3037
3139
  "labels__in": "str",
3038
3140
  "labels__contains": "str",
@@ -134,6 +134,8 @@ class DataAccessMixin:
134
134
 
135
135
 
136
136
  class APIDataNode(DataAccessMixin):
137
+ LEGACY_DEFAULT_PHYSICAL_SCHEMA = "public"
138
+
137
139
  @staticmethod
138
140
  def _require_data_source_uid(data_source: Any, *, context: str) -> str:
139
141
  if isinstance(data_source, dict):
@@ -172,6 +174,24 @@ class APIDataNode(DataAccessMixin):
172
174
  raise ValueError(f"{context} requires storage_table.physical_table_name.")
173
175
  return str(table_name)
174
176
 
177
+ @classmethod
178
+ def _storage_table_physical_schema(cls, storage_table: Any) -> str:
179
+ if isinstance(storage_table, dict):
180
+ physical_schema = storage_table.get("physical_schema")
181
+ table_contract = storage_table.get("table_contract")
182
+ else:
183
+ physical_schema = getattr(storage_table, "physical_schema", None)
184
+ table_contract = getattr(storage_table, "table_contract", None)
185
+ if physical_schema not in (None, ""):
186
+ return str(physical_schema)
187
+ if isinstance(table_contract, dict):
188
+ physical = table_contract.get("physical") or {}
189
+ if isinstance(physical, dict):
190
+ contract_schema = physical.get("schema") or physical.get("schema_")
191
+ if contract_schema not in (None, ""):
192
+ return str(contract_schema)
193
+ return cls.LEGACY_DEFAULT_PHYSICAL_SCHEMA
194
+
175
195
  @classmethod
176
196
  def build_from_local_time_serie(cls, source_table: "DataNodeUpdate") -> "APIDataNode":
177
197
  physical_table_name = getattr(source_table, "physical_table_name", None) or getattr(
@@ -202,6 +222,7 @@ class APIDataNode(DataAccessMixin):
202
222
  storage_table,
203
223
  context=context,
204
224
  ),
225
+ physical_schema=cls._storage_table_physical_schema(storage_table),
205
226
  physical_table_name=cls._require_physical_table_name(
206
227
  storage_table,
207
228
  context=context,
@@ -215,22 +236,55 @@ class APIDataNode(DataAccessMixin):
215
236
  return cls.build_from_meta_table(storage_table)
216
237
 
217
238
  @classmethod
218
- def build_from_table_name(cls, table_name: str) -> "APIDataNode":
239
+ def build_from_physical_identity(
240
+ cls,
241
+ *,
242
+ schema: str,
243
+ table_name: str,
244
+ data_source_uid: str | None = None,
245
+ ) -> "APIDataNode":
246
+ if schema in (None, ""):
247
+ raise ValueError("APIDataNode.build_from_physical_identity requires schema.")
248
+ filters = {
249
+ "physical_schema": str(schema),
250
+ "physical_table_name": table_name,
251
+ }
252
+ if data_source_uid not in (None, ""):
253
+ filters["data_source__uid"] = str(data_source_uid)
219
254
  storage_table = TimeIndexMetaTable.get_or_none(
220
- physical_table_name=table_name,
255
+ **filters,
221
256
  )
222
257
  if storage_table is None:
223
258
  raise DoesNotExist(
224
259
  "No TimeIndexMetaTable found matching "
225
- f"{{'physical_table_name': {table_name!r}}}"
260
+ f"{{'physical_schema': {schema!r}, 'physical_table_name': {table_name!r}}}"
226
261
  )
227
262
  return cls.build_from_meta_table(storage_table)
228
263
 
264
+ @classmethod
265
+ def build_from_table_name(
266
+ cls,
267
+ table_name: str,
268
+ *,
269
+ schema: str = LEGACY_DEFAULT_PHYSICAL_SCHEMA,
270
+ data_source_uid: str | None = None,
271
+ ) -> "APIDataNode":
272
+ logger.warning(
273
+ "APIDataNode.build_from_table_name is ambiguous across schemas; "
274
+ "use build_from_physical_identity(schema=..., table_name=...) instead."
275
+ )
276
+ return cls.build_from_physical_identity(
277
+ schema=schema,
278
+ table_name=table_name,
279
+ data_source_uid=data_source_uid,
280
+ )
281
+
229
282
  def __init__(
230
283
  self,
231
284
  data_source_uid: str,
232
285
  physical_table_name: str | None = None,
233
286
  *,
287
+ physical_schema: str | None = None,
234
288
  storage_hash: str | None = None,
235
289
  data_source_local_lake: DataSource | None = None,
236
290
  storage_table: MetaTable | None = None,
@@ -255,6 +309,7 @@ class APIDataNode(DataAccessMixin):
255
309
  if resolved_physical_table_name in (None, ""):
256
310
  raise ValueError("APIDataNode requires physical_table_name.")
257
311
  self.data_source_uid = str(data_source_uid)
312
+ self.physical_schema = str(physical_schema or self.LEGACY_DEFAULT_PHYSICAL_SCHEMA)
258
313
  self.physical_table_name = str(resolved_physical_table_name)
259
314
  self.storage_table = storage_table
260
315
  self.data_source = data_source_local_lake
@@ -321,6 +376,7 @@ class APIDataNode(DataAccessMixin):
321
376
  self._verify_local_data_source()
322
377
  self._local_persist_manager = APIPersistManager(
323
378
  physical_table_name=self.physical_table_name,
379
+ physical_schema=self.physical_schema,
324
380
  data_source_uid=self.data_source_uid,
325
381
  )
326
382
  storage_table = self._local_persist_manager.storage_table
@@ -31,10 +31,13 @@ from mainsequence.meta_tables import PlatformTimeIndexMetaTable, compute_metatab
31
31
  from .. import future_registry
32
32
 
33
33
  _STORAGE_TABLE_LOOKUP_LIMIT = 20
34
+ _LEGACY_POSTGRES_SCHEMA = "public"
34
35
 
35
36
 
36
37
  @dataclass(frozen=True)
37
38
  class _StorageTableLookupResult:
39
+ data_source_uid: str | None
40
+ physical_schema: str | None
38
41
  table_name: str | None
39
42
  filters: dict[str, Any]
40
43
  matches: list[TimeIndexMetaTable]
@@ -144,21 +147,51 @@ def _registered_storage_table_lookup(
144
147
  storage_table: type[PlatformTimeIndexMetaTable],
145
148
  ) -> _StorageTableLookupResult:
146
149
  table_name = _storage_table_physical_table_name(storage_table)
147
- if table_name:
150
+ physical_schema = _storage_table_physical_schema(storage_table)
151
+ data_source_uid = storage_table.get_data_source_uid()
152
+ if table_name and physical_schema and data_source_uid:
148
153
  filters = {
154
+ "data_source__uid": data_source_uid,
155
+ "physical_schema__in": [physical_schema],
149
156
  "physical_table_name__in": [table_name],
150
157
  "limit": _STORAGE_TABLE_LOOKUP_LIMIT,
151
158
  }
152
- matches = TimeIndexMetaTable.filter_by_body(
153
- **filters,
154
- )
159
+ raw_matches = TimeIndexMetaTable.filter_by_body(**filters)
160
+ matches = [
161
+ match
162
+ for match in raw_matches
163
+ if _time_index_meta_table_identity(match)
164
+ == (data_source_uid, physical_schema, table_name)
165
+ ]
155
166
  return _StorageTableLookupResult(
167
+ data_source_uid=data_source_uid,
168
+ physical_schema=physical_schema,
156
169
  table_name=table_name,
157
170
  filters=filters,
158
171
  matches=matches,
159
172
  )
160
173
 
161
- return _StorageTableLookupResult(table_name=None, filters={}, matches=[])
174
+ return _StorageTableLookupResult(
175
+ data_source_uid=data_source_uid,
176
+ physical_schema=physical_schema,
177
+ table_name=table_name,
178
+ filters={},
179
+ matches=[],
180
+ )
181
+
182
+
183
+ def _storage_table_physical_schema(
184
+ storage_table: type[PlatformTimeIndexMetaTable],
185
+ ) -> str | None:
186
+ schema_getter = getattr(storage_table, "get_physical_schema", None)
187
+ physical_schema = schema_getter() if callable(schema_getter) else None
188
+ if physical_schema not in (None, ""):
189
+ return str(physical_schema)
190
+ table = getattr(storage_table, "__table__", None)
191
+ table_schema = getattr(table, "schema", None)
192
+ if table_schema not in (None, ""):
193
+ return str(table_schema)
194
+ return _LEGACY_POSTGRES_SCHEMA
162
195
 
163
196
 
164
197
  def _storage_table_physical_table_name(
@@ -175,8 +208,9 @@ def _storage_table_physical_table_name(
175
208
 
176
209
 
177
210
  def _storage_table_lookup_label(storage_table: type[PlatformTimeIndexMetaTable]) -> str:
211
+ physical_schema = _storage_table_physical_schema(storage_table) or "<unknown-schema>"
178
212
  table_name = _storage_table_physical_table_name(storage_table) or "<unknown-table>"
179
- return f"{storage_table.__name__}(table={table_name})"
213
+ return f"{storage_table.__name__}(schema={physical_schema}, table={table_name})"
180
214
 
181
215
 
182
216
  def _unbound_storage_table_message(
@@ -204,6 +238,16 @@ def _unbound_storage_table_message(
204
238
  f"{message} The SDK could not determine a physical table name from "
205
239
  "the storage model, so no backend catalog lookup was possible."
206
240
  )
241
+ if lookup_result.physical_schema is None:
242
+ return (
243
+ f"{message} The SDK could not determine a physical schema from "
244
+ "the storage model, so no backend catalog lookup was possible."
245
+ )
246
+ if lookup_result.data_source_uid is None:
247
+ return (
248
+ f"{message} The SDK could not determine a data-source UID from "
249
+ "the storage model, so no backend catalog lookup was possible."
250
+ )
207
251
 
208
252
  filters = _format_lookup_filters(lookup_result.filters)
209
253
  match_count = len(lookup_result.matches)
@@ -248,6 +292,12 @@ def _storage_table_identity_summary(storage_table: type[PlatformTimeIndexMetaTab
248
292
  data_source_uid = storage_table.get_data_source_uid()
249
293
  if data_source_uid not in (None, ""):
250
294
  parts.append(f"model_data_source_uid={data_source_uid!r}")
295
+ physical_schema = _storage_table_physical_schema(storage_table)
296
+ if physical_schema not in (None, ""):
297
+ parts.append(f"physical_schema={physical_schema!r}")
298
+ table_name = _storage_table_physical_table_name(storage_table)
299
+ if table_name not in (None, ""):
300
+ parts.append(f"physical_table_name={table_name!r}")
251
301
  if not parts:
252
302
  return "Storage identity is incomplete."
253
303
  return "Storage identity: " + ", ".join(parts) + "."
@@ -262,6 +312,7 @@ def _time_index_meta_table_candidate_summary(meta_table: TimeIndexMetaTable) ->
262
312
  for attr in (
263
313
  "uid",
264
314
  "data_source_uid",
315
+ "physical_schema",
265
316
  "identifier",
266
317
  "physical_table_name",
267
318
  "provisioning_status",
@@ -272,6 +323,35 @@ def _time_index_meta_table_candidate_summary(meta_table: TimeIndexMetaTable) ->
272
323
  return "{" + ", ".join(parts) + "}" if parts else "{unidentified TimeIndexMetaTable}"
273
324
 
274
325
 
326
+ def _time_index_meta_table_identity(
327
+ meta_table: TimeIndexMetaTable,
328
+ ) -> tuple[str, str, str] | None:
329
+ data_source_uid = getattr(meta_table, "data_source_uid", None)
330
+ if data_source_uid in (None, ""):
331
+ data_source = getattr(meta_table, "data_source", None)
332
+ if isinstance(data_source, dict):
333
+ data_source_uid = data_source.get("uid") or data_source.get("data_source_uid")
334
+ else:
335
+ data_source_uid = getattr(data_source, "uid", None) or getattr(
336
+ data_source,
337
+ "data_source_uid",
338
+ None,
339
+ )
340
+ physical_schema = getattr(meta_table, "physical_schema", None)
341
+ if physical_schema in (None, ""):
342
+ table_contract = getattr(meta_table, "table_contract", None)
343
+ if isinstance(table_contract, dict):
344
+ physical = table_contract.get("physical") or {}
345
+ if isinstance(physical, dict):
346
+ physical_schema = physical.get("schema") or physical.get("schema_")
347
+ if physical_schema in (None, ""):
348
+ physical_schema = _LEGACY_POSTGRES_SCHEMA
349
+ table_name = getattr(meta_table, "physical_table_name", None)
350
+ if data_source_uid in (None, "") or table_name in (None, ""):
351
+ return None
352
+ return str(data_source_uid), str(physical_schema), str(table_name)
353
+
354
+
275
355
  class BasePersistManager:
276
356
  UPDATE_CLASS: ClassVar[type[Any] | None] = None
277
357
  UPDATE_DETAILS_CLASS: ClassVar[type[Any] | None] = None
@@ -420,10 +500,12 @@ class BasePersistManager:
420
500
  )
421
501
  if result is None:
422
502
  meta_table_uid = self.storage_table.get_meta_table_uid()
503
+ physical_schema = _storage_table_physical_schema(self.storage_table)
423
504
  physical_table_name = _storage_table_physical_table_name(self.storage_table)
424
505
  self.logger.warning(
425
506
  f"DataNodeUpdate {self.update_hash} for MetaTable {meta_table_uid} "
426
- f"(physical_table_name={physical_table_name}) not found in backend"
507
+ f"(physical_schema={physical_schema}, "
508
+ f"physical_table_name={physical_table_name}) not found in backend"
427
509
  )
428
510
  new_future.set_result(result)
429
511
  except Exception as exc:
@@ -650,15 +732,20 @@ class APIPersistManager:
650
732
  self,
651
733
  *,
652
734
  physical_table_name: str | None = None,
735
+ physical_schema: str | None = None,
653
736
  storage_hash: str | None = None,
654
737
  data_source_uid: str,
655
738
  ):
656
739
  if data_source_uid in (None, ""):
657
740
  raise ValueError("APIPersistManager requires data_source_uid.")
658
741
  self.data_source_uid: str = str(data_source_uid)
742
+ self.physical_schema: str = str(physical_schema or _LEGACY_POSTGRES_SCHEMA)
659
743
  self.physical_table_name: str | None = physical_table_name or storage_hash
660
744
 
661
- logger.debug(f"Initializing Time Serie {self.physical_table_name} as APIDataNode")
745
+ logger.debug(
746
+ "Initializing APIDataNode storage "
747
+ f"{self.physical_schema}.{self.physical_table_name}"
748
+ )
662
749
 
663
750
  self._storage_table_future = Future()
664
751
  future_registry.add_future(self._storage_table_future)
@@ -678,6 +765,7 @@ class APIPersistManager:
678
765
  def _init_storage_table(self) -> None:
679
766
  try:
680
767
  result = TimeIndexMetaTable.get_or_none(
768
+ physical_schema=self.physical_schema,
681
769
  physical_table_name=self.physical_table_name,
682
770
  data_source__uid=self.data_source_uid,
683
771
  )