mainsequence 4.1.2__tar.gz → 4.1.3__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.2 → mainsequence-4.1.3}/PKG-INFO +1 -1
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/dtype_codec.py +20 -19
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/models_metatables.py +75 -42
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/utils.py +13 -4
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/build_operations.py +6 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/persist_managers.py +11 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-4.1.2 → mainsequence-4.1.3}/pyproject.toml +1 -1
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_build_operations_hashing.py +34 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_data_node_update_flow.py +133 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_run_configuration.py +64 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/LICENSE +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/README.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/__main__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/cli.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/base.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/client.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/compute_validation.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/defaults.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/logconf.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/compiled_sql.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/config.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/configuration_models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/utils.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/setup.cfg +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_cli.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_client.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_filter_normalization.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_logconf.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_meta_tables_client_models.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_secret_client_model.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.1.2 → mainsequence-4.1.3}/tests/test_workspace_snapshot.py +0 -0
|
@@ -163,9 +163,7 @@ def record_definitions_to_column_dtypes_map(
|
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
if duplicate_columns:
|
|
166
|
-
raise ValueError(
|
|
167
|
-
f"Duplicate DataNode record column names: {sorted(duplicate_columns)}"
|
|
168
|
-
)
|
|
166
|
+
raise ValueError(f"Duplicate DataNode record column names: {sorted(duplicate_columns)}")
|
|
169
167
|
|
|
170
168
|
return column_dtypes_map
|
|
171
169
|
|
|
@@ -247,10 +245,14 @@ def token_to_pandas_series(
|
|
|
247
245
|
is_time_index: bool = False,
|
|
248
246
|
nullable: bool = True,
|
|
249
247
|
) -> pd.Series:
|
|
250
|
-
normalized =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
248
|
+
normalized = (
|
|
249
|
+
TIMESTAMP_TZ
|
|
250
|
+
if is_time_index
|
|
251
|
+
else normalize_dtype_token(
|
|
252
|
+
token,
|
|
253
|
+
remote=False,
|
|
254
|
+
allow_naive_datetime=True,
|
|
255
|
+
)
|
|
254
256
|
)
|
|
255
257
|
if normalized == TIMESTAMP_TZ:
|
|
256
258
|
return pd.to_datetime(series, errors="coerce", utc=True)
|
|
@@ -319,17 +321,15 @@ def _serialize_timestamp_tz_value(value: Any) -> str | None:
|
|
|
319
321
|
raise ValueError(
|
|
320
322
|
f"Remote datetime value {value!r} must include an explicit timezone offset or Z."
|
|
321
323
|
)
|
|
322
|
-
if isinstance(value, (pd.Timestamp, datetime.datetime)) and not _timestamp_is_timezone_aware(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
)
|
|
324
|
+
if isinstance(value, (pd.Timestamp, datetime.datetime)) and not _timestamp_is_timezone_aware(
|
|
325
|
+
value
|
|
326
|
+
):
|
|
327
|
+
raise ValueError(f"Remote datetime value {value!r} must be timezone-aware.")
|
|
326
328
|
timestamp = pd.to_datetime(value, errors="raise", utc=True)
|
|
327
329
|
if not isinstance(timestamp, pd.Timestamp):
|
|
328
330
|
timestamp = pd.Timestamp(timestamp).tz_convert("UTC")
|
|
329
331
|
if timestamp.tzinfo is None:
|
|
330
|
-
raise ValueError(
|
|
331
|
-
f"Remote datetime value {value!r} must be timezone-aware."
|
|
332
|
-
)
|
|
332
|
+
raise ValueError(f"Remote datetime value {value!r} must be timezone-aware.")
|
|
333
333
|
timestamp = timestamp.tz_convert("UTC")
|
|
334
334
|
return timestamp.isoformat().replace("+00:00", "Z")
|
|
335
335
|
|
|
@@ -375,11 +375,8 @@ def prepare_dataframe_for_remote_write(
|
|
|
375
375
|
if column_name not in prepared.columns:
|
|
376
376
|
continue
|
|
377
377
|
normalized = normalize_dtype_token(token, remote=True)
|
|
378
|
-
if normalized not in {DATE, TIMESTAMP_TZ}:
|
|
379
|
-
continue
|
|
380
378
|
prepared[column_name] = [
|
|
381
|
-
serialize_remote_value(value, normalized)
|
|
382
|
-
for value in prepared[column_name].tolist()
|
|
379
|
+
serialize_remote_value(value, normalized) for value in prepared[column_name].tolist()
|
|
383
380
|
]
|
|
384
381
|
return prepared
|
|
385
382
|
|
|
@@ -459,7 +456,11 @@ def sqlalchemy_type_to_token(column_type: Any, *, remote: bool = True) -> str:
|
|
|
459
456
|
"by the remote TS Manager contract."
|
|
460
457
|
)
|
|
461
458
|
return LOCAL_DATETIME_NAIVE
|
|
462
|
-
if
|
|
459
|
+
if (
|
|
460
|
+
timezone is True
|
|
461
|
+
or "with time zone" in normalized_backend
|
|
462
|
+
or "timestamptz" in normalized_backend
|
|
463
|
+
):
|
|
463
464
|
return TIMESTAMP_TZ
|
|
464
465
|
if remote:
|
|
465
466
|
raise ValueError(
|
|
@@ -1244,9 +1244,6 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
|
|
|
1244
1244
|
)
|
|
1245
1245
|
|
|
1246
1246
|
|
|
1247
|
-
LOGICAL_COLUMN_DTYPES_ATTR = "mainsequence_column_dtypes_map"
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
1247
|
def _warn_legacy_compat(message: str, *, stacklevel: int = 3) -> None:
|
|
1251
1248
|
warnings.warn(
|
|
1252
1249
|
f"Deprecated TDAG compatibility path: {message}",
|
|
@@ -2143,7 +2140,6 @@ class DataNodeUpdate(TableUpdateNode, BaseObjectOrm):
|
|
|
2143
2140
|
remote_dtypes: bool = True,
|
|
2144
2141
|
allow_naive_datetime: bool = False,
|
|
2145
2142
|
):
|
|
2146
|
-
logical_column_dtypes_map = data_frame.attrs.get(LOGICAL_COLUMN_DTYPES_ATTR)
|
|
2147
2143
|
record_column_dtypes_map = record_definitions_to_column_dtypes_map(
|
|
2148
2144
|
records,
|
|
2149
2145
|
remote=remote_dtypes,
|
|
@@ -2178,40 +2174,6 @@ class DataNodeUpdate(TableUpdateNode, BaseObjectOrm):
|
|
|
2178
2174
|
"DataNode records declare columns not present in the DataFrame: "
|
|
2179
2175
|
f"{missing_record_columns}"
|
|
2180
2176
|
)
|
|
2181
|
-
if logical_column_dtypes_map is not None:
|
|
2182
|
-
logical_column_dtypes_map = normalize_column_dtypes_map(
|
|
2183
|
-
logical_column_dtypes_map,
|
|
2184
|
-
remote=remote_dtypes,
|
|
2185
|
-
allow_naive_datetime=allow_naive_datetime,
|
|
2186
|
-
)
|
|
2187
|
-
missing_logical_columns = [
|
|
2188
|
-
column_name
|
|
2189
|
-
for column_name in logical_column_dtypes_map
|
|
2190
|
-
if column_name not in data_frame.columns
|
|
2191
|
-
]
|
|
2192
|
-
if missing_logical_columns:
|
|
2193
|
-
raise ValueError(
|
|
2194
|
-
"Logical column dtype contract contains columns not present "
|
|
2195
|
-
f"in the DataFrame: {missing_logical_columns}"
|
|
2196
|
-
)
|
|
2197
|
-
conflicting_declared_dtypes = {
|
|
2198
|
-
column_name: {
|
|
2199
|
-
"record_dtype": record_column_dtypes_map[column_name],
|
|
2200
|
-
"logical_dtype": logical_column_dtypes_map[column_name],
|
|
2201
|
-
}
|
|
2202
|
-
for column_name in logical_column_dtypes_map
|
|
2203
|
-
if (
|
|
2204
|
-
column_name in record_column_dtypes_map
|
|
2205
|
-
and logical_column_dtypes_map[column_name]
|
|
2206
|
-
!= record_column_dtypes_map[column_name]
|
|
2207
|
-
)
|
|
2208
|
-
}
|
|
2209
|
-
if conflicting_declared_dtypes:
|
|
2210
|
-
raise ValueError(
|
|
2211
|
-
"Logical column dtype contract conflicts with DataNode records: "
|
|
2212
|
-
f"{conflicting_declared_dtypes}"
|
|
2213
|
-
)
|
|
2214
|
-
column_dtypes_map.update(logical_column_dtypes_map)
|
|
2215
2177
|
column_dtypes_map.update(record_column_dtypes_map)
|
|
2216
2178
|
|
|
2217
2179
|
data_frame = data_frame.replace({np.nan: None})
|
|
@@ -3970,10 +3932,70 @@ class UpdateStatistics(BaseUpdateStatistics):
|
|
|
3970
3932
|
def _assign_nested_coordinate(root: dict[Any, Any], keys: list[Any], value: Any) -> None:
|
|
3971
3933
|
if not keys:
|
|
3972
3934
|
return
|
|
3935
|
+
normalized_keys = [_normalize_update_stat_key(key) for key in keys]
|
|
3973
3936
|
sub = root
|
|
3974
|
-
for key in
|
|
3975
|
-
|
|
3976
|
-
|
|
3937
|
+
for key in normalized_keys[:-1]:
|
|
3938
|
+
existing = sub.setdefault(key, {})
|
|
3939
|
+
if not isinstance(existing, dict):
|
|
3940
|
+
raise ValueError("Update statistics coordinate keys collide after JSON normalization.")
|
|
3941
|
+
sub = existing
|
|
3942
|
+
final_key = normalized_keys[-1]
|
|
3943
|
+
if final_key in sub:
|
|
3944
|
+
raise ValueError("Update statistics coordinate keys collide after JSON normalization.")
|
|
3945
|
+
sub[final_key] = value
|
|
3946
|
+
|
|
3947
|
+
|
|
3948
|
+
def _normalize_update_stat_key(key: Any) -> str:
|
|
3949
|
+
if isinstance(key, str):
|
|
3950
|
+
return key
|
|
3951
|
+
if isinstance(key, np.generic):
|
|
3952
|
+
key = key.item()
|
|
3953
|
+
if isinstance(key, datetime.datetime):
|
|
3954
|
+
value = serialize_to_json({"_": key})["_"]
|
|
3955
|
+
return str(value)
|
|
3956
|
+
if isinstance(key, datetime.date):
|
|
3957
|
+
return key.isoformat()
|
|
3958
|
+
if key is None:
|
|
3959
|
+
return "null"
|
|
3960
|
+
if isinstance(key, bool):
|
|
3961
|
+
return "true" if key else "false"
|
|
3962
|
+
if isinstance(key, int | float):
|
|
3963
|
+
try:
|
|
3964
|
+
return json.dumps(key, allow_nan=False)
|
|
3965
|
+
except (TypeError, ValueError):
|
|
3966
|
+
return str(key)
|
|
3967
|
+
|
|
3968
|
+
normalized = serialize_to_json({key: None})
|
|
3969
|
+
normalized_key = next(iter(normalized.keys()))
|
|
3970
|
+
if normalized_key is None:
|
|
3971
|
+
return "null"
|
|
3972
|
+
if isinstance(normalized_key, bool):
|
|
3973
|
+
return "true" if normalized_key else "false"
|
|
3974
|
+
if isinstance(normalized_key, int | float):
|
|
3975
|
+
try:
|
|
3976
|
+
return json.dumps(normalized_key, allow_nan=False)
|
|
3977
|
+
except (TypeError, ValueError):
|
|
3978
|
+
return str(normalized_key)
|
|
3979
|
+
return str(normalized_key)
|
|
3980
|
+
|
|
3981
|
+
|
|
3982
|
+
def _normalize_update_stat_mapping_keys(value: Any) -> Any:
|
|
3983
|
+
if isinstance(value, Mapping):
|
|
3984
|
+
normalized: dict[str, Any] = {}
|
|
3985
|
+
for key, item in value.items():
|
|
3986
|
+
normalized_key = _normalize_update_stat_key(key)
|
|
3987
|
+
if normalized_key in normalized:
|
|
3988
|
+
raise ValueError(
|
|
3989
|
+
"Update statistics coordinate keys collide after JSON normalization: "
|
|
3990
|
+
f"{normalized_key!r}."
|
|
3991
|
+
)
|
|
3992
|
+
normalized[normalized_key] = _normalize_update_stat_mapping_keys(item)
|
|
3993
|
+
return normalized
|
|
3994
|
+
if isinstance(value, list):
|
|
3995
|
+
return [_normalize_update_stat_mapping_keys(item) for item in value]
|
|
3996
|
+
if isinstance(value, tuple):
|
|
3997
|
+
return [_normalize_update_stat_mapping_keys(item) for item in value]
|
|
3998
|
+
return value
|
|
3977
3999
|
|
|
3978
4000
|
|
|
3979
4001
|
def get_index_progress_chunk_stats(chunk_df, time_index_name, index_names):
|
|
@@ -4081,6 +4103,11 @@ class LastUpdateMultiIndexStatsPayload(BaseModel):
|
|
|
4081
4103
|
index_progress: dict[str, Any] = Field(default_factory=dict)
|
|
4082
4104
|
index_min: dict[str, Any] = Field(default_factory=dict)
|
|
4083
4105
|
|
|
4106
|
+
@model_validator(mode="before")
|
|
4107
|
+
@classmethod
|
|
4108
|
+
def _normalize_mapping_keys(cls, value: Any) -> Any:
|
|
4109
|
+
return _normalize_update_stat_mapping_keys(value)
|
|
4110
|
+
|
|
4084
4111
|
def to_payload(self) -> dict[str, Any]:
|
|
4085
4112
|
return {
|
|
4086
4113
|
"_GLOBAL_": self.global_stats,
|
|
@@ -4099,6 +4126,11 @@ class LastUpdateIndexTimePayload(BaseModel):
|
|
|
4099
4126
|
multi_index_stats: LastUpdateMultiIndexStatsPayload | None = None
|
|
4100
4127
|
multi_index_column_stats: dict[str, Any] | None = Field(default_factory=dict)
|
|
4101
4128
|
|
|
4129
|
+
@model_validator(mode="before")
|
|
4130
|
+
@classmethod
|
|
4131
|
+
def _normalize_mapping_keys(cls, value: Any) -> Any:
|
|
4132
|
+
return _normalize_update_stat_mapping_keys(value)
|
|
4133
|
+
|
|
4102
4134
|
@model_validator(mode="after")
|
|
4103
4135
|
def _validate_shape(self):
|
|
4104
4136
|
top_level_progress_keys = [
|
|
@@ -4164,7 +4196,8 @@ def build_last_update_index_time_payload(
|
|
|
4164
4196
|
}
|
|
4165
4197
|
)
|
|
4166
4198
|
|
|
4167
|
-
|
|
4199
|
+
normalized_payload = _normalize_update_stat_mapping_keys(raw_payload)
|
|
4200
|
+
return LastUpdateIndexTimePayload.model_validate(normalized_payload).to_nested_payload()
|
|
4168
4201
|
|
|
4169
4202
|
|
|
4170
4203
|
class HistoricalUpdateRecord:
|
|
@@ -9,11 +9,11 @@ import socket
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import threading
|
|
11
11
|
import time
|
|
12
|
-
import uuid
|
|
13
12
|
from dataclasses import dataclass, field
|
|
14
13
|
from decimal import Decimal
|
|
15
14
|
from enum import Enum
|
|
16
15
|
from typing import TypedDict
|
|
16
|
+
from uuid import UUID, getnode
|
|
17
17
|
|
|
18
18
|
import psutil
|
|
19
19
|
import pytz
|
|
@@ -761,6 +761,9 @@ def serialize_to_json(kwargs):
|
|
|
761
761
|
if isinstance(v, Decimal):
|
|
762
762
|
return str(v)
|
|
763
763
|
|
|
764
|
+
if isinstance(v, UUID):
|
|
765
|
+
return str(v)
|
|
766
|
+
|
|
764
767
|
if isinstance(v, datetime.datetime):
|
|
765
768
|
dt = v
|
|
766
769
|
if dt.tzinfo is None:
|
|
@@ -776,13 +779,19 @@ def serialize_to_json(kwargs):
|
|
|
776
779
|
return v.model_dump()
|
|
777
780
|
|
|
778
781
|
if isinstance(v, dict):
|
|
779
|
-
return {k: to_jsonable(x) for k, x in v.items()}
|
|
782
|
+
return {to_json_key(k): to_jsonable(x) for k, x in v.items()}
|
|
780
783
|
if isinstance(v, (list, tuple)):
|
|
781
784
|
return [to_jsonable(x) for x in v]
|
|
782
785
|
|
|
783
786
|
return v
|
|
784
787
|
|
|
785
|
-
|
|
788
|
+
def to_json_key(value):
|
|
789
|
+
key = to_jsonable(value)
|
|
790
|
+
if key is None or isinstance(key, str | int | float | bool):
|
|
791
|
+
return key
|
|
792
|
+
return str(key)
|
|
793
|
+
|
|
794
|
+
return {to_json_key(k): to_jsonable(v) for k, v in kwargs.items()}
|
|
786
795
|
|
|
787
796
|
|
|
788
797
|
def _linux_machine_id() -> str | None:
|
|
@@ -839,7 +848,7 @@ def bios_uuid() -> str:
|
|
|
839
848
|
return mid
|
|
840
849
|
|
|
841
850
|
# Tier 4 – MAC address (uuid.getnode). Always available.
|
|
842
|
-
return f"{
|
|
851
|
+
return f"{getnode():012x}"
|
|
843
852
|
|
|
844
853
|
|
|
845
854
|
def _install_retry_adapters_in_place(
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/build_operations.py
RENAMED
|
@@ -13,6 +13,7 @@ from enum import Enum
|
|
|
13
13
|
from functools import singledispatch
|
|
14
14
|
from types import SimpleNamespace
|
|
15
15
|
from typing import TYPE_CHECKING, Any
|
|
16
|
+
from uuid import UUID
|
|
16
17
|
|
|
17
18
|
from pydantic import BaseModel
|
|
18
19
|
|
|
@@ -111,6 +112,11 @@ def _(value: datetime.datetime) -> str:
|
|
|
111
112
|
return value.isoformat()
|
|
112
113
|
|
|
113
114
|
|
|
115
|
+
@serialize_argument.register(UUID)
|
|
116
|
+
def _(value: UUID) -> str:
|
|
117
|
+
return str(value)
|
|
118
|
+
|
|
119
|
+
|
|
114
120
|
@serialize_argument.register(BaseModel)
|
|
115
121
|
def _(value: BaseModel) -> dict[str, Any]:
|
|
116
122
|
"""Serialization logic for any Pydantic BaseModel."""
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/persist_managers.py
RENAMED
|
@@ -438,11 +438,22 @@ class BasePersistManager:
|
|
|
438
438
|
data=temp_df,
|
|
439
439
|
data_source=self.data_source,
|
|
440
440
|
overwrite=overwrite,
|
|
441
|
+
source_table_schema=self._source_table_schema(),
|
|
441
442
|
)
|
|
442
443
|
|
|
443
444
|
persisted = True
|
|
444
445
|
return persisted
|
|
445
446
|
|
|
447
|
+
def _source_table_schema(self) -> dict[str, Any]:
|
|
448
|
+
time_index_name, index_names, column_dtypes_map = (
|
|
449
|
+
self.storage_metadata._require_time_indexed_table_contract()
|
|
450
|
+
)
|
|
451
|
+
return {
|
|
452
|
+
"time_index_name": time_index_name,
|
|
453
|
+
"index_names": list(index_names),
|
|
454
|
+
"column_dtypes_map": dict(column_dtypes_map),
|
|
455
|
+
}
|
|
456
|
+
|
|
446
457
|
def get_update_statistics_for_table(self) -> UpdateStatistics:
|
|
447
458
|
return self.storage_metadata.get_data_updates()
|
|
448
459
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
4
|
import os
|
|
5
|
+
import uuid
|
|
5
6
|
|
|
6
7
|
import pytest
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
@@ -24,6 +25,10 @@ def _hashes(payload):
|
|
|
24
25
|
return build_operations.hash_signature({"config": serialized_payload})
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
class UUIDNodeConfig(BaseModel):
|
|
29
|
+
account_uid: uuid.UUID
|
|
30
|
+
|
|
31
|
+
|
|
27
32
|
def test_create_config_crops_hash_prefix_to_postgres_identifier_limit(monkeypatch):
|
|
28
33
|
monkeypatch.setattr(build_operations, "POD_PROJECT", None, raising=False)
|
|
29
34
|
|
|
@@ -211,6 +216,35 @@ def test_platform_time_index_metadata_config_hashes_by_bound_metadata_uid(monkey
|
|
|
211
216
|
)
|
|
212
217
|
|
|
213
218
|
|
|
219
|
+
def test_uuid_config_values_serialize_hash_and_rebuild(monkeypatch):
|
|
220
|
+
monkeypatch.setattr(build_operations, "POD_PROJECT", None, raising=False)
|
|
221
|
+
|
|
222
|
+
account_uid = uuid.UUID("00000000-0000-4000-8000-000000000001")
|
|
223
|
+
other_account_uid = uuid.UUID("00000000-0000-4000-8000-000000000002")
|
|
224
|
+
|
|
225
|
+
serialized = build_operations.serialize_argument(UUIDNodeConfig(account_uid=account_uid))
|
|
226
|
+
assert serialized["serialized_model"]["account_uid"] == str(account_uid)
|
|
227
|
+
|
|
228
|
+
hashes_a = _hashes(UUIDNodeConfig(account_uid=account_uid))
|
|
229
|
+
hashes_b = _hashes(UUIDNodeConfig(account_uid=account_uid))
|
|
230
|
+
hashes_c = _hashes(UUIDNodeConfig(account_uid=other_account_uid))
|
|
231
|
+
|
|
232
|
+
assert hashes_a == hashes_b
|
|
233
|
+
assert hashes_a != hashes_c
|
|
234
|
+
|
|
235
|
+
config = build_operations.create_config(
|
|
236
|
+
ts_class_name="UUIDConfigNode",
|
|
237
|
+
kwargs={"config": UUIDNodeConfig(account_uid=account_uid)},
|
|
238
|
+
)
|
|
239
|
+
rebuilt = build_operations.DeserializerManager().rebuild_serialized_config(
|
|
240
|
+
config.local_initial_configuration,
|
|
241
|
+
time_serie_class_name="UUIDConfigNode",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
assert rebuilt["config"].account_uid == account_uid
|
|
245
|
+
assert isinstance(rebuilt["config"].account_uid, uuid.UUID)
|
|
246
|
+
|
|
247
|
+
|
|
214
248
|
def test_plain_dict_with_pydantic_model_import_path_key_is_not_treated_as_wrapper(monkeypatch):
|
|
215
249
|
monkeypatch.setattr(build_operations, "POD_PROJECT", None, raising=False)
|
|
216
250
|
|
|
@@ -3,6 +3,7 @@ import datetime
|
|
|
3
3
|
import gzip
|
|
4
4
|
import json
|
|
5
5
|
from types import SimpleNamespace
|
|
6
|
+
from uuid import UUID
|
|
6
7
|
|
|
7
8
|
import pandas as pd
|
|
8
9
|
import pytest
|
|
@@ -127,6 +128,72 @@ def test_post_data_frame_in_chunks_serializes_remote_temporal_payload_columns(mo
|
|
|
127
128
|
]
|
|
128
129
|
|
|
129
130
|
|
|
131
|
+
def test_post_data_frame_in_chunks_serializes_remote_uuid_payload_columns(monkeypatch):
|
|
132
|
+
captured = {}
|
|
133
|
+
|
|
134
|
+
class FakeResponse:
|
|
135
|
+
status_code = 200
|
|
136
|
+
text = ""
|
|
137
|
+
content = b'{"ok": true}'
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def json():
|
|
141
|
+
return {"ok": True}
|
|
142
|
+
|
|
143
|
+
def _fake_make_request(*, s, loaders, payload, r_type, url, time_out=None):
|
|
144
|
+
captured["payload"] = payload
|
|
145
|
+
captured["r_type"] = r_type
|
|
146
|
+
captured["url"] = url
|
|
147
|
+
return FakeResponse()
|
|
148
|
+
|
|
149
|
+
monkeypatch.setattr(models_metatables, "make_request", _fake_make_request)
|
|
150
|
+
monkeypatch.setattr(
|
|
151
|
+
models_metatables.DataNodeUpdate,
|
|
152
|
+
"build_session",
|
|
153
|
+
classmethod(lambda cls: object()),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
account_uid = UUID("00000000-0000-4000-8000-000000000001")
|
|
157
|
+
frame = pd.DataFrame(
|
|
158
|
+
{
|
|
159
|
+
"time_index": [pd.Timestamp("2026-05-29T13:40:00Z")],
|
|
160
|
+
"account_uid": [account_uid],
|
|
161
|
+
"unique_identifier": ["AAPL"],
|
|
162
|
+
"quantity": [12.0],
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
models_metatables.DataNodeUpdate.post_data_frame_in_chunks(
|
|
167
|
+
serialized_data_frame=frame,
|
|
168
|
+
data_node_update=_minimal_update(),
|
|
169
|
+
index_names=["time_index", "account_uid", "unique_identifier"],
|
|
170
|
+
time_index_name="time_index",
|
|
171
|
+
column_dtypes_map={
|
|
172
|
+
"time_index": "timestamp with time zone",
|
|
173
|
+
"account_uid": "uuid",
|
|
174
|
+
"unique_identifier": "string",
|
|
175
|
+
"quantity": "float64",
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
decoded = _decode_compressed_payload(captured["payload"])
|
|
180
|
+
assert decoded == [
|
|
181
|
+
{
|
|
182
|
+
"time_index": "2026-05-29T13:40:00Z",
|
|
183
|
+
"account_uid": str(account_uid),
|
|
184
|
+
"unique_identifier": "AAPL",
|
|
185
|
+
"quantity": 12.0,
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
json.dumps(captured["payload"]["json"], allow_nan=False)
|
|
189
|
+
assert captured["payload"]["json"]["chunk_stats"]["index_progress"] == {
|
|
190
|
+
str(account_uid): {"AAPL": "2026-05-29T13:40:00Z"}
|
|
191
|
+
}
|
|
192
|
+
assert captured["payload"]["json"]["chunk_stats"]["index_min"] == {
|
|
193
|
+
str(account_uid): {"AAPL": "2026-05-29T13:40:00Z"}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
130
197
|
def test_set_start_of_execution_prefers_canonical_update_stats(monkeypatch):
|
|
131
198
|
class FakeResponse:
|
|
132
199
|
status_code = 201
|
|
@@ -243,6 +310,50 @@ def test_last_update_payload_model_rejects_unknown_keys_generically():
|
|
|
243
310
|
)
|
|
244
311
|
|
|
245
312
|
|
|
313
|
+
def test_last_update_payload_builder_normalizes_nested_coordinate_keys():
|
|
314
|
+
account_uid = UUID("00000000-0000-4000-8000-000000000001")
|
|
315
|
+
raw_payload = {
|
|
316
|
+
"global_index_progress": {
|
|
317
|
+
"max": _dt(3),
|
|
318
|
+
"min": _dt(0),
|
|
319
|
+
},
|
|
320
|
+
"index_progress": {account_uid: {101: _dt(2)}},
|
|
321
|
+
"index_min": {account_uid: {101: _dt(0)}},
|
|
322
|
+
"multi_index_column_stats": {
|
|
323
|
+
"quantity": {
|
|
324
|
+
account_uid: {
|
|
325
|
+
101: {
|
|
326
|
+
"min": _dt(0),
|
|
327
|
+
"max": _dt(2),
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
payload = models_metatables.LastUpdateIndexTimePayload.model_validate(
|
|
335
|
+
raw_payload
|
|
336
|
+
).to_nested_payload()
|
|
337
|
+
builder_payload = models_metatables.build_last_update_index_time_payload(
|
|
338
|
+
**raw_payload,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
account_key = str(account_uid)
|
|
342
|
+
assert payload["multi_index_stats"]["index_progress"] == {account_key: {"101": _dt(2)}}
|
|
343
|
+
assert payload["multi_index_stats"]["index_min"] == {account_key: {"101": _dt(0)}}
|
|
344
|
+
assert payload["multi_index_column_stats"] == {
|
|
345
|
+
"quantity": {
|
|
346
|
+
account_key: {
|
|
347
|
+
"101": {
|
|
348
|
+
"min": _dt(0),
|
|
349
|
+
"max": _dt(2),
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
assert builder_payload == payload
|
|
355
|
+
|
|
356
|
+
|
|
246
357
|
def test_set_last_update_index_time_from_update_stats_sends_canonical_payload(monkeypatch):
|
|
247
358
|
captured = {}
|
|
248
359
|
|
|
@@ -335,6 +446,28 @@ def test_get_index_progress_chunk_stats_for_three_index_frame():
|
|
|
335
446
|
assert grouped_dates is not None
|
|
336
447
|
|
|
337
448
|
|
|
449
|
+
def test_get_index_progress_chunk_stats_normalizes_uuid_coordinate_keys():
|
|
450
|
+
account_uid = UUID("00000000-0000-4000-8000-000000000001")
|
|
451
|
+
df = pd.DataFrame(
|
|
452
|
+
{
|
|
453
|
+
"time_index": [_dt(0), _dt(2)],
|
|
454
|
+
"account_uid": [account_uid, account_uid],
|
|
455
|
+
"unique_identifier": ["asset-1", "asset-1"],
|
|
456
|
+
"value": [1, 2],
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
stats, grouped_dates = models_metatables.get_index_progress_chunk_stats(
|
|
461
|
+
df,
|
|
462
|
+
time_index_name="time_index",
|
|
463
|
+
index_names=["time_index", "account_uid", "unique_identifier"],
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
assert stats["index_progress"] == {str(account_uid): {"asset-1": _dt(2)}}
|
|
467
|
+
assert stats["index_min"] == {str(account_uid): {"asset-1": _dt(0)}}
|
|
468
|
+
assert grouped_dates is not None
|
|
469
|
+
|
|
470
|
+
|
|
338
471
|
def test_set_last_update_index_time_rejects_legacy_per_asset_backend_payload(monkeypatch):
|
|
339
472
|
def _unexpected_make_request(**_kwargs):
|
|
340
473
|
raise AssertionError("legacy backend payload should fail before make_request")
|
|
@@ -214,6 +214,70 @@ def test_persist_manager_build_update_details_uses_update_details_resource():
|
|
|
214
214
|
assert patched == [("data-node-update-44", {})]
|
|
215
215
|
|
|
216
216
|
|
|
217
|
+
def test_persist_manager_passes_storage_contract_schema_to_update():
|
|
218
|
+
captured = {}
|
|
219
|
+
|
|
220
|
+
class UpdateResource:
|
|
221
|
+
build_configuration = {}
|
|
222
|
+
|
|
223
|
+
def upsert_data_into_table(self, **kwargs):
|
|
224
|
+
captured.update(kwargs)
|
|
225
|
+
return self
|
|
226
|
+
|
|
227
|
+
storage_metadata = TimeIndexMetaData.model_construct(
|
|
228
|
+
uid="data-node-storage-44",
|
|
229
|
+
data_source_uid="data-source-uid",
|
|
230
|
+
data_source=SimpleNamespace(
|
|
231
|
+
related_resource=SimpleNamespace(class_type="postgres"),
|
|
232
|
+
),
|
|
233
|
+
time_indexed_profile=models_metatables.TimeIndexedProfile(
|
|
234
|
+
related_table_uid="data-node-storage-44",
|
|
235
|
+
time_index_name="time_index",
|
|
236
|
+
index_names=["time_index", "account_uid", "unique_identifier"],
|
|
237
|
+
column_dtypes_map={
|
|
238
|
+
"time_index": "timestamp with time zone",
|
|
239
|
+
"account_uid": "uuid",
|
|
240
|
+
"unique_identifier": "string",
|
|
241
|
+
"quantity": "float64",
|
|
242
|
+
},
|
|
243
|
+
storage_layout={
|
|
244
|
+
"time_index": "time_index",
|
|
245
|
+
"identity_dimensions": ["account_uid", "unique_identifier"],
|
|
246
|
+
},
|
|
247
|
+
physical_index_plan={
|
|
248
|
+
"uniqueness": {
|
|
249
|
+
"columns": ["time_index", "account_uid", "unique_identifier"],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
storage_table = _platform_storage_model(storage_metadata)
|
|
255
|
+
manager = BasePersistManager(
|
|
256
|
+
update_hash="account-holdings-update-hash",
|
|
257
|
+
storage_table=storage_table,
|
|
258
|
+
data_node_update=UpdateResource(),
|
|
259
|
+
)
|
|
260
|
+
df = pd.DataFrame(
|
|
261
|
+
{"quantity": [12.0]},
|
|
262
|
+
index=pd.MultiIndex.from_tuples(
|
|
263
|
+
[("2026-05-30T12:00:00Z", "account-a", "AAPL")],
|
|
264
|
+
names=["time_index", "account_uid", "unique_identifier"],
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
assert manager.persist_updated_data(df) is True
|
|
269
|
+
assert captured["source_table_schema"] == {
|
|
270
|
+
"time_index_name": "time_index",
|
|
271
|
+
"index_names": ["time_index", "account_uid", "unique_identifier"],
|
|
272
|
+
"column_dtypes_map": {
|
|
273
|
+
"time_index": "timestamp with time zone",
|
|
274
|
+
"account_uid": "uuid",
|
|
275
|
+
"unique_identifier": "string",
|
|
276
|
+
"quantity": "float64",
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
217
281
|
def test_data_node_storage_accepts_namespace():
|
|
218
282
|
storage = TimeIndexMetaData(
|
|
219
283
|
uid="data-node-storage-12",
|
|
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.2 → mainsequence-4.1.3}/agent_scaffold/skills/command_center/connections/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/dashboards/streamlit/SKILL.md
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/data_access/exploration/SKILL.md
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/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.2 → mainsequence-4.1.3}/mainsequence/client/command_center/app_component.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/command_center/workspace_snapshot.py
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/data_sources_interfaces/__init__.py
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/client/data_sources_interfaces/duckdb.py
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/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
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/namespacing.py
RENAMED
|
File without changes
|
{mainsequence-4.1.2 → mainsequence-4.1.3}/mainsequence/meta_tables/data_nodes/run_operations.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
|
|
File without changes
|