mainsequence 4.1.7__tar.gz → 4.1.9__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 (121) hide show
  1. {mainsequence-4.1.7 → mainsequence-4.1.9}/PKG-INFO +1 -1
  2. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +65 -15
  3. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +39 -9
  4. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/__init__.py +1 -0
  5. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -7
  6. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/pydantic_metadata.py +0 -18
  7. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/sqlalchemy_contracts.py +500 -199
  8. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence.egg-info/PKG-INFO +1 -1
  9. {mainsequence-4.1.7 → mainsequence-4.1.9}/pyproject.toml +1 -1
  10. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_build_operations_hashing.py +0 -40
  11. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_meta_tables_sqlalchemy_contracts.py +302 -72
  12. {mainsequence-4.1.7 → mainsequence-4.1.9}/LICENSE +0 -0
  13. {mainsequence-4.1.7 → mainsequence-4.1.9}/README.md +0 -0
  14. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/AGENTS.md +0 -0
  15. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  16. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  17. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  18. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  19. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  20. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  21. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  22. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  23. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  24. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  25. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  26. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  27. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  28. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  29. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  30. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  31. {mainsequence-4.1.7 → mainsequence-4.1.9}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  32. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/__init__.py +0 -0
  33. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/__main__.py +0 -0
  34. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/bootstrap.py +0 -0
  35. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/__init__.py +0 -0
  36. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/api.py +0 -0
  37. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/browser_auth.py +0 -0
  38. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/cli.py +0 -0
  39. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/config.py +0 -0
  40. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/docker_utils.py +0 -0
  41. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/doctor.py +0 -0
  42. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/local_ops.py +0 -0
  43. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/model_filters.py +0 -0
  44. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/project_status.py +0 -0
  45. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/pydantic_cli.py +0 -0
  46. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/sdk_utils.py +0 -0
  47. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/ssh_utils.py +0 -0
  48. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/cli/ui.py +0 -0
  49. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/__init__.py +0 -0
  50. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/agent_runtime_models.py +0 -0
  51. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/base.py +0 -0
  52. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/client.py +0 -0
  53. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/command_center/__init__.py +0 -0
  54. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/command_center/app_component.py +0 -0
  55. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/command_center/connections.py +0 -0
  56. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/command_center/data_models.py +0 -0
  57. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/command_center/workspace.py +0 -0
  58. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  59. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/compute_validation.py +0 -0
  60. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  61. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  62. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  63. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  64. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/dtype_codec.py +0 -0
  65. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/exceptions.py +0 -0
  66. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/fastapi/__init__.py +0 -0
  67. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/fastapi/auth.py +0 -0
  68. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/models_foundry.py +0 -0
  69. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/models_helpers.py +0 -0
  70. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/models_metatables.py +0 -0
  71. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/models_user.py +0 -0
  72. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/client/utils.py +0 -0
  73. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/defaults.py +0 -0
  74. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/instrumentation/__init__.py +0 -0
  75. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/instrumentation/utils.py +0 -0
  76. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/logconf.py +0 -0
  77. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/__main__.py +0 -0
  78. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  79. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  80. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  81. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  82. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  83. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  84. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  85. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  86. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  87. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/future_registry.py +0 -0
  88. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/meta_tables/hashing.py +0 -0
  89. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence/runtime_flags.py +0 -0
  90. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence.egg-info/SOURCES.txt +0 -0
  91. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence.egg-info/dependency_links.txt +0 -0
  92. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence.egg-info/entry_points.txt +0 -0
  93. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence.egg-info/requires.txt +0 -0
  94. {mainsequence-4.1.7 → mainsequence-4.1.9}/mainsequence.egg-info/top_level.txt +0 -0
  95. {mainsequence-4.1.7 → mainsequence-4.1.9}/setup.cfg +0 -0
  96. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_auth_precedence.py +0 -0
  97. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_cli.py +0 -0
  98. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_cli_browser_auth.py +0 -0
  99. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_client.py +0 -0
  100. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_command_center_app_component_models.py +0 -0
  101. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_command_center_data_models.py +0 -0
  102. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_command_center_models.py +0 -0
  103. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  104. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_data_node_storage_dimension_queries.py +0 -0
  105. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_data_node_update_flow.py +0 -0
  106. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_dependency_extras.py +0 -0
  107. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_duckdb_interface_dimensions.py +0 -0
  108. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_filter_normalization.py +0 -0
  109. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_logconf.py +0 -0
  110. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_meta_tables_client_models.py +0 -0
  111. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_models_user_request_bound_auth.py +0 -0
  112. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_pod_project_resolution.py +0 -0
  113. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_project_batch_jobs_from_file.py +0 -0
  114. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_run_configuration.py +0 -0
  115. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_secret_client_model.py +0 -0
  116. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_source_table_configuration.py +0 -0
  117. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_sqlite_interface_dimensions.py +0 -0
  118. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_update_runner_uid_runtime.py +0 -0
  119. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_update_statistics.py +0 -0
  120. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_update_uid_guards.py +0 -0
  121. {mainsequence-4.1.7 → mainsequence-4.1.9}/tests/test_workspace_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.1.7
