mainsequence 4.1.6__tar.gz → 4.1.8__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.1.6 → mainsequence-4.1.8}/PKG-INFO +1 -1
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +65 -14
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +30 -4
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_metatables.py +61 -2
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/__init__.py +0 -4
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -7
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/pydantic_metadata.py +0 -18
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/sqlalchemy_contracts.py +263 -66
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-4.1.6 → mainsequence-4.1.8}/pyproject.toml +1 -1
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_build_operations_hashing.py +3 -43
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_meta_tables_client_models.py +46 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_meta_tables_sqlalchemy_contracts.py +271 -26
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_run_configuration.py +1 -1
- {mainsequence-4.1.6 → mainsequence-4.1.8}/LICENSE +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/README.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/__main__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/cli.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/base.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/client.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/utils.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/defaults.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/logconf.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/setup.cfg +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_cli.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_client.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_data_node_update_flow.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_filter_normalization.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_logconf.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_secret_client_model.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_workspace_snapshot.py +0 -0
{mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md
RENAMED
|
@@ -25,7 +25,7 @@ Canonical workflow:
|
|
|
25
25
|
- modify an existing `DataNode` update process
|
|
26
26
|
- review whether a DataNode change affects update identity or table contract
|
|
27
27
|
- define or refactor `DataNodeConfiguration`
|
|
28
|
-
- classify
|
|
28
|
+
- classify values as hashed config fields or non-config class/runtime values
|
|
29
29
|
- implement or review:
|
|
30
30
|
- `dependencies()`
|
|
31
31
|
- `update()`
|
|
@@ -108,6 +108,20 @@ The following are storage-contract decisions:
|
|
|
108
108
|
- foreign keys
|
|
109
109
|
- table description and labels
|
|
110
110
|
|
|
111
|
+
Every storage class must include `__metatable_description__`. The description
|
|
112
|
+
should explain the table's intention, row grain, and downstream use, not only
|
|
113
|
+
list columns or schema mechanics.
|
|
114
|
+
|
|
115
|
+
Use `__metatable_extra_hash_components__` on storage classes when distinct
|
|
116
|
+
DataNode storage tables could otherwise have the same storage-relevant shape.
|
|
117
|
+
For example, two one-index daily tables with one float column need a stable
|
|
118
|
+
component such as `{"storage_name": "daily_random_number"}` versus
|
|
119
|
+
`{"storage_name": "daily_random_addition"}`.
|
|
120
|
+
|
|
121
|
+
This is storage identity. Changing it creates a different storage table. Do not
|
|
122
|
+
use it for labels, descriptions, runtime options, test isolation, backend UIDs,
|
|
123
|
+
data-source UIDs, or updater scope.
|
|
124
|
+
|
|
111
125
|
Do not put those concerns in `DataNodeConfiguration`.
|
|
112
126
|
|
|
113
127
|
Minimal pattern:
|
|
@@ -128,6 +142,11 @@ class Base(DeclarativeBase):
|
|
|
128
142
|
class PricesTable(PlatformTimeIndexMetaData, Base):
|
|
129
143
|
__metatable_namespace__ = "<domain_namespace>"
|
|
130
144
|
__metatable_identifier__ = "<table_identifier>"
|
|
145
|
+
__metatable_extra_hash_components__ = {"storage_name": "<stable_storage_name>"}
|
|
146
|
+
__metatable_description__ = (
|
|
147
|
+
"Daily close prices keyed by asset unique identifier for portfolio and "
|
|
148
|
+
"risk analytics."
|
|
149
|
+
)
|
|
131
150
|
__time_index_name__ = "time_index"
|
|
132
151
|
__index_names__ = ["time_index", "unique_identifier"]
|
|
133
152
|
|
|
@@ -175,12 +194,21 @@ Do not accept `test_node`. It has been removed. Use explicit
|
|
|
175
194
|
Pattern:
|
|
176
195
|
|
|
177
196
|
```python
|
|
197
|
+
from typing import ClassVar
|
|
198
|
+
|
|
199
|
+
from pydantic import Field
|
|
200
|
+
|
|
178
201
|
from mainsequence.meta_tables import DataNode, DataNodeConfiguration
|
|
179
202
|
from mainsequence.meta_tables import PlatformTimeIndexMetaData
|
|
180
203
|
|
|
181
204
|
|
|
182
205
|
class PricesConfig(DataNodeConfiguration):
|
|
183
|
-
shard_id: str
|
|
206
|
+
shard_id: str = Field(
|
|
207
|
+
...,
|
|
208
|
+
description="Stable updater partition for this price job.",
|
|
209
|
+
examples=["us_equities_daily"],
|
|
210
|
+
)
|
|
211
|
+
reference_dimension: ClassVar[str] = "unique_identifier"
|
|
184
212
|
|
|
185
213
|
|
|
186
214
|
class PricesUpdate(DataNode):
|
|
@@ -208,23 +236,45 @@ class PricesUpdate(DataNode):
|
|
|
208
236
|
### 3. Configuration Is Update-Scoped By Default
|
|
209
237
|
|
|
210
238
|
Every `DataNodeConfiguration` field participates in `update_hash` by default.
|
|
239
|
+
Declare values that change output values, dependencies, source choice, or
|
|
240
|
+
updater scope as normal config fields.
|
|
211
241
|
|
|
212
|
-
|
|
242
|
+
Every config field must be declared with `Field(...)`. Include a clear
|
|
243
|
+
`description` and add `examples=[...]` whenever a realistic example helps. The
|
|
244
|
+
description must explain what the value means for the update process, not repeat
|
|
245
|
+
the Python type.
|
|
213
246
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
247
|
+
Values that must not affect `update_hash` should not be Pydantic config fields.
|
|
248
|
+
Use `ClassVar[...]` for class-level invariants and implementation constants, or
|
|
249
|
+
keep runtime controls in environment/runtime configuration outside the
|
|
250
|
+
DataNode config.
|
|
218
251
|
|
|
219
|
-
|
|
252
|
+
If a value genuinely must remain a Pydantic field while not affecting
|
|
253
|
+
`update_hash`, the only supported field-level opt-out is
|
|
254
|
+
`json_schema_extra={"hash_excluded": True}`:
|
|
220
255
|
|
|
221
256
|
```python
|
|
222
|
-
|
|
257
|
+
from pydantic import Field
|
|
258
|
+
|
|
259
|
+
from mainsequence.meta_tables import DataNodeConfiguration
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class PricesConfig(DataNodeConfiguration):
|
|
263
|
+
shard_id: str = Field(
|
|
264
|
+
...,
|
|
265
|
+
description="Stable updater partition for this price job.",
|
|
266
|
+
examples=["us_equities_daily"],
|
|
267
|
+
)
|
|
268
|
+
display_label: str | None = Field(
|
|
269
|
+
default=None,
|
|
270
|
+
description="Optional human-facing label for UI display only.",
|
|
271
|
+
examples=["US equities daily prices"],
|
|
272
|
+
json_schema_extra={"hash_excluded": True},
|
|
273
|
+
)
|
|
223
274
|
```
|
|
224
275
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
updater scope, it must remain a normal config field.
|
|
276
|
+
Do not invent other metadata-marker exceptions. Scope, dependency, source, and
|
|
277
|
+
output-affecting fields must remain hashed config fields.
|
|
228
278
|
|
|
229
279
|
### 4. `hash_namespace` Is Isolation Only
|
|
230
280
|
|
|
@@ -319,13 +369,13 @@ Do not put schema or published table metadata on the DataNode configuration.
|
|
|
319
369
|
When reviewing an existing DataNode, look for:
|
|
320
370
|
|
|
321
371
|
- output storage contract hidden in `DataNodeConfiguration`
|
|
372
|
+
- missing `__metatable_description__` on the storage table
|
|
322
373
|
- dependency storage table passed as an ad hoc constructor argument
|
|
323
374
|
- schema or published table metadata hidden in DataNode configuration
|
|
324
|
-
- `update_only`, `runtime_only`, or `ignore_from_storage_hash`
|
|
325
375
|
- `test_node=True`
|
|
326
376
|
- missing explicit `storage_table`
|
|
327
377
|
- accidental storage registration inside the DataNode
|
|
328
|
-
- wrong
|
|
378
|
+
- wrong split between hashed config fields and non-config class/runtime values
|
|
329
379
|
- misuse of `hash_namespace`
|
|
330
380
|
- non-incremental `update()` behavior
|
|
331
381
|
- hidden dependency creation inside `update()`
|
|
@@ -339,6 +389,7 @@ Do not claim success until you have checked:
|
|
|
339
389
|
|
|
340
390
|
- the relevant docs were read first
|
|
341
391
|
- storage is a registered `PlatformTimeIndexMetaData` class
|
|
392
|
+
- storage has an intention-rich `__metatable_description__`
|
|
342
393
|
- the DataNode constructor requires `storage_table`
|
|
343
394
|
- dependency storage-table references live in config and are registered
|
|
344
395
|
- config fields are updater-scoped by default
|
{mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md
RENAMED
|
@@ -99,15 +99,39 @@ The mixin derives the SQLAlchemy physical table name from storage-relevant confi
|
|
|
99
99
|
|
|
100
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
101
|
|
|
102
|
+
Always declare `__metatable_description__` on the model. The description must
|
|
103
|
+
explain the table's business intention, row grain, and expected use, not only
|
|
104
|
+
the schema. Column-level descriptions stay in `mapped_column(info={...})`.
|
|
105
|
+
|
|
106
|
+
Use `__metatable_extra_hash_components__` when two backend-managed tables could
|
|
107
|
+
otherwise produce the same storage hash because their storage-relevant shape is
|
|
108
|
+
identical or intentionally generic. The value must be stable and deterministic,
|
|
109
|
+
usually a small mapping such as `{"storage_name": "account_holdings"}`.
|
|
110
|
+
|
|
111
|
+
This attribute is part of storage identity. Changing it defines a different
|
|
112
|
+
table. Do not use it for labels, descriptions, runtime options, test isolation,
|
|
113
|
+
backend UIDs, data-source UIDs, or updater scope. Use `hash_namespace` for test
|
|
114
|
+
or experiment isolation.
|
|
115
|
+
|
|
102
116
|
Register through the class API:
|
|
103
117
|
|
|
104
118
|
```python
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
class Account(PlatformManagedMetaTable, Base):
|
|
120
|
+
__metatable_namespace__ = "sdk-examples"
|
|
121
|
+
__metatable_identifier__ = "Account"
|
|
122
|
+
__metatable_extra_hash_components__ = {"storage_name": "account"}
|
|
123
|
+
__metatable_description__ = (
|
|
124
|
+
"Customer account master records used to scope balances, holdings, and "
|
|
125
|
+
"account-level limits."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
account_meta_table = Account.register(labels=["sdk-example"])
|
|
109
130
|
```
|
|
110
131
|
|
|
132
|
+
Pass `description=...` only when the call intentionally overrides the class
|
|
133
|
+
default.
|
|
134
|
+
|
|
111
135
|
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
136
|
|
|
113
137
|
Only call `build_registration_request()` when the task explicitly needs to inspect or validate the payload before registration.
|
|
@@ -172,6 +196,7 @@ Do not hardcode platform-managed physical names manually.
|
|
|
172
196
|
When reviewing an existing MetaTable workflow, look for:
|
|
173
197
|
|
|
174
198
|
- missing namespace or identifier
|
|
199
|
+
- missing `__metatable_description__`, or a description that only repeats column names instead of table intention
|
|
175
200
|
- backend-managed models that do not inherit `PlatformManagedMetaTable`
|
|
176
201
|
- backend-managed examples that use namespace environment variables instead of a plain `sdk-examples` namespace
|
|
177
202
|
- duplicate schema sources outside SQLAlchemy table metadata
|
|
@@ -187,6 +212,7 @@ When reviewing an existing MetaTable workflow, look for:
|
|
|
187
212
|
Do not claim success until you have checked:
|
|
188
213
|
|
|
189
214
|
- the table contract matches the intended row contract
|
|
215
|
+
- the table has an intention-rich `__metatable_description__`
|
|
190
216
|
- indexes are intentional
|
|
191
217
|
- foreign keys resolve to the correct dependency targets
|
|
192
218
|
- management mode is correct
|
|
@@ -220,7 +220,13 @@ class MetaTablePhysicalContract(BasePydanticModel):
|
|
|
220
220
|
exclude=True,
|
|
221
221
|
description=("Input-only schema alias. MetaTable uses the data source default schema."),
|
|
222
222
|
)
|
|
223
|
-
table_name: str = Field(
|
|
223
|
+
table_name: str | None = Field(
|
|
224
|
+
default=None,
|
|
225
|
+
description=(
|
|
226
|
+
"Physical database table name. Required for external_registered tables; "
|
|
227
|
+
"omitted by platform_managed client requests because the backend owns it."
|
|
228
|
+
),
|
|
229
|
+
)
|
|
224
230
|
|
|
225
231
|
model_config = ConfigDict(populate_by_name=True)
|
|
226
232
|
|
|
@@ -1090,6 +1096,55 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
|
|
|
1090
1096
|
self.introspection_snapshot = snapshot
|
|
1091
1097
|
return response_json
|
|
1092
1098
|
|
|
1099
|
+
def get_schema_graph(
|
|
1100
|
+
self,
|
|
1101
|
+
*,
|
|
1102
|
+
depth: int = 1,
|
|
1103
|
+
include_incoming: bool = False,
|
|
1104
|
+
timeout: int | float | tuple[float, float] | None = None,
|
|
1105
|
+
) -> dict[str, Any]:
|
|
1106
|
+
"""
|
|
1107
|
+
Return the MetaTable foreign-key schema graph rooted at this table.
|
|
1108
|
+
|
|
1109
|
+
The graph is the client API for dependency analysis. Outgoing edges are
|
|
1110
|
+
foreign keys declared by this table. When ``include_incoming`` is true,
|
|
1111
|
+
the response also includes visible tables that depend on this table.
|
|
1112
|
+
|
|
1113
|
+
Args:
|
|
1114
|
+
depth: Relationship traversal depth. The backend clamps this to its
|
|
1115
|
+
supported range.
|
|
1116
|
+
include_incoming: Include inbound relationships where another
|
|
1117
|
+
visible MetaTable has a foreign key targeting this table.
|
|
1118
|
+
timeout: Optional request timeout in seconds or ``requests`` timeout
|
|
1119
|
+
tuple form.
|
|
1120
|
+
|
|
1121
|
+
Returns:
|
|
1122
|
+
A dictionary with ``root_uid``, ``depth``, ``include_incoming``,
|
|
1123
|
+
``nodes``, and ``edges``. Inbound dependencies are edges where
|
|
1124
|
+
``edge["target_uid"] == self.uid``; the dependent table UID is
|
|
1125
|
+
``edge["source_uid"]``.
|
|
1126
|
+
"""
|
|
1127
|
+
url = f"{type(self).get_object_url().rstrip('/')}/{self._public_uid()}/schema-graph/"
|
|
1128
|
+
payload = {
|
|
1129
|
+
"params": serialize_to_json(
|
|
1130
|
+
{
|
|
1131
|
+
"depth": depth,
|
|
1132
|
+
"include_incoming": include_incoming,
|
|
1133
|
+
}
|
|
1134
|
+
)
|
|
1135
|
+
}
|
|
1136
|
+
response = make_request(
|
|
1137
|
+
s=type(self).build_session(),
|
|
1138
|
+
loaders=type(self).LOADERS,
|
|
1139
|
+
r_type="GET",
|
|
1140
|
+
url=url,
|
|
1141
|
+
payload=payload,
|
|
1142
|
+
time_out=timeout,
|
|
1143
|
+
)
|
|
1144
|
+
if response.status_code != 200:
|
|
1145
|
+
raise_for_response(response, payload=payload)
|
|
1146
|
+
return response.json()
|
|
1147
|
+
|
|
1093
1148
|
def refresh_table_search_index(
|
|
1094
1149
|
self,
|
|
1095
1150
|
*,
|
|
@@ -1335,7 +1390,11 @@ class TimeIndexMetaTableRegistrationRequest(BasePydanticModel):
|
|
|
1335
1390
|
model_config = ConfigDict(extra="forbid")
|
|
1336
1391
|
|
|
1337
1392
|
data_source_uid: str = Field(..., description="Public uid of the storage data source")
|
|
1338
|
-
storage_hash: str = Field(
|
|
1393
|
+
storage_hash: str = Field(
|
|
1394
|
+
...,
|
|
1395
|
+
max_length=63,
|
|
1396
|
+
description="Canonical logical storage identity for the time-indexed MetaTable",
|
|
1397
|
+
)
|
|
1339
1398
|
identifier: str | None = Field(None, description="Optional published storage identifier")
|
|
1340
1399
|
namespace: str | None = Field(None, description="Optional published storage namespace")
|
|
1341
1400
|
description: str | None = Field(None, description="Optional storage description")
|
|
@@ -38,10 +38,6 @@ _LAZY_IMPORTS = {
|
|
|
38
38
|
".sqlalchemy_contracts",
|
|
39
39
|
"register_external_sqlalchemy_model",
|
|
40
40
|
),
|
|
41
|
-
"register_platform_managed_sqlalchemy_model": (
|
|
42
|
-
".sqlalchemy_contracts",
|
|
43
|
-
"register_platform_managed_sqlalchemy_model",
|
|
44
|
-
),
|
|
45
41
|
"slugify_identifier": (".hashing", "slugify_identifier"),
|
|
46
42
|
"table_contract_from_sqlalchemy_model": (
|
|
47
43
|
".sqlalchemy_contracts",
|
|
@@ -420,13 +420,6 @@ class DataNode(DataAccessMixin, ABC):
|
|
|
420
420
|
"""
|
|
421
421
|
super().__init_subclass__(**kwargs)
|
|
422
422
|
|
|
423
|
-
if "_ARGS_IGNORE_IN_STORAGE_HASH" in cls.__dict__:
|
|
424
|
-
raise TypeError(
|
|
425
|
-
f"{cls.__name__} uses removed class attribute _ARGS_IGNORE_IN_STORAGE_HASH; "
|
|
426
|
-
"move those fields into DataNodeConfiguration. Configuration fields "
|
|
427
|
-
"participate in update hashing by default."
|
|
428
|
-
)
|
|
429
|
-
|
|
430
423
|
# Get the original __init__ from the new subclass
|
|
431
424
|
original_init = cls.__init__
|
|
432
425
|
|
|
@@ -20,24 +20,6 @@ def serialize_pydantic_model(
|
|
|
20
20
|
|
|
21
21
|
for field_name, field_info in value.__class__.model_fields.items():
|
|
22
22
|
extra = field_info.json_schema_extra or {}
|
|
23
|
-
if "ignore_from_storage_hash" in extra:
|
|
24
|
-
raise ValueError(
|
|
25
|
-
f"{value.__class__.__name__}.{field_name} uses removed metadata "
|
|
26
|
-
"'ignore_from_storage_hash'. All configuration fields participate in "
|
|
27
|
-
'update hashing by default; use json_schema_extra={"hash_excluded": True} '
|
|
28
|
-
"only for fields that should not affect update identity."
|
|
29
|
-
)
|
|
30
|
-
if "update_only" in extra:
|
|
31
|
-
raise ValueError(
|
|
32
|
-
f"{value.__class__.__name__}.{field_name} uses removed metadata "
|
|
33
|
-
"'update_only'. All configuration fields are update-scoped by default."
|
|
34
|
-
)
|
|
35
|
-
if "runtime_only" in extra:
|
|
36
|
-
raise ValueError(
|
|
37
|
-
f"{value.__class__.__name__}.{field_name} uses removed metadata "
|
|
38
|
-
"'runtime_only'; use json_schema_extra={\"hash_excluded\": True} instead."
|
|
39
|
-
)
|
|
40
|
-
|
|
41
23
|
is_hash_excluded = extra.get("hash_excluded", False)
|
|
42
24
|
if not isinstance(is_hash_excluded, bool):
|
|
43
25
|
raise ValueError(
|