mainsequence 4.0.3__tar.gz → 4.0.5__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 (126) hide show
  1. {mainsequence-4.0.3 → mainsequence-4.0.5}/PKG-INFO +1 -1
  2. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +67 -10
  3. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_tdag.py +37 -26
  4. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/build_operations.py +3 -3
  5. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/meta_tables/__init__.py +9 -0
  6. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/meta_tables/hashing.py +54 -0
  7. mainsequence-4.0.5/mainsequence/tdag/meta_tables/sqlalchemy_contracts.py +1066 -0
  8. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/PKG-INFO +1 -1
  9. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/SOURCES.txt +0 -1
  10. {mainsequence-4.0.3 → mainsequence-4.0.5}/pyproject.toml +1 -1
  11. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_node_update_flow.py +34 -0
  12. mainsequence-4.0.5/tests/test_meta_tables_sqlalchemy_contracts.py +551 -0
  13. mainsequence-4.0.5/tests/test_pod_project_resolution.py +177 -0
  14. mainsequence-4.0.3/mainsequence/client/data_sources_interfaces/timescale.py +0 -524
  15. mainsequence-4.0.3/mainsequence/tdag/meta_tables/sqlalchemy_contracts.py +0 -476
  16. mainsequence-4.0.3/tests/test_meta_tables_sqlalchemy_contracts.py +0 -209
  17. mainsequence-4.0.3/tests/test_pod_project_resolution.py +0 -98
  18. {mainsequence-4.0.3 → mainsequence-4.0.5}/LICENSE +0 -0
  19. {mainsequence-4.0.3 → mainsequence-4.0.5}/README.md +0 -0
  20. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/AGENTS.md +0 -0
  21. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  22. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  23. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  24. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  25. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  26. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  27. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  28. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  29. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  30. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  31. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  32. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  33. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  34. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/maintenance/local_journal/SKILL.md +0 -0
  35. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  36. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  37. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  38. {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  39. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/__init__.py +0 -0
  40. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/__main__.py +0 -0
  41. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/bootstrap.py +0 -0
  42. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/__init__.py +0 -0
  43. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/api.py +0 -0
  44. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/browser_auth.py +0 -0
  45. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/cli.py +0 -0
  46. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/config.py +0 -0
  47. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/docker_utils.py +0 -0
  48. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/doctor.py +0 -0
  49. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/local_ops.py +0 -0
  50. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/model_filters.py +0 -0
  51. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/project_status.py +0 -0
  52. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/pydantic_cli.py +0 -0
  53. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/sdk_utils.py +0 -0
  54. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/ssh_utils.py +0 -0
  55. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/ui.py +0 -0
  56. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/__init__.py +0 -0
  57. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/agent_runtime_models.py +0 -0
  58. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/base.py +0 -0
  59. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/client.py +0 -0
  60. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/__init__.py +0 -0
  61. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/app_component.py +0 -0
  62. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/connections.py +0 -0
  63. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/data_models.py +0 -0
  64. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/workspace.py +0 -0
  65. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  66. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  67. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  68. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/exceptions.py +0 -0
  69. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/fastapi/__init__.py +0 -0
  70. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/fastapi/auth.py +0 -0
  71. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_helpers.py +0 -0
  72. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_metatables.py +0 -0
  73. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_user.py +0 -0
  74. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/utils.py +0 -0
  75. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/compute_validation.py +0 -0
  76. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/defaults.py +0 -0
  77. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/instrumentation/__init__.py +0 -0
  78. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/instrumentation/utils.py +0 -0
  79. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/logconf.py +0 -0
  80. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/runtime_flags.py +0 -0
  81. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/__init__.py +0 -0
  82. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/__main__.py +0 -0
  83. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/base_persist_managers.py +0 -0
  84. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/config.py +0 -0
  85. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/configuration_models.py +0 -0
  86. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/__init__.py +0 -0
  87. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
  88. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/filters.py +0 -0
  89. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/models.py +0 -0
  90. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/namespacing.py +0 -0
  91. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
  92. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
  93. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/utils.py +0 -0
  94. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/filters.py +0 -0
  95. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/future_registry.py +0 -0
  96. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/meta_tables/compiled_sql.py +0 -0
  97. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/pydantic_metadata.py +0 -0
  98. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/utils.py +0 -0
  99. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/dependency_links.txt +0 -0
  100. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/entry_points.txt +0 -0
  101. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/requires.txt +0 -0
  102. {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/top_level.txt +0 -0
  103. {mainsequence-4.0.3 → mainsequence-4.0.5}/setup.cfg +0 -0
  104. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_auth_precedence.py +0 -0
  105. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_build_operations_hashing.py +0 -0
  106. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_cli.py +0 -0
  107. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_cli_browser_auth.py +0 -0
  108. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_client.py +0 -0
  109. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_command_center_app_component_models.py +0 -0
  110. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_command_center_data_models.py +0 -0
  111. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_command_center_models.py +0 -0
  112. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  113. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_node_search_join_filters.py +0 -0
  114. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_node_storage_dimension_queries.py +0 -0
  115. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_dependency_extras.py +0 -0
  116. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_filter_normalization.py +0 -0
  117. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_logconf.py +0 -0
  118. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_meta_tables_client_models.py +0 -0
  119. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_models_user_request_bound_auth.py +0 -0
  120. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_project_batch_jobs_from_file.py +0 -0
  121. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_run_configuration.py +0 -0
  122. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_source_table_configuration.py +0 -0
  123. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_update_runner_uid_runtime.py +0 -0
  124. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_update_statistics.py +0 -0
  125. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_update_uid_guards.py +0 -0
  126. {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_workspace_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.0.3
3
+ Version: 4.0.5
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
@@ -15,7 +15,8 @@ This skill is for schema-driven application tables registered through TS Manager
15
15
 
16
16
  - define SQLAlchemy/Core or ORM table models for `MetaTable` registration
17
17
  - choose `platform_managed` or `external_registered` management mode
18
- - build registration requests from resolved SQLAlchemy metadata
18
+ - register platform-managed tables through the model class API
19
+ - build registration requests from resolved SQLAlchemy metadata when inspection is useful
19
20
  - define indexes and foreign keys in the table contract
20
21
  - design governed compiled SQL read and write operations
21
22
  - review table contracts for physical-name, namespace, and identifier issues
@@ -67,7 +68,7 @@ Before changing code, collect or infer:
67
68
  - expected read patterns
68
69
  - expected mutation patterns
69
70
  - whether TS Manager should create the physical table
70
- - the target `DynamicTableDataSource` UID
71
+ - for `external_registered`, the target `DynamicTableDataSource` UID
71
72
 
72
73
  If ownership of the physical table lifecycle is unclear, stop before choosing a management mode.
73
74
 
@@ -92,15 +93,67 @@ Do not hand-build contract fragments when the SQLAlchemy helper can derive them.
92
93
 
93
94
  ### 2. Use storage-hash physical names for backend-managed tables
94
95
 
95
- For `platform_managed`, use `metatable_tablename(...)` as the SQLAlchemy `__tablename__`.
96
+ For `platform_managed`, inherit from `PlatformManagedMetaTable`.
96
97
 
97
- The backend expects that deterministic physical name to match the registration storage hash.
98
+ The mixin derives the SQLAlchemy physical table name from storage-relevant configuration and table shape. Do not hand-write `__tablename__` for normal backend-managed tables.
99
+
100
+ Schema must come from SQLAlchemy table metadata, usually `__table_args__ = {"schema": "public"}` or the tuple form ending in `{"schema": ...}`. Do not add a separate MetaTable-specific schema attribute.
101
+
102
+ Register through the class API:
103
+
104
+ ```python
105
+ account_meta_table = Account.register(
106
+ description="Example account table.",
107
+ labels=["sdk-example"],
108
+ )
109
+ ```
110
+
111
+ For platform-managed registration, the data source is resolved from the active Main Sequence project/session, the same way DataNode does. Do not require or thread a `data_source_uid` through normal platform-managed example code.
112
+
113
+ Only call `build_registration_request()` when the task explicitly needs to inspect or validate the payload before registration.
114
+
115
+ For SDK examples, use a plain namespace constant:
116
+
117
+ ```python
118
+ NAMESPACE = "sdk-examples"
119
+ ```
120
+
121
+ Do not add an environment variable for namespace in examples.
122
+
123
+ Do not add generic labels such as `"meta-table"` or `"platform-managed"` to examples. Keep labels specific to the example or domain.
124
+
125
+ Do not add a `MAINSEQUENCE_META_TABLE_REGISTER` toggle in registration examples. Registration examples should register directly.
98
126
 
99
127
  ### 3. Register parent tables before child tables
100
128
 
101
129
  Foreign-key contracts reference the target `MetaTable` UID.
102
130
 
103
- Register parent tables first, then pass their UIDs when building child table registration requests.
131
+ For `PlatformManagedMetaTable`, register parent tables first and then register child tables normally. The SDK inspects SQLAlchemy foreign-key constraints and resolves each target `MetaTable` by looking up the already registered table in the same data source, schema, and physical table name.
132
+
133
+ Example registration order:
134
+
135
+ ```python
136
+ account_meta_table = Account.register(...)
137
+ asset_meta_table = Asset.register(...)
138
+ ```
139
+
140
+ The child registration will fail if the parent table has not already been registered, because there is no target `MetaTable.uid` for the backend FK contract.
141
+
142
+ Do not pass `target_meta_tables` or `target_meta_table_uid_by_fullname` in the normal platform-managed path. Use explicit FK target mappings only for edge cases where automatic lookup is ambiguous or impossible.
143
+
144
+ For `external_registered`, there is no platform-managed parent lookup through the model class. Register the parent first, then build the child registration request with the parent UID mapped by target table fullname:
145
+
146
+ ```python
147
+ account_meta_table = MetaTable.register(account_request)
148
+ asset_request = external_registered_registration_request_from_sqlalchemy_model(
149
+ Asset,
150
+ data_source_uid=data_source_uid,
151
+ target_meta_table_uid_by_fullname={
152
+ Account.__table__.fullname: account_meta_table.uid,
153
+ },
154
+ )
155
+ asset_meta_table = MetaTable.register(asset_request)
156
+ ```
104
157
 
105
158
  ### 4. Governed operations declare scope
106
159
 
@@ -119,9 +172,12 @@ Do not hardcode platform-managed physical names manually.
119
172
  When reviewing an existing MetaTable workflow, look for:
120
173
 
121
174
  - missing namespace or identifier
122
- - backend-managed models that do not use `metatable_tablename(...)`
175
+ - backend-managed models that do not inherit `PlatformManagedMetaTable`
176
+ - backend-managed examples that use namespace environment variables instead of a plain `sdk-examples` namespace
177
+ - duplicate schema sources outside SQLAlchemy table metadata
123
178
  - external tables registered with unstable physical names
124
- - foreign keys that do not reference target MetaTable UIDs
179
+ - platform-managed child tables that are registered before parent tables
180
+ - external child registrations that do not map foreign-key targets to registered parent `MetaTable.uid` values
125
181
  - compiled SQL operations without complete table scope
126
182
  - raw SQL that hardcodes stale physical names
127
183
  - a table that should really be modeled as a DataNode instead
@@ -132,7 +188,7 @@ Do not claim success until you have checked:
132
188
 
133
189
  - the table contract matches the intended row contract
134
190
  - indexes are intentional
135
- - foreign keys point to the correct dependency targets
191
+ - foreign keys resolve to the correct dependency targets
136
192
  - management mode is correct
137
193
  - backend-managed physical names match the storage hash
138
194
  - registration returns a `MetaTable.uid`
@@ -141,13 +197,14 @@ Do not claim success until you have checked:
141
197
  For related tables, also check:
142
198
 
143
199
  - aliases are readable
144
- - parent table UIDs are passed into child contracts
200
+ - platform-managed parent tables are registered before child tables
201
+ - external child registration requests map FK targets to the registered parent UIDs
145
202
  - query results still match the expected response contract
146
203
 
147
204
  ## This Skill Must Stop And Escalate When
148
205
 
149
206
  - physical table lifecycle ownership is unclear
150
- - the target data source is unknown
207
+ - the target data source is unknown for an `external_registered` workflow
151
208
  - the task really requires a time-series published table
152
209
  - the workflow requires direct database credentials outside TS Manager governance
153
210
  - the task is actually an API or orchestration problem
@@ -28,7 +28,6 @@ from mainsequence.logconf import logger
28
28
  from . import exceptions
29
29
  from .base import BaseObjectOrm, BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin
30
30
  from .data_sources_interfaces import get_duckdb_interface_class
31
- from .data_sources_interfaces import timescale as TimeScaleInterface
32
31
  from .exceptions import raise_for_response
33
32
  from .utils import (
34
33
  MAINSEQUENCE_ENDPOINT,
@@ -44,7 +43,6 @@ from .utils import (
44
43
  make_request,
45
44
  serialize_to_json,
46
45
  session,
47
- set_types_in_table,
48
46
  )
49
47
 
50
48
  _default_data_source = None # Module-level cache
@@ -350,7 +348,13 @@ class DataNodeUpdate(TableUpdateNode, BaseObjectOrm):
350
348
  url = cls.get_object_url() + "/get_or_create/"
351
349
  kwargs = serialize_to_json(kwargs)
352
350
  pod_project = _require_local_pod_project("DataNodeUpdate.get_or_create")
353
- kwargs["current_project_id"] = pod_project.id
351
+ project_uid = str(getattr(pod_project, "uid", "") or "").strip()
352
+ if not project_uid:
353
+ raise RuntimeError(
354
+ "DataNodeUpdate.get_or_create requires a local pod project uid, "
355
+ "but the resolved project does not expose one."
356
+ )
357
+ kwargs["current_project_uid"] = project_uid
354
358
  payload = {"json": kwargs}
355
359
  s = cls.build_session()
356
360
  r = make_request(s=s, loaders=cls.LOADERS, r_type="POST", url=url, payload=payload)
@@ -3150,11 +3154,23 @@ class UpdateBatchResponse(BaseModel, Generic[UpdateT, UpdateDetailsT, SourceTabl
3150
3154
 
3151
3155
 
3152
3156
  class DataSource(BasePydanticModel, BaseObjectOrm):
3157
+ uid: str | None = Field(
3158
+ None,
3159
+ description="Public uid of the data source.",
3160
+ )
3161
+ data_source_uid: str | None = Field(
3162
+ None,
3163
+ description="Compatibility alias for the public data source uid.",
3164
+ )
3153
3165
  id: int | None = Field(None, description="The unique identifier of the Local Disk Source Lake")
3154
3166
  display_name: str
3155
3167
  organization: int | None = Field(
3156
3168
  None, description="The unique identifier of the Local Disk Source Lake"
3157
3169
  )
3170
+ organization_uid: str | None = Field(
3171
+ None,
3172
+ description="Public uid of the owning organization.",
3173
+ )
3158
3174
  class_type: str
3159
3175
  status: str
3160
3176
  extra_arguments: dict | None = None
@@ -3307,7 +3323,14 @@ class DataSource(BasePydanticModel, BaseObjectOrm):
3307
3323
 
3308
3324
 
3309
3325
  class DynamicTableDataSource(BasePydanticModel, BaseObjectOrm):
3310
- id: int
3326
+ uid: str | None = Field(
3327
+ None,
3328
+ description="Public uid of the dynamic table data source.",
3329
+ )
3330
+ id: int | None = Field(
3331
+ None,
3332
+ description="Legacy numeric identifier of the dynamic table data source.",
3333
+ )
3311
3334
  related_resource: DataSource
3312
3335
  related_resource_class_type: str
3313
3336
 
@@ -3345,23 +3368,8 @@ class DynamicTableDataSource(BasePydanticModel, BaseObjectOrm):
3345
3368
  raise Exception(f"Error in request {r.text}")
3346
3369
  return cls(**r.json())
3347
3370
 
3348
- def has_direct_postgres_connection(self):
3349
- return self.related_resource.class_type == "direct"
3350
-
3351
3371
  def get_data_by_time_index(self, *args, **kwargs):
3352
- if self.has_direct_postgres_connection():
3353
- stc = kwargs["data_node_update"].data_node_storage.sourcetableconfiguration
3354
-
3355
- df = TimeScaleInterface.direct_data_from_db(
3356
- *args,
3357
- connection_uri=self.related_resource.get_connection_uri(),
3358
-
3359
- **kwargs,
3360
- )
3361
- df = set_types_in_table(df, stc.column_dtypes_map)
3362
- return df
3363
- else:
3364
- return self.related_resource.get_data_by_time_index(*args, **kwargs)
3372
+ return self.related_resource.get_data_by_time_index(*args, **kwargs)
3365
3373
 
3366
3374
 
3367
3375
 
@@ -3478,14 +3486,11 @@ class Project(LabelableObjectMixin, ShareableObjectMixin, BasePydanticModel, Bas
3478
3486
  FILTERSET_FIELDS: ClassVar[dict[str, list[str]]] = {
3479
3487
  "project_name": ["in", "exact", "contains"],
3480
3488
  "uid": ["in", "exact"],
3481
- "id": ["in", "exact"],
3482
3489
  "labels": ["exact", "in", "contains"],
3483
3490
  }
3484
3491
  FILTER_VALUE_NORMALIZERS: ClassVar[dict[str, str]] = {
3485
3492
  "uid": "str",
3486
3493
  "uid__in": "str",
3487
- "id": "id",
3488
- "id__in": "id",
3489
3494
  "project_name": "str",
3490
3495
  "labels": "str",
3491
3496
  "labels__in": "str",
@@ -3498,10 +3503,10 @@ class Project(LabelableObjectMixin, ShareableObjectMixin, BasePydanticModel, Bas
3498
3503
  examples=["project-uid-142"],
3499
3504
  json_schema_extra={"label": "Project UID"},
3500
3505
  )
3501
- id: int = Field(
3502
- ...,
3506
+ id: int | None = Field(
3507
+ None,
3503
3508
  title="Project ID",
3504
- description="Unique identifier of the project.",
3509
+ description="Legacy numeric identifier of the project.",
3505
3510
  examples=[142],
3506
3511
  json_schema_extra={"label": "Project ID"},
3507
3512
  )
@@ -3525,6 +3530,12 @@ class Project(LabelableObjectMixin, ShareableObjectMixin, BasePydanticModel, Bas
3525
3530
  examples=["git@github.com:mainsequence/data-pipeline.git"],
3526
3531
  json_schema_extra={"label": "Git SSH URL"},
3527
3532
  )
3533
+ created_by: str | int | dict[str, Any] | None = Field(
3534
+ None,
3535
+ title="Created By",
3536
+ description="Backend-provided creator metadata for the project.",
3537
+ json_schema_extra={"label": "Created By"},
3538
+ )
3528
3539
 
3529
3540
  labels: list[str] = Field(
3530
3541
  default_factory=list,
@@ -245,10 +245,10 @@ def hash_signature(dictionary: dict[str, Any]) -> tuple[str, str]:
245
245
  local_ts_dict_to_hash = _strip_pydantic_hash_exclusions(parsed_dictionary, for_storage_hash=False)
246
246
  remote_ts_in_db_hash = _strip_pydantic_hash_exclusions(parsed_dictionary, for_storage_hash=True)
247
247
 
248
- # Add project_id for local hash
248
+ # Add project_uid for local hash so local hashing follows the public project contract.
249
249
  resolution = _resolve_local_pod_project()
250
- if resolution.project is not None:
251
- local_ts_dict_to_hash["project_id"] = resolution.project.id
250
+ if resolution.project is not None and getattr(resolution.project, "uid", None):
251
+ local_ts_dict_to_hash["project_uid"] = resolution.project.uid
252
252
  # Encode and hash both versions
253
253
  encoded_local = json.dumps(local_ts_dict_to_hash, sort_keys=True).encode()
254
254
  encoded_remote = json.dumps(remote_ts_in_db_hash, sort_keys=True).encode()
@@ -7,17 +7,26 @@ _LAZY_IMPORTS = {
7
7
  ".sqlalchemy_contracts",
8
8
  "DEFAULT_PLATFORM_MANAGED_PROVISIONING",
9
9
  ),
10
+ "PlatformManagedMetaTable": (".sqlalchemy_contracts", "PlatformManagedMetaTable"),
10
11
  "POSTGRES_IDENTIFIER_MAX_LENGTH": (".hashing", "POSTGRES_IDENTIFIER_MAX_LENGTH"),
11
12
  "build_compiled_sql_v1_operation": (
12
13
  ".compiled_sql",
13
14
  "build_compiled_sql_v1_operation",
14
15
  ),
16
+ "build_meta_table_configured_storage_hash": (
17
+ ".hashing",
18
+ "build_meta_table_configured_storage_hash",
19
+ ),
15
20
  "build_meta_table_storage_hash": (".hashing", "build_meta_table_storage_hash"),
16
21
  "compile_sqlalchemy_statement": (".compiled_sql", "compile_sqlalchemy_statement"),
17
22
  "external_registered_registration_request_from_sqlalchemy_model": (
18
23
  ".sqlalchemy_contracts",
19
24
  "external_registered_registration_request_from_sqlalchemy_model",
20
25
  ),
26
+ "metatable_configured_tablename": (
27
+ ".sqlalchemy_contracts",
28
+ "metatable_configured_tablename",
29
+ ),
21
30
  "metatable_tablename": (".sqlalchemy_contracts", "metatable_tablename"),
22
31
  "platform_managed_registration_request_from_sqlalchemy_model": (
23
32
  ".sqlalchemy_contracts",
@@ -76,6 +76,59 @@ def build_meta_table_storage_hash(
76
76
  return storage_hash
77
77
 
78
78
 
79
+ def build_meta_table_configured_storage_hash(
80
+ *,
81
+ namespace: str,
82
+ schema: str = "public",
83
+ table_storage_identity: Mapping[str, Any],
84
+ hash_namespace: str | None = None,
85
+ extra_hash_components: Mapping[str, Any] | None = None,
86
+ max_length: int = POSTGRES_IDENTIFIER_MAX_LENGTH,
87
+ ) -> str:
88
+ """
89
+ Build a PostgreSQL-safe storage hash for a configured MetaTable.
90
+
91
+ This path intentionally excludes the logical MetaTable identifier. The
92
+ physical table is identified by storage-relevant configuration, while the
93
+ identifier remains backend/display metadata.
94
+ """
95
+
96
+ if max_length <= _HASH_SUFFIX_LENGTH:
97
+ raise ValueError("max_length must leave room for the DataNode hash suffix.")
98
+
99
+ namespace = namespace.strip()
100
+ schema = schema.strip()
101
+ if not namespace:
102
+ raise ValueError("namespace is required to build a configured MetaTable storage hash.")
103
+ if not schema:
104
+ raise ValueError("schema is required to build a configured MetaTable storage hash.")
105
+
106
+ prefix_base = slugify_identifier(f"mt_{namespace}")
107
+ max_prefix_length = max_length - _HASH_SUFFIX_LENGTH
108
+ prefix = prefix_base[:max_prefix_length].rstrip("_") or "mt"
109
+
110
+ hash_payload: dict[str, Any] = {
111
+ "namespace": namespace,
112
+ "schema": schema,
113
+ "table_storage_identity": dict(table_storage_identity),
114
+ }
115
+ if hash_namespace:
116
+ hash_payload["hash_namespace"] = hash_namespace.strip()
117
+ if extra_hash_components:
118
+ hash_payload.update(dict(extra_hash_components))
119
+
120
+ storage_hash = _build_storage_hash_with_data_node_machinery(
121
+ prefix=prefix,
122
+ hash_payload=hash_payload,
123
+ )
124
+ if len(storage_hash) > max_length:
125
+ raise ValueError(
126
+ f"Generated MetaTable storage hash exceeds {max_length} characters: "
127
+ f"{storage_hash!r}."
128
+ )
129
+ return storage_hash
130
+
131
+
79
132
  def _build_storage_hash_with_data_node_machinery(
80
133
  *,
81
134
  prefix: str,
@@ -113,6 +166,7 @@ def _build_storage_hash_without_tdag_config(
113
166
 
114
167
  __all__ = [
115
168
  "POSTGRES_IDENTIFIER_MAX_LENGTH",
169
+ "build_meta_table_configured_storage_hash",
116
170
  "build_meta_table_storage_hash",
117
171
  "slugify_identifier",
118
172
  ]