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.
- {mainsequence-4.0.3 → mainsequence-4.0.5}/PKG-INFO +1 -1
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +67 -10
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_tdag.py +37 -26
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/build_operations.py +3 -3
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/meta_tables/__init__.py +9 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/meta_tables/hashing.py +54 -0
- mainsequence-4.0.5/mainsequence/tdag/meta_tables/sqlalchemy_contracts.py +1066 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/SOURCES.txt +0 -1
- {mainsequence-4.0.3 → mainsequence-4.0.5}/pyproject.toml +1 -1
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_node_update_flow.py +34 -0
- mainsequence-4.0.5/tests/test_meta_tables_sqlalchemy_contracts.py +551 -0
- mainsequence-4.0.5/tests/test_pod_project_resolution.py +177 -0
- mainsequence-4.0.3/mainsequence/client/data_sources_interfaces/timescale.py +0 -524
- mainsequence-4.0.3/mainsequence/tdag/meta_tables/sqlalchemy_contracts.py +0 -476
- mainsequence-4.0.3/tests/test_meta_tables_sqlalchemy_contracts.py +0 -209
- mainsequence-4.0.3/tests/test_pod_project_resolution.py +0 -98
- {mainsequence-4.0.3 → mainsequence-4.0.5}/LICENSE +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/README.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/maintenance/local_journal/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/__main__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/cli.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/base.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/client.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_metatables.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/client/utils.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/compute_validation.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/defaults.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/logconf.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/__main__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/base_persist_managers.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/config.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/configuration_models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/__init__.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/filters.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/namespacing.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/data_nodes/utils.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/filters.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/future_registry.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/meta_tables/compiled_sql.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/pydantic_metadata.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence/tdag/utils.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/setup.cfg +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_cli.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_client.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_node_search_join_filters.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_filter_normalization.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_logconf.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_meta_tables_client_models.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_run_configuration.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.0.3 → mainsequence-4.0.5}/tests/test_workspace_snapshot.py +0 -0
{mainsequence-4.0.3 → mainsequence-4.0.5}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md
RENAMED
|
@@ -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
|
-
-
|
|
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`,
|
|
96
|
+
For `platform_managed`, inherit from `PlatformManagedMetaTable`.
|
|
96
97
|
|
|
97
|
-
The
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
|
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["
|
|
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
|
]
|