3
+ Version: 4.1.9
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
@@ -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 config fields into update identity and hash-excluded descriptive metadata
28
+ - classify values as hashed config fields or non-config class/runtime values
29
29
  - implement or review:
30
30
  - `dependencies()`
31
31
  - `update()`
@@ -112,6 +112,16 @@ Every storage class must include `__metatable_description__`. The description
112
112
  should explain the table's intention, row grain, and downstream use, not only
113
113
  list columns or schema mechanics.
114
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
+
115
125
  Do not put those concerns in `DataNodeConfiguration`.
116
126
 
117
127
  Minimal pattern:
@@ -132,6 +142,7 @@ class Base(DeclarativeBase):
132
142
  class PricesTable(PlatformTimeIndexMetaData, Base):
133
143
  __metatable_namespace__ = "<domain_namespace>"
134
144
  __metatable_identifier__ = "<table_identifier>"
145
+ __metatable_extra_hash_components__ = {"storage_name": "<stable_storage_name>"}
135
146
  __metatable_description__ = (
136
147
  "Daily close prices keyed by asset unique identifier for portfolio and "
137
148
  "risk analytics."
@@ -183,12 +194,21 @@ Do not accept `test_node`. It has been removed. Use explicit
183
194
  Pattern:
184
195
 
185
196
  ```python
197
+ from typing import ClassVar
198
+
199
+ from pydantic import Field
200
+
186
201
  from mainsequence.meta_tables import DataNode, DataNodeConfiguration
187
202
  from mainsequence.meta_tables import PlatformTimeIndexMetaData
188
203
 
189
204
 
190
205
  class PricesConfig(DataNodeConfiguration):
191
- 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"
192
212
 
193
213
 
194
214
  class PricesUpdate(DataNode):
@@ -216,23 +236,45 @@ class PricesUpdate(DataNode):
216
236
  ### 3. Configuration Is Update-Scoped By Default
217
237
 
218
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.
219
241
 
220
- Do not use:
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.
221
246
 
222
- - `json_schema_extra={"update_only": True}`
223
- - `json_schema_extra={"runtime_only": True}`
224
- - `json_schema_extra={"ignore_from_storage_hash": True}`
225
- - `_ARGS_IGNORE_IN_STORAGE_HASH`
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.
226
251
 
227
- Those are removed. The only supported opt-out is:
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}`:
228
255
 
229
256
  ```python
230
- Field(..., json_schema_extra={"hash_excluded": True})
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
+ )
231
274
  ```
232
275
 
233
- Use `hash_excluded` only for descriptive metadata that must not affect update
234
- identity. If a field changes output values, dependencies, source choice, or
235
- 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.
236
278
 
237
279
  ### 4. `hash_namespace` Is Isolation Only
238
280
 
@@ -311,7 +353,16 @@ Do not construct dependency graphs dynamically inside `update()`.
311
353
  ### 8. Foreign Keys Belong To The Storage Contract
312
354
 
313
355
  For new code, model foreign keys on the `PlatformTimeIndexMetaData` storage
314
- class or route the storage-contract work to the MetaTable skill.
356
+ class or route the storage-contract work to the MetaTable skill. When a
357
+ DataNode storage table needs a platform-managed FK, use
358
+ `MetaTableForeignKey(TargetModel, column=...)` on the storage class. Do not use
359
+ `ForeignKey(Target.__table__.c.uid)`, table fullnames, or explicit target UID
360
+ maps in DataNode examples.
361
+
362
+ Registration of the storage class follows the MetaTable lifecycle:
363
+ `register()` recursively registers unresolved FK target model classes, uses the
364
+ local process registry keyed by `storage_hash`, and writes the target
365
+ `MetaTable.uid` into the FK contract.
315
366
 
316
367
  Do not add DataNode configuration fields just to mutate storage metadata.
317
368
 
@@ -330,11 +381,10 @@ When reviewing an existing DataNode, look for:
330
381
  - missing `__metatable_description__` on the storage table
331
382
  - dependency storage table passed as an ad hoc constructor argument
332
383
  - schema or published table metadata hidden in DataNode configuration
333
- - `update_only`, `runtime_only`, or `ignore_from_storage_hash`
334
384
  - `test_node=True`
335
385
  - missing explicit `storage_table`
336
386
  - accidental storage registration inside the DataNode
337
- - wrong meaning/scope/hash-excluded split
387
+ - wrong split between hashed config fields and non-config class/runtime values
338
388
  - misuse of `hash_namespace`
339
389
  - non-incremental `update()` behavior
340
390
  - hidden dependency creation inside `update()`
@@ -103,12 +103,23 @@ Always declare `__metatable_description__` on the model. The description must
103
103
  explain the table's business intention, row grain, and expected use, not only
104
104
  the schema. Column-level descriptions stay in `mapped_column(info={...})`.
105
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
+
106
116
  Register through the class API:
107
117
 
108
118
  ```python
109
119
  class Account(PlatformManagedMetaTable, Base):
110
120
  __metatable_namespace__ = "sdk-examples"
111
121
  __metatable_identifier__ = "Account"
122
+ __metatable_extra_hash_components__ = {"storage_name": "account"}
112
123
  __metatable_description__ = (
113
124
  "Customer account master records used to scope balances, holdings, and "
114
125
  "account-level limits."
@@ -141,29 +152,48 @@ Do not add a `MAINSEQUENCE_META_TABLE_REGISTER` toggle in registration examples.
141
152
 
142
153
  Foreign-key contracts reference the target `MetaTable` UID.
143
154
 
144
- 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.
155
+ For `PlatformManagedMetaTable`, define foreign keys with
156
+ `MetaTableForeignKey(TargetModel, column=...)`. Do not write raw SQLAlchemy
157
+ table fullnames, `Parent.__table__.c.<column>` targets, or explicit target UID
158
+ maps in the platform-managed path. Registration is the lifecycle path:
159
+ `register()` recursively registers unresolved target model classes, stores each
160
+ returned `MetaTable` in a local process registry keyed by `storage_hash`, and
161
+ uses the target `MetaTable.uid` in the child FK contract.
162
+
163
+ Use this pattern:
164
+
165
+ ```python
166
+ account_uid: Mapped[uuid.UUID] = mapped_column(
167
+ Uuid,
168
+ MetaTableForeignKey(Account, column="uid", ondelete="RESTRICT"),
169
+ nullable=False,
170
+ )
171
+ ```
172
+
173
+ Every participating table must include `__metatable_description__` describing
174
+ both the schema and the table's intention.
145
175
 
146
176
  Example registration order:
147
177
 
148
178
  ```python
149
- account_meta_table = Account.register(...)
150
179
  asset_meta_table = Asset.register(...)
151
180
  ```
152
181
 
153
- 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.
154
-
155
- 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.
182
+ The child registration registers `Account` first if it has not already been
183
+ registered in the current process. The local registry prevents duplicate backend
184
+ registration attempts for the same `storage_hash` and raises a clear error for
185
+ recursive registration cycles.
156
186
 
157
- 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:
187
+ For `external_registered`, there is no platform-managed parent lookup. Register
188
+ the parent first, then build the child registration request with
189
+ `target_meta_tables={Account: account_meta_table}`:
158
190
 
159
191
  ```python
160
192
  account_meta_table = MetaTable.register(account_request)
161
193
  asset_request = external_registered_registration_request_from_sqlalchemy_model(
162
194
  Asset,
163
195
  data_source_uid=data_source_uid,
164
- target_meta_table_uid_by_fullname={
165
- Account.__table__.fullname: account_meta_table.uid,
166
- },
196
+ target_meta_tables={Account: account_meta_table},
167
197
  )
168
198
  asset_meta_table = MetaTable.register(asset_request)
169
199
  ```
@@ -9,6 +9,7 @@ _LAZY_IMPORTS = {
9
9
  ".sqlalchemy_contracts",
10
10
  "DEFAULT_PLATFORM_MANAGED_PROVISIONING",
11
11
  ),
12
+ "MetaTableForeignKey": (".sqlalchemy_contracts", "MetaTableForeignKey"),
12
13
  "PlatformManagedMetaTable": (".sqlalchemy_contracts", "PlatformManagedMetaTable"),
13
14
  "PlatformTimeIndexMetaData": (".sqlalchemy_contracts", "PlatformTimeIndexMetaData"),
14
15
  "POSTGRES_IDENTIFIER_MAX_LENGTH": (".hashing", "POSTGRES_IDENTIFIER_MAX_LENGTH"),
@@ -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(