mainsequence 4.1.1__tar.gz → 4.1.2__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.1 → mainsequence-4.1.2}/PKG-INFO +1 -1
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +16 -1
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/build_operations.py +44 -98
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/run_operations.py +41 -63
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/sqlalchemy_contracts.py +7 -4
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-4.1.1 → mainsequence-4.1.2}/pyproject.toml +1 -1
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_build_operations_hashing.py +41 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_pod_project_resolution.py +3 -55
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_update_runner_uid_runtime.py +85 -4
- {mainsequence-4.1.1 → mainsequence-4.1.2}/LICENSE +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/README.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/__main__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/cli.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/base.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/client.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/models_metatables.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/utils.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/compute_validation.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/defaults.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/logconf.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/compiled_sql.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/config.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/configuration_models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/utils.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/setup.cfg +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_cli.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_client.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_data_node_update_flow.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_filter_normalization.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_logconf.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_meta_tables_client_models.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_run_configuration.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_secret_client_model.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.1.1 → mainsequence-4.1.2}/tests/test_workspace_snapshot.py +0 -0
{mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md
RENAMED
|
@@ -161,6 +161,16 @@ The DataNode constructor should accept:
|
|
|
161
161
|
- a registered `storage_table: type[PlatformTimeIndexMetaData]`
|
|
162
162
|
- optional `hash_namespace`
|
|
163
163
|
|
|
164
|
+
The constructor `storage_table` is the output storage contract. Keep it out of
|
|
165
|
+
`DataNodeConfiguration`.
|
|
166
|
+
|
|
167
|
+
If the DataNode needs to select another DataNode's storage table as a
|
|
168
|
+
dependency, put that dependency storage reference in the config as
|
|
169
|
+
`type[PlatformTimeIndexMetaData]`. Do not add an extra constructor argument for
|
|
170
|
+
dependency storage tables. Config values of this type are hashed by the bound
|
|
171
|
+
`TimeIndexMetaData.uid` from `StorageClass.__time_index_metadata__`, so the class
|
|
172
|
+
must be registered or bound before DataNode construction.
|
|
173
|
+
|
|
164
174
|
Do not accept `test_node`. It has been removed. Use explicit
|
|
165
175
|
`hash_namespace(...)` or `hash_namespace="..."`.
|
|
166
176
|
|
|
@@ -287,6 +297,9 @@ Time index must be datetime64[ns, UTC]
|
|
|
287
297
|
|
|
288
298
|
Dependencies belong in constructor setup and `dependencies()`.
|
|
289
299
|
|
|
300
|
+
Dependency storage-table selection belongs in `DataNodeConfiguration`, because
|
|
301
|
+
changing it changes the dependency graph and update identity.
|
|
302
|
+
|
|
290
303
|
Do not construct dependency graphs dynamically inside `update()`.
|
|
291
304
|
|
|
292
305
|
### 8. Asset-Scoped Updates Must Be Explicit
|
|
@@ -318,7 +331,8 @@ surface for new DataNode work.
|
|
|
318
331
|
|
|
319
332
|
When reviewing an existing DataNode, look for:
|
|
320
333
|
|
|
321
|
-
- storage contract hidden in `DataNodeConfiguration`
|
|
334
|
+
- output storage contract hidden in `DataNodeConfiguration`
|
|
335
|
+
- dependency storage table passed as an ad hoc constructor argument
|
|
322
336
|
- old `RecordDefinition` or `DataNodeMetaData` schema patterns
|
|
323
337
|
- `update_only`, `runtime_only`, or `ignore_from_storage_hash`
|
|
324
338
|
- `test_node=True`
|
|
@@ -339,6 +353,7 @@ Do not claim success until you have checked:
|
|
|
339
353
|
- the relevant docs were read first
|
|
340
354
|
- storage is a registered or bound `PlatformTimeIndexMetaData` class
|
|
341
355
|
- the DataNode constructor requires `storage_table`
|
|
356
|
+
- dependency storage-table references live in config and are registered or bound
|
|
342
357
|
- config fields are updater-scoped by default
|
|
343
358
|
- no removed hash metadata markers remain
|
|
344
359
|
- no `test_node` usage remains
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/build_operations.py
RENAMED
|
@@ -19,16 +19,12 @@ from pydantic import BaseModel
|
|
|
19
19
|
from mainsequence.client import BaseObjectOrm
|
|
20
20
|
from mainsequence.client.models_helpers import get_model_class
|
|
21
21
|
from mainsequence.client.models_metatables import _resolve_local_pod_project
|
|
22
|
-
from mainsequence.instrumentation import tracer, tracer_instrumentator
|
|
23
22
|
from mainsequence.meta_tables.pydantic_metadata import (
|
|
24
23
|
is_serialized_pydantic_model,
|
|
25
24
|
serialize_pydantic_model,
|
|
26
25
|
strip_pydantic_hash_exclusions,
|
|
27
26
|
)
|
|
28
27
|
|
|
29
|
-
from .namespacing import disable_hash_namespace
|
|
30
|
-
from .persist_managers import PersistManager
|
|
31
|
-
|
|
32
28
|
if TYPE_CHECKING:
|
|
33
29
|
from .data_nodes import APIDataNode, DataNode
|
|
34
30
|
|
|
@@ -70,6 +66,46 @@ def _serialize_api_timeserie(value: APIDataNode) -> dict[str, Any]:
|
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
|
|
69
|
+
def _import_qualified_name(module_name: str, qualname: str) -> Any:
|
|
70
|
+
module = importlib.import_module(module_name)
|
|
71
|
+
value: Any = module
|
|
72
|
+
for part in qualname.split("."):
|
|
73
|
+
value = getattr(value, part)
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _is_platform_time_index_metadata_class(value: Any) -> bool:
|
|
78
|
+
try:
|
|
79
|
+
from mainsequence.meta_tables.sqlalchemy_contracts import PlatformTimeIndexMetaData
|
|
80
|
+
except ImportError:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
return isinstance(value, type) and issubclass(value, PlatformTimeIndexMetaData)
|
|
85
|
+
except TypeError:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@serialize_argument.register(type)
|
|
90
|
+
def _(value: type[Any]) -> Any:
|
|
91
|
+
if not _is_platform_time_index_metadata_class(value):
|
|
92
|
+
return value
|
|
93
|
+
|
|
94
|
+
time_index_metadata = value.get_time_index_metadata()
|
|
95
|
+
uid = getattr(time_index_metadata, "uid", None)
|
|
96
|
+
if uid in (None, ""):
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"PlatformTimeIndexMetaData config values must be registered or bound "
|
|
99
|
+
"before they can be hashed."
|
|
100
|
+
)
|
|
101
|
+
return {
|
|
102
|
+
"__type__": "platform_time_index_metadata",
|
|
103
|
+
"uid": str(uid),
|
|
104
|
+
"module": value.__module__,
|
|
105
|
+
"qualname": value.__qualname__,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
73
109
|
@serialize_argument.register(datetime.datetime)
|
|
74
110
|
def _(value: datetime.datetime) -> str:
|
|
75
111
|
return value.isoformat()
|
|
@@ -180,6 +216,8 @@ def parse_dictionary_before_hashing(dictionary: dict[str, Any]) -> dict[str, Any
|
|
|
180
216
|
# The value["items"] are already serialized dicts
|
|
181
217
|
|
|
182
218
|
local_ts_dict_to_hash[key] = [v["unique_identifier"] for v in value["items"]]
|
|
219
|
+
elif value.get("__type__") == "platform_time_index_metadata":
|
|
220
|
+
local_ts_dict_to_hash[key] = value["uid"]
|
|
183
221
|
else:
|
|
184
222
|
# recursively apply hash signature
|
|
185
223
|
local_ts_dict_to_hash[key] = parse_dictionary_before_hashing(value)
|
|
@@ -327,6 +365,8 @@ class ConfigRebuilder(BaseRebuilder):
|
|
|
327
365
|
return build_model(value)
|
|
328
366
|
|
|
329
367
|
def _handle_complex_type(self, value: dict, **kwargs) -> Any:
|
|
368
|
+
if value.get("__type__") == "platform_time_index_metadata":
|
|
369
|
+
return _import_qualified_name(value["module"], value["qualname"])
|
|
330
370
|
# Special case for ORM lists within the generic complex type handler
|
|
331
371
|
if value.get("__type__") == "orm_model_list":
|
|
332
372
|
return [build_model(item) for item in value["items"]]
|
|
@@ -434,97 +474,3 @@ def create_config(
|
|
|
434
474
|
remote_initial_configuration=remote_config,
|
|
435
475
|
build_configuration_json_schema=build_configuration_json_schema,
|
|
436
476
|
)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def rebuild_and_set_from_update_hash(
|
|
440
|
-
update_hash: str,
|
|
441
|
-
data_source_uid: str,
|
|
442
|
-
set_dependencies_df: bool = False,
|
|
443
|
-
graph_depth_limit: int = 1,
|
|
444
|
-
) -> DataNode:
|
|
445
|
-
"""
|
|
446
|
-
Rebuilds a DataNode from stored configuration and attaches runtime sessions.
|
|
447
|
-
|
|
448
|
-
Args:
|
|
449
|
-
update_hash: The local hash ID of the DataNode.
|
|
450
|
-
data_source_uid: The data source UID.
|
|
451
|
-
set_dependencies_df: Whether to set the dependencies DataFrame.
|
|
452
|
-
graph_depth_limit: The depth limit for graph traversal.
|
|
453
|
-
|
|
454
|
-
Returns:
|
|
455
|
-
The rebuilt DataNode object.
|
|
456
|
-
"""
|
|
457
|
-
ts = rebuild_from_configuration(
|
|
458
|
-
update_hash=update_hash,
|
|
459
|
-
data_source=data_source_uid,
|
|
460
|
-
)
|
|
461
|
-
if set_dependencies_df:
|
|
462
|
-
ts.set_relation_tree()
|
|
463
|
-
ts._set_state_with_sessions(
|
|
464
|
-
graph_depth=0,
|
|
465
|
-
graph_depth_limit=graph_depth_limit,
|
|
466
|
-
include_client_objects=False,
|
|
467
|
-
)
|
|
468
|
-
ts.logger.debug(f"ts {update_hash} rebuilt from configuration")
|
|
469
|
-
return ts
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
@tracer.start_as_current_span("TS: Rebuild From Configuration")
|
|
473
|
-
def rebuild_from_configuration(update_hash: str, data_source: str | object) -> DataNode:
|
|
474
|
-
"""
|
|
475
|
-
Rebuilds a DataNode instance from its configuration.
|
|
476
|
-
|
|
477
|
-
Args:
|
|
478
|
-
update_hash: The local hash ID of the DataNode.
|
|
479
|
-
data_source: The data source UID or object.
|
|
480
|
-
|
|
481
|
-
Returns:
|
|
482
|
-
The rebuilt DataNode instance.
|
|
483
|
-
"""
|
|
484
|
-
import importlib
|
|
485
|
-
|
|
486
|
-
tracer_instrumentator.append_attribute_to_current_span("update_hash", update_hash)
|
|
487
|
-
|
|
488
|
-
data_source_uid = getattr(data_source, "uid", data_source)
|
|
489
|
-
data_node_update = PersistManager.UPDATE_CLASS.get_or_none(
|
|
490
|
-
update_hash=update_hash,
|
|
491
|
-
remote_table__data_source__uid=str(data_source_uid),
|
|
492
|
-
include_relations_detail=True,
|
|
493
|
-
)
|
|
494
|
-
if data_node_update is None:
|
|
495
|
-
raise ValueError(
|
|
496
|
-
f"DataNodeUpdate {update_hash!r} with data source {data_source_uid!r} was not found."
|
|
497
|
-
)
|
|
498
|
-
storage_table = data_node_update.data_node_storage
|
|
499
|
-
persist_manager = PersistManager.get_from_storage_table(
|
|
500
|
-
update_hash=update_hash,
|
|
501
|
-
storage_table=storage_table,
|
|
502
|
-
data_node_update=data_node_update,
|
|
503
|
-
)
|
|
504
|
-
try:
|
|
505
|
-
time_serie_config = persist_manager.local_build_configuration
|
|
506
|
-
except Exception as e:
|
|
507
|
-
raise e
|
|
508
|
-
|
|
509
|
-
try:
|
|
510
|
-
mod = importlib.import_module(time_serie_config["time_series_class_import_path"]["module"])
|
|
511
|
-
TimeSerieClass = getattr(
|
|
512
|
-
mod, time_serie_config["time_series_class_import_path"]["qualname"]
|
|
513
|
-
)
|
|
514
|
-
except Exception as e:
|
|
515
|
-
raise e
|
|
516
|
-
|
|
517
|
-
time_serie_class_name = time_serie_config["time_series_class_import_path"]["qualname"]
|
|
518
|
-
|
|
519
|
-
time_serie_config.pop("time_series_class_import_path")
|
|
520
|
-
time_serie_config = DeserializerManager().rebuild_serialized_config(
|
|
521
|
-
time_serie_config, time_serie_class_name=time_serie_class_name
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
# IMPORTANT:
|
|
525
|
-
# When rebuilding from stored config, ignore any ambient test namespace.
|
|
526
|
-
# If the stored config includes 'hash_namespace' (test tables), it will still be passed explicitly.
|
|
527
|
-
with disable_hash_namespace():
|
|
528
|
-
re_build_ts = TimeSerieClass(**time_serie_config)
|
|
529
|
-
|
|
530
|
-
return re_build_ts
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/run_operations.py
RENAMED
|
@@ -31,9 +31,6 @@ from mainsequence.client.dtype_codec import (
|
|
|
31
31
|
# Instrumentation and Logging
|
|
32
32
|
from mainsequence.instrumentation import TracerInstrumentator, tracer
|
|
33
33
|
|
|
34
|
-
# MetaTable DataNode core components and helpers
|
|
35
|
-
from mainsequence.meta_tables.data_nodes import build_operations
|
|
36
|
-
|
|
37
34
|
if TYPE_CHECKING:
|
|
38
35
|
from .data_nodes import DataNode
|
|
39
36
|
|
|
@@ -377,7 +374,6 @@ class UpdateRunner:
|
|
|
377
374
|
|
|
378
375
|
def _start_update(
|
|
379
376
|
self,
|
|
380
|
-
reuse_declared_dependency_instances: bool,
|
|
381
377
|
override_update_stats: BaseUpdateStatistics | None = None,
|
|
382
378
|
) -> tuple[bool, LocalUpdateResult]:
|
|
383
379
|
"""Orchestrates a single DataNode update, including pre/post routines."""
|
|
@@ -404,7 +400,6 @@ class UpdateRunner:
|
|
|
404
400
|
self.logger.debug(f"Update required for {self.ts}.")
|
|
405
401
|
update_result = self._update_local(
|
|
406
402
|
historical_update=historical_update,
|
|
407
|
-
reuse_declared_dependency_instances=reuse_declared_dependency_instances,
|
|
408
403
|
)
|
|
409
404
|
else:
|
|
410
405
|
self.logger.debug(f"Already up-to-date. Skipping update for {self.ts}.")
|
|
@@ -509,7 +504,6 @@ class UpdateRunner:
|
|
|
509
504
|
def _update_local(
|
|
510
505
|
self,
|
|
511
506
|
historical_update: Any,
|
|
512
|
-
reuse_declared_dependency_instances: bool,
|
|
513
507
|
) -> LocalUpdateResult:
|
|
514
508
|
"""
|
|
515
509
|
Calculates, validates, and persists the node update result.
|
|
@@ -519,14 +513,11 @@ class UpdateRunner:
|
|
|
519
513
|
``set_start_of_execution()`` for this run. The node-specific
|
|
520
514
|
``_execute_local_update(...)`` implementation is responsible for
|
|
521
515
|
interpreting any fields on this object.
|
|
522
|
-
|
|
523
|
-
updates reuse already-instantiated dependency nodes from the current
|
|
524
|
-
in-memory dependency graph instead of rebuilding them from backend
|
|
525
|
-
metadata.
|
|
516
|
+
Dependencies are executed from the source-declared in-memory graph.
|
|
526
517
|
"""
|
|
527
518
|
# 1. Handle dependency tree update first
|
|
528
519
|
if self.update_tree:
|
|
529
|
-
self._verify_tree_is_updated(
|
|
520
|
+
self._verify_tree_is_updated()
|
|
530
521
|
if self.update_only_tree:
|
|
531
522
|
self.logger.info(
|
|
532
523
|
f"Dependency tree for {self.ts} updated. Halting run as requested."
|
|
@@ -552,10 +543,7 @@ class UpdateRunner:
|
|
|
552
543
|
self.ts.update_statistics = us
|
|
553
544
|
|
|
554
545
|
@tracer.start_as_current_span("UpdateRunner._verify_tree_is_updated")
|
|
555
|
-
def _verify_tree_is_updated(
|
|
556
|
-
self,
|
|
557
|
-
reuse_declared_dependency_instances: bool,
|
|
558
|
-
) -> None:
|
|
546
|
+
def _verify_tree_is_updated(self) -> None:
|
|
559
547
|
"""
|
|
560
548
|
Ensures all dependencies in the tree are updated before the head node.
|
|
561
549
|
|
|
@@ -563,10 +551,9 @@ class UpdateRunner:
|
|
|
563
551
|
then delegates the update execution to either a sequential (debug) or
|
|
564
552
|
parallel (production) helper method.
|
|
565
553
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
them from backend metadata.
|
|
554
|
+
Dependencies are executed from the currently declared DataNode graph.
|
|
555
|
+
Backend dependency metadata is ordering/state only; it is not used to
|
|
556
|
+
cold-rebuild executable DataNode instances.
|
|
570
557
|
"""
|
|
571
558
|
# 1. Ensure the dependency graph is built in the backend
|
|
572
559
|
declared_dependencies = self.ts.dependencies() or {}
|
|
@@ -611,10 +598,8 @@ class UpdateRunner:
|
|
|
611
598
|
self.logger.debug("No dependencies to update.")
|
|
612
599
|
return
|
|
613
600
|
|
|
614
|
-
# 3. Build
|
|
615
|
-
update_map =
|
|
616
|
-
if self.debug_mode and reuse_declared_dependency_instances:
|
|
617
|
-
update_map = self._get_update_map(declared_dependencies, logger=self.logger)
|
|
601
|
+
# 3. Build the executable dependency map from source declarations.
|
|
602
|
+
update_map = self._get_update_map(declared_dependencies, logger=self.logger)
|
|
618
603
|
|
|
619
604
|
# 4. Delegate to the appropriate execution method
|
|
620
605
|
self.logger.debug(f"Starting update for {len(dependencies_df)} dependencies...")
|
|
@@ -638,7 +623,7 @@ class UpdateRunner:
|
|
|
638
623
|
declared_dependencies: dict[str, DataNode],
|
|
639
624
|
logger: object,
|
|
640
625
|
dependecy_map: dict | None = None,
|
|
641
|
-
) -> dict[
|
|
626
|
+
) -> dict[str, dict[str, Any]]:
|
|
642
627
|
"""
|
|
643
628
|
Obtains all DataNode objects in the dependency graph by recursively
|
|
644
629
|
calling the dependencies() method.
|
|
@@ -651,7 +636,7 @@ class UpdateRunner:
|
|
|
651
636
|
dependecy_map: An optional dictionary to store the dependency map, used for recursion.
|
|
652
637
|
|
|
653
638
|
Returns:
|
|
654
|
-
A dictionary mapping
|
|
639
|
+
A dictionary mapping update node uid to DataNode info.
|
|
655
640
|
"""
|
|
656
641
|
# Initialize the map on the first call
|
|
657
642
|
if dependecy_map is None:
|
|
@@ -660,16 +645,16 @@ class UpdateRunner:
|
|
|
660
645
|
# Get the explicitly declared dependencies, just like set_relation_tree
|
|
661
646
|
|
|
662
647
|
for name, dependency_ts in declared_dependencies.items():
|
|
663
|
-
key = (dependency_ts.update_hash, dependency_ts.data_source_uid)
|
|
664
|
-
|
|
665
|
-
# If we have already processed this node, skip it to prevent infinite loops
|
|
666
|
-
if key in dependecy_map:
|
|
667
|
-
continue
|
|
668
648
|
if dependency_ts.is_api:
|
|
669
649
|
continue
|
|
670
650
|
|
|
671
|
-
# Ensure the dependency is initialized in the persistence layer
|
|
651
|
+
# Ensure the dependency is initialized in the persistence layer.
|
|
672
652
|
_ = dependency_ts.local_persist_manager
|
|
653
|
+
key = _require_uid(dependency_ts.data_node_update, "DataNodeUpdate")
|
|
654
|
+
|
|
655
|
+
# If we have already processed this node, skip it to prevent infinite loops
|
|
656
|
+
if key in dependecy_map:
|
|
657
|
+
continue
|
|
673
658
|
|
|
674
659
|
logger.debug(f"Adding dependency '{name}' to update map.")
|
|
675
660
|
dependecy_map[key] = {"ts": dependency_ts}
|
|
@@ -686,7 +671,7 @@ class UpdateRunner:
|
|
|
686
671
|
def _execute_sequential_debug_update(
|
|
687
672
|
self,
|
|
688
673
|
dependencies_df: pd.DataFrame,
|
|
689
|
-
update_map: dict[
|
|
674
|
+
update_map: dict[str, dict],
|
|
690
675
|
) -> None:
|
|
691
676
|
"""Runs dependency updates sequentially in the same process for debugging."""
|
|
692
677
|
self.logger.info("Executing dependency updates in sequential debug mode.")
|
|
@@ -707,40 +692,34 @@ class UpdateRunner:
|
|
|
707
692
|
sorted_deps = priority_df.sort_values("number_of_upstreams", ascending=False)
|
|
708
693
|
|
|
709
694
|
for _, ts_row in sorted_deps.iterrows():
|
|
710
|
-
|
|
711
|
-
ts_to_update = None
|
|
695
|
+
update_node_uid = str(ts_row["update_node_uid"])
|
|
712
696
|
try:
|
|
713
|
-
if
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
else:
|
|
720
|
-
# If not in the map, it must be rebuilt from storage
|
|
721
|
-
ts_to_update = build_operations.rebuild_and_set_from_update_hash(
|
|
722
|
-
update_hash=key[0], data_source_uid=key[1]
|
|
697
|
+
if update_node_uid not in update_map:
|
|
698
|
+
raise DependencyUpdateError(
|
|
699
|
+
"Backend dependency metadata includes an update node that "
|
|
700
|
+
"is not declared by the current DataNode.dependencies() graph: "
|
|
701
|
+
f"update_node_uid={update_node_uid!r}."
|
|
723
702
|
)
|
|
724
703
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
704
|
+
ts_to_update = update_map[update_node_uid]["ts"]
|
|
705
|
+
refresh_update_statistics_of_deps(ts_to_update)
|
|
706
|
+
|
|
707
|
+
self.logger.debug(
|
|
708
|
+
f"Running debug update for dependency: {ts_to_update.update_hash}"
|
|
709
|
+
)
|
|
710
|
+
# Each dependency gets its own clean runner.
|
|
711
|
+
dep_runner = UpdateRunner(
|
|
712
|
+
time_serie=ts_to_update,
|
|
713
|
+
debug_mode=True,
|
|
714
|
+
update_tree=False,
|
|
715
|
+
force_update=self.force_update,
|
|
716
|
+
remote_scheduler=self.scheduler,
|
|
717
|
+
)
|
|
718
|
+
dep_runner._setup_scheduler()
|
|
719
|
+
|
|
720
|
+
dep_runner._start_update()
|
|
742
721
|
except Exception as e:
|
|
743
|
-
self.logger.exception(f"Failed to update dependency {
|
|
722
|
+
self.logger.exception(f"Failed to update dependency {update_node_uid}")
|
|
744
723
|
raise e # Re-raise to halt the entire process on failure
|
|
745
724
|
|
|
746
725
|
# refresh update statistics of direct dependencies
|
|
@@ -803,7 +782,6 @@ class UpdateRunner:
|
|
|
803
782
|
|
|
804
783
|
# 5. Trigger the core update process
|
|
805
784
|
error_on_last_update, update_result = self._start_update(
|
|
806
|
-
reuse_declared_dependency_instances=True,
|
|
807
785
|
override_update_stats=self.override_update_stats,
|
|
808
786
|
)
|
|
809
787
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Mapping, Sequence
|
|
4
|
-
from typing import Any, ClassVar
|
|
4
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
from mainsequence.client.dtype_codec import (
|
|
@@ -21,6 +21,9 @@ from mainsequence.client.models_metatables import (
|
|
|
21
21
|
|
|
22
22
|
from .hashing import build_meta_table_configured_storage_hash, build_meta_table_storage_hash
|
|
23
23
|
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from mainsequence.client.models_metatables import TimeIndexMetaData
|
|
26
|
+
|
|
24
27
|
DEFAULT_PLATFORM_MANAGED_PROVISIONING = {
|
|
25
28
|
"create_table": True,
|
|
26
29
|
"if_not_exists": True,
|
|
@@ -351,19 +354,19 @@ class PlatformTimeIndexMetaData(PlatformManagedMetaTable):
|
|
|
351
354
|
ordinary non-null table columns.
|
|
352
355
|
"""
|
|
353
356
|
|
|
354
|
-
__time_index_metadata__: ClassVar[
|
|
357
|
+
__time_index_metadata__: ClassVar[TimeIndexMetaData | None] = None
|
|
355
358
|
|
|
356
359
|
if _sqlalchemy_declared_attr is not None:
|
|
357
360
|
__mapper_args__ = _sqlalchemy_declared_attr.directive(_time_index_mapper_args)
|
|
358
361
|
|
|
359
362
|
@classmethod
|
|
360
|
-
def bind_meta_table(cls, meta_table:
|
|
363
|
+
def bind_meta_table(cls, meta_table: TimeIndexMetaData) -> TimeIndexMetaData:
|
|
361
364
|
bound = super().bind_meta_table(meta_table)
|
|
362
365
|
cls.__time_index_metadata__ = bound
|
|
363
366
|
return bound
|
|
364
367
|
|
|
365
368
|
@classmethod
|
|
366
|
-
def get_time_index_metadata(cls) ->
|
|
369
|
+
def get_time_index_metadata(cls) -> TimeIndexMetaData | None:
|
|
367
370
|
return getattr(cls, "__time_index_metadata__", None)
|
|
368
371
|
|
|
369
372
|
@classmethod
|
|
@@ -10,8 +10,11 @@ os.environ.setdefault("MAINSEQUENCE_ACCESS_TOKEN", "test-access-token")
|
|
|
10
10
|
os.environ.setdefault("MAINSEQUENCE_REFRESH_TOKEN", "test-refresh-token")
|
|
11
11
|
|
|
12
12
|
import mainsequence.meta_tables.data_nodes.build_operations as build_operations
|
|
13
|
+
from mainsequence.client.models_metatables import TimeIndexMetaData
|
|
13
14
|
from mainsequence.meta_tables import (
|
|
15
|
+
DataNode,
|
|
14
16
|
DataNodeConfiguration,
|
|
17
|
+
PlatformTimeIndexMetaData,
|
|
15
18
|
RecordDefinition,
|
|
16
19
|
)
|
|
17
20
|
|
|
@@ -170,6 +173,44 @@ def test_offset_start_changes_update_hash(monkeypatch):
|
|
|
170
173
|
assert storage_hash_a != storage_hash_b
|
|
171
174
|
|
|
172
175
|
|
|
176
|
+
def test_platform_time_index_metadata_config_hashes_by_bound_metadata_uid(monkeypatch):
|
|
177
|
+
monkeypatch.setattr(build_operations, "POD_PROJECT", None, raising=False)
|
|
178
|
+
|
|
179
|
+
class StorageA(PlatformTimeIndexMetaData):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
class StorageB(PlatformTimeIndexMetaData):
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
class StorageC(PlatformTimeIndexMetaData):
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
StorageA.bind_meta_table(TimeIndexMetaData.model_construct(uid="storage-uid-a"))
|
|
189
|
+
StorageB.bind_meta_table(TimeIndexMetaData.model_construct(uid="storage-uid-a"))
|
|
190
|
+
StorageC.bind_meta_table(TimeIndexMetaData.model_construct(uid="storage-uid-c"))
|
|
191
|
+
|
|
192
|
+
class NodeConfig(BaseModel):
|
|
193
|
+
dependency_storage: type[PlatformTimeIndexMetaData]
|
|
194
|
+
|
|
195
|
+
hashes_a = _hashes(NodeConfig(dependency_storage=StorageA))
|
|
196
|
+
hashes_b = _hashes(NodeConfig(dependency_storage=StorageB))
|
|
197
|
+
hashes_c = _hashes(NodeConfig(dependency_storage=StorageC))
|
|
198
|
+
|
|
199
|
+
assert hashes_a == hashes_b
|
|
200
|
+
assert hashes_a != hashes_c
|
|
201
|
+
|
|
202
|
+
config = build_operations.create_config(
|
|
203
|
+
ts_class_name="StorageConfigNode",
|
|
204
|
+
kwargs={"config": NodeConfig(dependency_storage=StorageA)},
|
|
205
|
+
)
|
|
206
|
+
assert (
|
|
207
|
+
config.local_initial_configuration["config"]["serialized_model"]["dependency_storage"][
|
|
208
|
+
"uid"
|
|
209
|
+
]
|
|
210
|
+
== "storage-uid-a"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
173
214
|
def test_plain_dict_with_pydantic_model_import_path_key_is_not_treated_as_wrapper(monkeypatch):
|
|
174
215
|
monkeypatch.setattr(build_operations, "POD_PROJECT", None, raising=False)
|
|
175
216
|
|
|
@@ -486,58 +486,6 @@ def test_build_operations_api_node_reference_serialization_uses_data_source_uid(
|
|
|
486
486
|
assert "is_api_time_serie_pickled" not in payload
|
|
487
487
|
|
|
488
488
|
|
|
489
|
-
def
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
class FakeLogger:
|
|
493
|
-
def debug(self, message):
|
|
494
|
-
calls.append(("debug", message))
|
|
495
|
-
|
|
496
|
-
class FakeDataNode:
|
|
497
|
-
logger = FakeLogger()
|
|
498
|
-
|
|
499
|
-
def set_relation_tree(self):
|
|
500
|
-
calls.append(("set_relation_tree",))
|
|
501
|
-
|
|
502
|
-
def _set_state_with_sessions(
|
|
503
|
-
self,
|
|
504
|
-
*,
|
|
505
|
-
graph_depth,
|
|
506
|
-
graph_depth_limit,
|
|
507
|
-
include_client_objects,
|
|
508
|
-
):
|
|
509
|
-
calls.append(
|
|
510
|
-
(
|
|
511
|
-
"set_state_with_sessions",
|
|
512
|
-
graph_depth,
|
|
513
|
-
graph_depth_limit,
|
|
514
|
-
include_client_objects,
|
|
515
|
-
)
|
|
516
|
-
)
|
|
517
|
-
|
|
518
|
-
fake_ts = FakeDataNode()
|
|
519
|
-
|
|
520
|
-
def fake_rebuild_from_configuration(update_hash, data_source):
|
|
521
|
-
calls.append(("rebuild_from_configuration", update_hash, data_source))
|
|
522
|
-
return fake_ts
|
|
523
|
-
|
|
524
|
-
monkeypatch.setattr(
|
|
525
|
-
build_operations,
|
|
526
|
-
"rebuild_from_configuration",
|
|
527
|
-
fake_rebuild_from_configuration,
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
result = build_operations.rebuild_and_set_from_update_hash(
|
|
531
|
-
update_hash="update-hash-1",
|
|
532
|
-
data_source_uid=DATA_SOURCE_UID,
|
|
533
|
-
set_dependencies_df=True,
|
|
534
|
-
graph_depth_limit=3,
|
|
535
|
-
)
|
|
536
|
-
|
|
537
|
-
assert result is fake_ts
|
|
538
|
-
assert calls == [
|
|
539
|
-
("rebuild_from_configuration", "update-hash-1", DATA_SOURCE_UID),
|
|
540
|
-
("set_relation_tree",),
|
|
541
|
-
("set_state_with_sessions", 0, 3, False),
|
|
542
|
-
("debug", "ts update-hash-1 rebuilt from configuration"),
|
|
543
|
-
]
|
|
489
|
+
def test_build_operations_does_not_expose_data_node_cold_rebuild_helpers():
|
|
490
|
+
assert not hasattr(build_operations, "rebuild_from_configuration")
|
|
491
|
+
assert not hasattr(build_operations, "rebuild_and_set_from_update_hash")
|
|
@@ -16,6 +16,9 @@ class _Logger:
|
|
|
16
16
|
def info(self, *_args, **_kwargs):
|
|
17
17
|
return None
|
|
18
18
|
|
|
19
|
+
def exception(self, *_args, **_kwargs):
|
|
20
|
+
return None
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
class _PersistManager:
|
|
21
24
|
def __init__(self, data_node_update):
|
|
@@ -30,6 +33,9 @@ class _PersistManager:
|
|
|
30
33
|
def set_data_node_update_lazy(self, include_relations_detail=False):
|
|
31
34
|
self.include_relations_detail = include_relations_detail
|
|
32
35
|
|
|
36
|
+
def get_update_statistics_for_table(self):
|
|
37
|
+
return {"updated": self.data_node_update.uid}
|
|
38
|
+
|
|
33
39
|
|
|
34
40
|
class _Scheduler:
|
|
35
41
|
uid = "scheduler-uid"
|
|
@@ -172,9 +178,13 @@ def test_update_runner_verify_tree_uses_dependency_uids(monkeypatch):
|
|
|
172
178
|
depth_df=depth_df,
|
|
173
179
|
dependencies_df=depth_df.copy(),
|
|
174
180
|
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
181
|
+
dependency = SimpleNamespace(
|
|
182
|
+
is_api=False,
|
|
183
|
+
data_node_update=dependency_update,
|
|
184
|
+
local_persist_manager=_PersistManager(dependency_update),
|
|
185
|
+
dependencies=lambda: {},
|
|
186
|
+
)
|
|
187
|
+
ts.dependencies = lambda: {"dependency": dependency}
|
|
178
188
|
runner = run_operations.UpdateRunner(ts, debug_mode=True)
|
|
179
189
|
executed = {}
|
|
180
190
|
monkeypatch.setattr(
|
|
@@ -188,10 +198,81 @@ def test_update_runner_verify_tree_uses_dependency_uids(monkeypatch):
|
|
|
188
198
|
),
|
|
189
199
|
)
|
|
190
200
|
|
|
191
|
-
runner._verify_tree_is_updated(
|
|
201
|
+
runner._verify_tree_is_updated()
|
|
192
202
|
|
|
193
203
|
assert patch_calls == []
|
|
194
204
|
assert executed["update_node_uids"] == ["dep-uid"]
|
|
205
|
+
assert list(executed["update_map"]) == ["dep-uid"]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_sequential_debug_update_uses_update_node_uid_without_data_source_uid(monkeypatch):
|
|
209
|
+
dependency_update = _update("dep-uid")
|
|
210
|
+
dependency = SimpleNamespace(
|
|
211
|
+
is_api=False,
|
|
212
|
+
data_node_update=dependency_update,
|
|
213
|
+
update_hash="dep-hash",
|
|
214
|
+
local_persist_manager=_PersistManager(dependency_update),
|
|
215
|
+
dependencies=lambda: {},
|
|
216
|
+
logger=_Logger(),
|
|
217
|
+
)
|
|
218
|
+
dependencies_df = pd.DataFrame(
|
|
219
|
+
[
|
|
220
|
+
{
|
|
221
|
+
"update_node_uid": "dep-uid",
|
|
222
|
+
"update_hash": "dep-hash",
|
|
223
|
+
"update_priority": 0,
|
|
224
|
+
"number_of_upstreams": 0,
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
)
|
|
228
|
+
ts = _time_series()
|
|
229
|
+
ts.dependencies = lambda: {"dependency": dependency}
|
|
230
|
+
runner = run_operations.UpdateRunner(ts, debug_mode=True)
|
|
231
|
+
runner.scheduler = _Scheduler()
|
|
232
|
+
started = []
|
|
233
|
+
|
|
234
|
+
monkeypatch.setattr(
|
|
235
|
+
run_operations.UpdateRunner,
|
|
236
|
+
"_setup_scheduler",
|
|
237
|
+
lambda self: started.append(("scheduler", self.ts.data_node_update.uid)),
|
|
238
|
+
)
|
|
239
|
+
monkeypatch.setattr(
|
|
240
|
+
run_operations.UpdateRunner,
|
|
241
|
+
"_start_update",
|
|
242
|
+
lambda self, **_kwargs: started.append(("update", self.ts.data_node_update.uid)),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
runner._execute_sequential_debug_update(
|
|
246
|
+
dependencies_df=dependencies_df,
|
|
247
|
+
update_map={"dep-uid": {"ts": dependency}},
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
assert started == [("scheduler", "dep-uid"), ("update", "dep-uid")]
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def test_sequential_debug_update_rejects_backend_dependency_not_declared():
|
|
254
|
+
dependencies_df = pd.DataFrame(
|
|
255
|
+
[
|
|
256
|
+
{
|
|
257
|
+
"update_node_uid": "stale-dep-uid",
|
|
258
|
+
"update_hash": "stale-dep-hash",
|
|
259
|
+
"update_priority": 0,
|
|
260
|
+
"number_of_upstreams": 0,
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
)
|
|
264
|
+
runner = run_operations.UpdateRunner(_time_series(), debug_mode=True)
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
runner._execute_sequential_debug_update(
|
|
268
|
+
dependencies_df=dependencies_df,
|
|
269
|
+
update_map={},
|
|
270
|
+
)
|
|
271
|
+
except run_operations.DependencyUpdateError as exc:
|
|
272
|
+
assert "not declared by the current DataNode.dependencies() graph" in str(exc)
|
|
273
|
+
assert "stale-dep-uid" in str(exc)
|
|
274
|
+
else:
|
|
275
|
+
raise AssertionError("Expected DependencyUpdateError")
|
|
195
276
|
|
|
196
277
|
|
|
197
278
|
def test_data_node_update_dependency_priority_normalizes_uid_columns(monkeypatch):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/command_center/connections/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/dashboards/streamlit/SKILL.md
RENAMED
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/data_access/exploration/SKILL.md
RENAMED
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md
RENAMED
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/app_component.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/command_center/workspace_snapshot.py
RENAMED
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/data_sources_interfaces/__init__.py
RENAMED
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/data_sources_interfaces/duckdb.py
RENAMED
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/client/data_sources_interfaces/sqlite.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/namespacing.py
RENAMED
|
File without changes
|
{mainsequence-4.1.1 → mainsequence-4.1.2}/mainsequence/meta_tables/data_nodes/persist_managers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|