mainsequence 4.3.0__tar.gz → 4.3.6__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.3.0/mainsequence.egg-info → mainsequence-4.3.6}/PKG-INFO +1 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +7 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +5 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/migrations.py +25 -8
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/metatables/core.py +87 -9
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/compiled_sql/v1.py +9 -2
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/migrations.py +72 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/sqlalchemy_contracts.py +37 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence.egg-info/entry_points.txt +1 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/pyproject.toml +2 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_cli_migrations.py +102 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_data_access_mixin_dimension_audit.py +0 -2
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_filter_normalization.py +16 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_meta_table_migrations.py +52 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_meta_tables_client_models.py +44 -1
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_meta_tables_sqlalchemy_contracts.py +75 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/LICENSE +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/README.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/__main__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/cli.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/base.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/client.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/metatables/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/client/utils.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/defaults.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/logconf.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/schema_names.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/setup.cfg +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_cli.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_client.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_data_node_update_flow.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_instrumentation.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_logconf.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_run_configuration.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_schema_names.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_secret_client_model.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.3.0 → mainsequence-4.3.6}/tests/test_workspace_snapshot.py +0 -0
{mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md
RENAMED
|
@@ -159,6 +159,7 @@ class PricesTable(PlatformTimeIndexMetaTable, Base):
|
|
|
159
159
|
"risk analytics."
|
|
160
160
|
)
|
|
161
161
|
__time_index_name__ = "time_index"
|
|
162
|
+
__cadence__ = "1d"
|
|
162
163
|
__index_names__ = ["time_index", "unique_identifier"]
|
|
163
164
|
|
|
164
165
|
time_index: Mapped[datetime.datetime] = mapped_column(
|
|
@@ -197,6 +198,12 @@ autogenerate runs. Do not manually repeat the full grain unique index in
|
|
|
197
198
|
`__table_args__`; add ordinary SQLAlchemy `Index(...)` entries only for
|
|
198
199
|
additional lookup/performance paths.
|
|
199
200
|
|
|
201
|
+
When the dataset has a known stable observation interval, declare `__cadence__`
|
|
202
|
+
on the `PlatformTimeIndexMetaTable` model, for example `1m`, `5m`, `1h`, `1d`,
|
|
203
|
+
`1w`, `1mo`, `1q`, or `1y`. Cadence is table metadata and should be included
|
|
204
|
+
whenever possible; do not make it a DataNode runtime configuration field unless
|
|
205
|
+
changing it actually changes the produced dataset identity.
|
|
206
|
+
|
|
200
207
|
`PlatformTimeIndexMetaTable.register()` remains SDK plumbing for the migration
|
|
201
208
|
workflow. Do not manually attach an existing UID, reconstruct a generic
|
|
202
209
|
`MetaTable`, or use manual bind helpers as an authoring step.
|
{mainsequence-4.3.0 → mainsequence-4.3.6}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md
RENAMED
|
@@ -137,6 +137,11 @@ first version, use Alembic. Keep the SDK model as a normal
|
|
|
137
137
|
`PlatformManagedMetaTable` or `PlatformTimeIndexMetaTable` catalog contract, and
|
|
138
138
|
apply physical schema changes through the Alembic migration workflow.
|
|
139
139
|
|
|
140
|
+
For `PlatformTimeIndexMetaTable`, declare `__cadence__` whenever the table has a
|
|
141
|
+
known stable observation interval, for example `1m`, `5m`, `1h`, `1d`, `1w`,
|
|
142
|
+
`1mo`, `1q`, or `1y`. Cadence is table metadata and belongs on the storage
|
|
143
|
+
model when possible.
|
|
144
|
+
|
|
140
145
|
Default-schema tables must leave SQLAlchemy `Table.schema` unset; do not write
|
|
141
146
|
`__table_args__ = {"schema": "public"}` for the default PostgreSQL schema. Set
|
|
142
147
|
schema metadata only for non-default schemas, using `__table_args__ = {"schema":
|
|
@@ -122,12 +122,21 @@ def _load_alembic_command(command_name: str) -> Any:
|
|
|
122
122
|
return command
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def _coerce_alembic_version_path(version_path: str) -> str:
|
|
126
|
+
try:
|
|
127
|
+
from alembic import util
|
|
128
|
+
except ImportError as exc:
|
|
129
|
+
raise typer.BadParameter("Alembic is required for migration commands.") from exc
|
|
130
|
+
return str(util.coerce_resource_to_filename(version_path))
|
|
131
|
+
|
|
132
|
+
|
|
125
133
|
def _emit_alembic_script_context(
|
|
126
134
|
config: Any,
|
|
127
135
|
*,
|
|
128
136
|
target_revision: str | None = None,
|
|
129
137
|
) -> None:
|
|
130
138
|
script_location = config.get_main_option("script_location")
|
|
139
|
+
version_locations = config.get_main_option("version_locations")
|
|
131
140
|
version_table = config.get_main_option("version_table")
|
|
132
141
|
version_table_schema = config.get_main_option("version_table_schema")
|
|
133
142
|
version_table_label = (
|
|
@@ -135,9 +144,14 @@ def _emit_alembic_script_context(
|
|
|
135
144
|
if version_table_schema not in (None, "")
|
|
136
145
|
else version_table
|
|
137
146
|
)
|
|
147
|
+
version_locations_label = (
|
|
148
|
+
version_locations.replace("\n", ", ") if version_locations else "<default>"
|
|
149
|
+
)
|
|
138
150
|
_emit_status(
|
|
139
151
|
"Alembic script context "
|
|
140
|
-
f"script_location={script_location}
|
|
152
|
+
f"script_location={script_location} "
|
|
153
|
+
f"version_locations={version_locations_label} "
|
|
154
|
+
f"version_table={version_table_label}"
|
|
141
155
|
)
|
|
142
156
|
try:
|
|
143
157
|
from alembic.script import ScriptDirectory
|
|
@@ -745,14 +759,17 @@ def revision(
|
|
|
745
759
|
)
|
|
746
760
|
_emit_alembic_script_context(config, target_revision=head)
|
|
747
761
|
_emit_status(f"Starting Alembic revision now rev_id={resolved_rev_id}...")
|
|
762
|
+
revision_kwargs = {
|
|
763
|
+
"message": resolved_message,
|
|
764
|
+
"autogenerate": autogenerate,
|
|
765
|
+
"rev_id": resolved_rev_id,
|
|
766
|
+
"head": head,
|
|
767
|
+
}
|
|
768
|
+
version_path = migration.resolved_version_path()
|
|
769
|
+
if version_path is not None:
|
|
770
|
+
revision_kwargs["version_path"] = _coerce_alembic_version_path(version_path)
|
|
748
771
|
with _forward_alembic_logging():
|
|
749
|
-
script = command.revision(
|
|
750
|
-
config,
|
|
751
|
-
message=resolved_message,
|
|
752
|
-
autogenerate=autogenerate,
|
|
753
|
-
rev_id=resolved_rev_id,
|
|
754
|
-
head=head,
|
|
755
|
-
)
|
|
772
|
+
script = command.revision(config, **revision_kwargs)
|
|
756
773
|
_emit_status("Alembic revision finished.")
|
|
757
774
|
_emit(
|
|
758
775
|
{
|
|
@@ -8,6 +8,7 @@ import gzip
|
|
|
8
8
|
import json
|
|
9
9
|
import math
|
|
10
10
|
import os
|
|
11
|
+
import re
|
|
11
12
|
import time
|
|
12
13
|
from collections.abc import Callable, Mapping, Sequence
|
|
13
14
|
from dataclasses import dataclass
|
|
@@ -58,6 +59,24 @@ from ..utils import (
|
|
|
58
59
|
DUCK_DB = "duck_db"
|
|
59
60
|
SQLITE = "sqlite"
|
|
60
61
|
LOCAL_DATA_SOURCE_CLASS_TYPES = {DUCK_DB, SQLITE}
|
|
62
|
+
_TIME_INDEXED_CADENCE_RE = re.compile(
|
|
63
|
+
r"^[1-9][0-9]*(mo|s|m|h|d|w|q|y)$",
|
|
64
|
+
re.IGNORECASE,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _normalize_time_indexed_cadence(value: Any) -> str | None:
|
|
69
|
+
if value is None:
|
|
70
|
+
return None
|
|
71
|
+
normalized = str(value).strip().lower()
|
|
72
|
+
if not normalized:
|
|
73
|
+
return None
|
|
74
|
+
if not _TIME_INDEXED_CADENCE_RE.fullmatch(normalized):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
"cadence must be an interval token such as 1m, 5m, 1h, 1d, 1w, "
|
|
77
|
+
"1mo, 1q, or 1y."
|
|
78
|
+
)
|
|
79
|
+
return normalized
|
|
61
80
|
|
|
62
81
|
|
|
63
82
|
def _duckdb_interface():
|
|
@@ -452,8 +471,43 @@ class MetaTableOperationScopeTable(BasePydanticModel):
|
|
|
452
471
|
|
|
453
472
|
|
|
454
473
|
class MetaTableOperationScope(BasePydanticModel):
|
|
474
|
+
data_source_uid: str | None = Field(
|
|
475
|
+
default=None,
|
|
476
|
+
min_length=1,
|
|
477
|
+
validation_alias=AliasChoices("data_source_uid", "dataSourceUid"),
|
|
478
|
+
description=(
|
|
479
|
+
"Public UID of the DynamicTableDataSource that owns the compiled "
|
|
480
|
+
"SQL execution connection. If omitted, the SDK resolves the "
|
|
481
|
+
"configured project/session default data source. Scoped MetaTables "
|
|
482
|
+
"are the permission contract, not the source of execution routing."
|
|
483
|
+
),
|
|
484
|
+
)
|
|
455
485
|
tables: list[MetaTableOperationScopeTable] = Field(..., min_length=1)
|
|
456
486
|
|
|
487
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
488
|
+
|
|
489
|
+
@model_validator(mode="after")
|
|
490
|
+
def resolve_default_data_source_uid(self):
|
|
491
|
+
if self.data_source_uid not in (None, ""):
|
|
492
|
+
self.data_source_uid = str(self.data_source_uid)
|
|
493
|
+
return self
|
|
494
|
+
|
|
495
|
+
try:
|
|
496
|
+
data_source = get_session_data_source()
|
|
497
|
+
except Exception as exc:
|
|
498
|
+
raise ValueError(
|
|
499
|
+
"MetaTable compiled SQL scope requires data_source_uid or a "
|
|
500
|
+
"configured project/session default data source."
|
|
501
|
+
) from exc
|
|
502
|
+
|
|
503
|
+
uid = getattr(data_source, "uid", None) or getattr(data_source, "data_source_uid", None)
|
|
504
|
+
if uid in (None, ""):
|
|
505
|
+
raise ValueError(
|
|
506
|
+
"Configured project/session default data source does not expose a uid."
|
|
507
|
+
)
|
|
508
|
+
self.data_source_uid = str(uid)
|
|
509
|
+
return self
|
|
510
|
+
|
|
457
511
|
|
|
458
512
|
class MetaTableOperationLimits(BasePydanticModel):
|
|
459
513
|
max_rows: int | None = Field(default=None, ge=1)
|
|
@@ -632,6 +686,11 @@ class ManagedMetaTableFinalizeTableResult(BasePydanticModel):
|
|
|
632
686
|
)
|
|
633
687
|
table_kind: str = Field(..., description="Backend table kind after reconciliation.")
|
|
634
688
|
time_indexed: bool = Field(..., description="Whether the MetaTable is time-indexed.")
|
|
689
|
+
cadence: str | None = Field(
|
|
690
|
+
None,
|
|
691
|
+
max_length=32,
|
|
692
|
+
description="Optional declared cadence for time-indexed MetaTables.",
|
|
693
|
+
)
|
|
635
694
|
finalized: bool = Field(
|
|
636
695
|
...,
|
|
637
696
|
description="True when this row was reconciled and activated by this call.",
|
|
@@ -660,6 +719,11 @@ class ManagedMetaTableFinalizeTableResult(BasePydanticModel):
|
|
|
660
719
|
description="Per-table structured error when finalization did not activate this row.",
|
|
661
720
|
)
|
|
662
721
|
|
|
722
|
+
@field_validator("cadence")
|
|
723
|
+
@classmethod
|
|
724
|
+
def _normalize_cadence(cls, value: str | None) -> str | None:
|
|
725
|
+
return _normalize_time_indexed_cadence(value)
|
|
726
|
+
|
|
663
727
|
model_config = ConfigDict(extra="ignore")
|
|
664
728
|
|
|
665
729
|
|
|
@@ -1888,6 +1952,14 @@ class TimeIndexMetaTableRegistrationRequest(BasePydanticModel):
|
|
|
1888
1952
|
default_factory=lambda: {"create_table": True, "if_not_exists": True}
|
|
1889
1953
|
)
|
|
1890
1954
|
time_index_name: str = Field(..., description="Canonical timestamp column name")
|
|
1955
|
+
cadence: str | None = Field(
|
|
1956
|
+
None,
|
|
1957
|
+
max_length=32,
|
|
1958
|
+
description=(
|
|
1959
|
+
"Optional time-indexed cadence token such as 1m, 5m, 1h, 1d, "
|
|
1960
|
+
"1w, 1mo, 1q, or 1y."
|
|
1961
|
+
),
|
|
1962
|
+
)
|
|
1891
1963
|
partition_strategy: str = Field(
|
|
1892
1964
|
default="backend_default",
|
|
1893
1965
|
description="Time-indexed MetaTable physical partitioning strategy",
|
|
@@ -1938,6 +2010,11 @@ class TimeIndexMetaTableRegistrationRequest(BasePydanticModel):
|
|
|
1938
2010
|
data["table_contract"] = normalized_contract
|
|
1939
2011
|
return data
|
|
1940
2012
|
|
|
2013
|
+
@field_validator("cadence")
|
|
2014
|
+
@classmethod
|
|
2015
|
+
def _normalize_cadence(cls, value: str | None) -> str | None:
|
|
2016
|
+
return _normalize_time_indexed_cadence(value)
|
|
2017
|
+
|
|
1941
2018
|
|
|
1942
2019
|
def _payload_get(obj: Any, key: str, default: Any = None) -> Any:
|
|
1943
2020
|
if isinstance(obj, Mapping):
|
|
@@ -2078,6 +2155,11 @@ class TimeIndexedProfile(TimeIndexedProfileBase, BasePydanticModel):
|
|
|
2078
2155
|
None, description="Public uid of the related TimeIndexMetaTable"
|
|
2079
2156
|
)
|
|
2080
2157
|
time_index_name: str = Field(..., max_length=100, description="Time index name")
|
|
2158
|
+
cadence: str | None = Field(
|
|
2159
|
+
None,
|
|
2160
|
+
max_length=32,
|
|
2161
|
+
description="Optional declared cadence for the time-indexed table.",
|
|
2162
|
+
)
|
|
2081
2163
|
partition_strategy: str | None = None
|
|
2082
2164
|
last_time_index_value: datetime.datetime | None = Field(
|
|
2083
2165
|
None, description="Last time index value"
|
|
@@ -2089,6 +2171,11 @@ class TimeIndexedProfile(TimeIndexedProfileBase, BasePydanticModel):
|
|
|
2089
2171
|
physical_index_plan: dict[str, Any] | None = Field(
|
|
2090
2172
|
None, description="Server-rendered physical index plan"
|
|
2091
2173
|
)
|
|
2174
|
+
|
|
2175
|
+
@field_validator("cadence")
|
|
2176
|
+
@classmethod
|
|
2177
|
+
def _normalize_cadence(cls, value: str | None) -> str | None:
|
|
2178
|
+
return _normalize_time_indexed_cadence(value)
|
|
2092
2179
|
multi_index_stats: dict[str, Any] | None = Field(
|
|
2093
2180
|
None, description="Canonical multi-index progress statistics"
|
|
2094
2181
|
)
|
|
@@ -2928,15 +3015,6 @@ class TimeIndexMetaTable(MetaTable):
|
|
|
2928
3015
|
"labels__in": "str",
|
|
2929
3016
|
"labels__contains": "str",
|
|
2930
3017
|
}
|
|
2931
|
-
READ_QUERY_PARAMS: ClassVar[dict[str, str]] = {
|
|
2932
|
-
"include_relations_detail": "bool",
|
|
2933
|
-
}
|
|
2934
|
-
READ_QUERY_PARAM_DESCRIPTIONS: ClassVar[dict[str, str]] = {
|
|
2935
|
-
"include_relations_detail": (
|
|
2936
|
-
"Expand related objects in the serializer response. "
|
|
2937
|
-
"This changes response detail only and does not change which rows are returned."
|
|
2938
|
-
),
|
|
2939
|
-
}
|
|
2940
3018
|
build_configuration_json_schema: dict[str, Any] | None = Field(
|
|
2941
3019
|
None,
|
|
2942
3020
|
description="JSON schema describing the DataNode update build configuration.",
|
|
@@ -34,8 +34,8 @@ def build_operation(
|
|
|
34
34
|
Build and validate the TS Manager compiled-sql.v1 operation contract.
|
|
35
35
|
|
|
36
36
|
This is the client-side protocol object. It is intentionally plain SQL plus
|
|
37
|
-
bound parameters
|
|
38
|
-
object.
|
|
37
|
+
bound parameters, an explicit execution data source, and declared MetaTable
|
|
38
|
+
scope, not a serialized SQLAlchemy object.
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
41
|
if parameters is None:
|
|
@@ -72,6 +72,7 @@ def compile_sqlalchemy_statement(
|
|
|
72
72
|
statement: Any,
|
|
73
73
|
*,
|
|
74
74
|
operation: MetaTableOperation,
|
|
75
|
+
data_source_uid: str | None = None,
|
|
75
76
|
scope_tables: Sequence[MetaTableOperationScopeTable | Mapping[str, Any]],
|
|
76
77
|
limits: MetaTableOperationLimits | Mapping[str, Any] | None = None,
|
|
77
78
|
dialect: MetaTableCompiledSQLDialect = "postgresql",
|
|
@@ -80,6 +81,11 @@ def compile_sqlalchemy_statement(
|
|
|
80
81
|
) -> MetaTableCompiledSQLOperation:
|
|
81
82
|
"""
|
|
82
83
|
Compile a SQLAlchemy/Core statement into the TS Manager compiled-sql.v1 payload.
|
|
84
|
+
|
|
85
|
+
``data_source_uid`` selects the DynamicTableDataSource connection. If it is
|
|
86
|
+
omitted, the SDK resolves the configured project/session default data source.
|
|
87
|
+
``scope_tables`` remains the declared MetaTable permission scope for the
|
|
88
|
+
operation.
|
|
83
89
|
"""
|
|
84
90
|
|
|
85
91
|
if dialect != "postgresql":
|
|
@@ -97,6 +103,7 @@ def compile_sqlalchemy_statement(
|
|
|
97
103
|
)
|
|
98
104
|
parameter_types = _compiled_sqlalchemy_parameter_types(compiled)
|
|
99
105
|
scope = MetaTableOperationScope(
|
|
106
|
+
data_source_uid=data_source_uid,
|
|
100
107
|
tables=[
|
|
101
108
|
(
|
|
102
109
|
table
|
|
@@ -211,7 +211,6 @@ class APIDataNode(DataAccessMixin):
|
|
|
211
211
|
def build_from_table_name(cls, table_name: str) -> "APIDataNode":
|
|
212
212
|
storage_table = TimeIndexMetaTable.get_or_none(
|
|
213
213
|
physical_table_name=table_name,
|
|
214
|
-
include_relations_detail=True,
|
|
215
214
|
)
|
|
216
215
|
if storage_table is None:
|
|
217
216
|
raise DoesNotExist(
|
{mainsequence-4.3.0 → mainsequence-4.3.6}/mainsequence/meta_tables/data_nodes/persist_managers.py
RENAMED
|
@@ -567,7 +567,6 @@ class APIPersistManager:
|
|
|
567
567
|
result = TimeIndexMetaTable.get_or_none(
|
|
568
568
|
physical_table_name=self.storage_hash,
|
|
569
569
|
data_source__uid=self.data_source_uid,
|
|
570
|
-
include_relations_detail=True,
|
|
571
570
|
)
|
|
572
571
|
self._storage_table_future.set_result(result)
|
|
573
572
|
except Exception as exc:
|
|
@@ -101,6 +101,49 @@ class AlembicProviderPhysicalStateError(RuntimeError):
|
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
def _normalize_optional_alembic_location(value: Any, field_name: str) -> str | None:
|
|
105
|
+
if value is None:
|
|
106
|
+
return None
|
|
107
|
+
text = str(value).strip()
|
|
108
|
+
if not text:
|
|
109
|
+
raise ValueError(f"AlembicMetaTableMigration {field_name} cannot be empty.")
|
|
110
|
+
return text
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _normalize_alembic_version_locations(value: str | Sequence[str] | None) -> tuple[str, ...]:
|
|
114
|
+
if value is None:
|
|
115
|
+
return ()
|
|
116
|
+
raw_locations: Sequence[str]
|
|
117
|
+
if isinstance(value, str):
|
|
118
|
+
raw_locations = [value]
|
|
119
|
+
else:
|
|
120
|
+
raw_locations = value
|
|
121
|
+
|
|
122
|
+
locations: list[str] = []
|
|
123
|
+
seen: set[str] = set()
|
|
124
|
+
for raw_location in raw_locations:
|
|
125
|
+
location = _normalize_optional_alembic_location(raw_location, "version_locations")
|
|
126
|
+
if location is None or location in seen:
|
|
127
|
+
continue
|
|
128
|
+
seen.add(location)
|
|
129
|
+
locations.append(location)
|
|
130
|
+
if not locations:
|
|
131
|
+
raise ValueError("AlembicMetaTableMigration version_locations cannot be empty.")
|
|
132
|
+
return tuple(locations)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _configure_alembic_version_locations(
|
|
136
|
+
config: Any,
|
|
137
|
+
version_locations: str | Sequence[str] | None,
|
|
138
|
+
) -> tuple[str, ...]:
|
|
139
|
+
locations = _normalize_alembic_version_locations(version_locations)
|
|
140
|
+
if not locations:
|
|
141
|
+
return ()
|
|
142
|
+
config.set_main_option("version_locations", "\n".join(locations))
|
|
143
|
+
config.set_main_option("path_separator", "newline")
|
|
144
|
+
return locations
|
|
145
|
+
|
|
146
|
+
|
|
104
147
|
class AlembicVersionMetaTable:
|
|
105
148
|
"""MetaTable catalog binding for Alembic's version table.
|
|
106
149
|
|
|
@@ -255,6 +298,8 @@ class AlembicMetaTableMigration:
|
|
|
255
298
|
script_location: str
|
|
256
299
|
target_metadata: Any
|
|
257
300
|
alembic_registry: type[AlembicVersionMetaTable]
|
|
301
|
+
version_locations: str | Sequence[str] | None = None
|
|
302
|
+
version_path: str | None = None
|
|
258
303
|
metatable_models: Sequence[type[Any]] = field(default_factory=tuple)
|
|
259
304
|
after_register_metatables: Callable[[AlembicMetaTableCatalogRefreshContext], Any] | None = None
|
|
260
305
|
include_name_hook: Any | None = None
|
|
@@ -273,6 +318,8 @@ class AlembicMetaTableMigration:
|
|
|
273
318
|
self.after_register_metatables
|
|
274
319
|
):
|
|
275
320
|
raise TypeError("after_register_metatables must be callable when provided.")
|
|
321
|
+
self.resolved_version_locations()
|
|
322
|
+
self.resolved_version_path()
|
|
276
323
|
_normalize_provider_default_schemas(self.metatable_models)
|
|
277
324
|
_ensure_provider_time_index_grain_indexes(self.metatable_models)
|
|
278
325
|
|
|
@@ -292,6 +339,23 @@ class AlembicMetaTableMigration:
|
|
|
292
339
|
def version_table_schema(self) -> str | None:
|
|
293
340
|
return self.alembic_registry.__alembic_version_schema__
|
|
294
341
|
|
|
342
|
+
def resolved_version_locations(self) -> tuple[str, ...]:
|
|
343
|
+
return _normalize_alembic_version_locations(self.version_locations)
|
|
344
|
+
|
|
345
|
+
def resolved_version_path(self) -> str | None:
|
|
346
|
+
explicit = _normalize_optional_alembic_location(self.version_path, "version_path")
|
|
347
|
+
if explicit is not None:
|
|
348
|
+
return explicit
|
|
349
|
+
locations = self.resolved_version_locations()
|
|
350
|
+
if len(locations) == 1:
|
|
351
|
+
return locations[0]
|
|
352
|
+
if len(locations) > 1:
|
|
353
|
+
raise ValueError(
|
|
354
|
+
"AlembicMetaTableMigration with multiple version_locations requires "
|
|
355
|
+
"an explicit version_path for revision generation."
|
|
356
|
+
)
|
|
357
|
+
return None
|
|
358
|
+
|
|
295
359
|
def include_name(self, name: str | None, type_: str, parent_names: dict[str, Any]) -> bool:
|
|
296
360
|
if self.include_name_hook is not None:
|
|
297
361
|
return bool(self.include_name_hook(name, type_, parent_names))
|
|
@@ -750,6 +814,7 @@ def alembic_config_for_provider(
|
|
|
750
814
|
if output_buffer is not None:
|
|
751
815
|
config.output_buffer = output_buffer
|
|
752
816
|
config.set_main_option("script_location", migration.script_location)
|
|
817
|
+
version_locations = _configure_alembic_version_locations(config, migration.version_locations)
|
|
753
818
|
config.set_main_option("sqlalchemy.url", sqlalchemy_url.replace("%", "%%"))
|
|
754
819
|
config.set_main_option("sqlalchemy.echo", "true")
|
|
755
820
|
config.set_main_option("version_table", migration.version_table)
|
|
@@ -764,6 +829,8 @@ def alembic_config_for_provider(
|
|
|
764
829
|
config.attributes["alembic_version_table"] = migration.alembic_version_table
|
|
765
830
|
config.attributes["version_table"] = migration.version_table
|
|
766
831
|
config.attributes["version_table_schema"] = migration.version_table_schema
|
|
832
|
+
config.attributes["version_locations"] = version_locations
|
|
833
|
+
config.attributes["version_path"] = migration.resolved_version_path()
|
|
767
834
|
return config
|
|
768
835
|
|
|
769
836
|
|
|
@@ -805,6 +872,7 @@ def resolve_alembic_revision_metadata(
|
|
|
805
872
|
*,
|
|
806
873
|
script_location: str,
|
|
807
874
|
revision: str,
|
|
875
|
+
version_locations: str | Sequence[str] | None = None,
|
|
808
876
|
) -> tuple[str, str | None]:
|
|
809
877
|
try:
|
|
810
878
|
from alembic.config import Config
|
|
@@ -814,6 +882,7 @@ def resolve_alembic_revision_metadata(
|
|
|
814
882
|
|
|
815
883
|
config = Config()
|
|
816
884
|
config.set_main_option("script_location", script_location)
|
|
885
|
+
_configure_alembic_version_locations(config, version_locations)
|
|
817
886
|
script = ScriptDirectory.from_config(config)
|
|
818
887
|
resolved = script.get_revision(revision)
|
|
819
888
|
if resolved is None:
|
|
@@ -866,6 +935,9 @@ def _collection_create_row_from_registration_request(
|
|
|
866
935
|
time_index_name = getattr(request, "time_index_name", None)
|
|
867
936
|
if time_index_name not in (None, ""):
|
|
868
937
|
row["time_index_name"] = str(time_index_name)
|
|
938
|
+
cadence = getattr(request, "cadence", None)
|
|
939
|
+
if cadence not in (None, ""):
|
|
940
|
+
row["cadence"] = str(cadence)
|
|
869
941
|
partition_strategy = getattr(request, "partition_strategy", None)
|
|
870
942
|
if partition_strategy not in (None, ""):
|
|
871
943
|
row["partition_strategy"] = str(partition_strategy)
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import contextvars
|
|
5
5
|
import json
|
|
6
|
+
import re
|
|
6
7
|
from collections.abc import Mapping, Sequence
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from typing import Any, ClassVar
|
|
@@ -37,6 +38,10 @@ DEFAULT_PLATFORM_MANAGED_PROVISIONING = {
|
|
|
37
38
|
}
|
|
38
39
|
SERVER_GENERATED_UUID_DEFAULT = "gen_random_uuid()"
|
|
39
40
|
DEFAULT_POSTGRES_SCHEMA = "public"
|
|
41
|
+
_TIME_INDEXED_CADENCE_RE = re.compile(
|
|
42
|
+
r"^[1-9][0-9]*(mo|s|m|h|d|w|q|y)$",
|
|
43
|
+
re.IGNORECASE,
|
|
44
|
+
)
|
|
40
45
|
_PLATFORM_MANAGED_MIGRATION_REGISTRATION_CONTEXT: contextvars.ContextVar[bool] = (
|
|
41
46
|
contextvars.ContextVar(
|
|
42
47
|
"mainsequence_platform_managed_migration_registration_context",
|
|
@@ -292,6 +297,7 @@ class PlatformTimeIndexMetaTable(PlatformManagedMetaTable):
|
|
|
292
297
|
"""
|
|
293
298
|
|
|
294
299
|
__time_index_meta_table__: ClassVar[TimeIndexMetaTable | None] = None
|
|
300
|
+
__cadence__: ClassVar[str | None] = None
|
|
295
301
|
|
|
296
302
|
__mapper_args__ = _sqlalchemy_declared_attr.directive(_time_index_mapper_args)
|
|
297
303
|
|
|
@@ -505,6 +511,7 @@ def time_indexed_registration_request_from_sqlalchemy_model(
|
|
|
505
511
|
model_or_table,
|
|
506
512
|
storage_layout=storage_layout,
|
|
507
513
|
)
|
|
514
|
+
resolved_cadence = _resolve_time_index_cadence(model_or_table)
|
|
508
515
|
columns = _iter_columns(table)
|
|
509
516
|
_validate_time_index_contract(
|
|
510
517
|
columns=columns,
|
|
@@ -520,6 +527,7 @@ def time_indexed_registration_request_from_sqlalchemy_model(
|
|
|
520
527
|
time_index_name=resolved_time_index_name,
|
|
521
528
|
index_names=resolved_index_names,
|
|
522
529
|
storage_layout=resolved_storage_layout,
|
|
530
|
+
cadence=resolved_cadence,
|
|
523
531
|
),
|
|
524
532
|
hash_namespace=resolved_hash_namespace,
|
|
525
533
|
extra_hash_components=resolved_extra_hash_components,
|
|
@@ -559,6 +567,7 @@ def time_indexed_registration_request_from_sqlalchemy_model(
|
|
|
559
567
|
labels=_resolve_labels(model_or_table, labels=labels),
|
|
560
568
|
provisioning=_resolve_provisioning(model_or_table, provisioning=provisioning),
|
|
561
569
|
time_index_name=resolved_time_index_name,
|
|
570
|
+
cadence=resolved_cadence,
|
|
562
571
|
table_contract={
|
|
563
572
|
"version": "relational-table.v1",
|
|
564
573
|
"table_kind": "time_indexed",
|
|
@@ -571,6 +580,7 @@ def time_indexed_registration_request_from_sqlalchemy_model(
|
|
|
571
580
|
"time_indexed": {
|
|
572
581
|
"time_index_name": resolved_time_index_name,
|
|
573
582
|
"index_names": resolved_index_names,
|
|
583
|
+
**({"cadence": resolved_cadence} if resolved_cadence else {}),
|
|
574
584
|
**(
|
|
575
585
|
{"storage_layout": dict(resolved_storage_layout)}
|
|
576
586
|
if resolved_storage_layout
|
|
@@ -1246,6 +1256,7 @@ def _configured_table_storage_identity(model_or_table: Any, *, table: Any) -> di
|
|
|
1246
1256
|
time_index_name=time_index_name,
|
|
1247
1257
|
index_names=index_names,
|
|
1248
1258
|
storage_layout=_resolve_time_index_storage_layout(model_or_table),
|
|
1259
|
+
cadence=_resolve_time_index_cadence(model_or_table),
|
|
1249
1260
|
)
|
|
1250
1261
|
|
|
1251
1262
|
|
|
@@ -1260,12 +1271,14 @@ def _time_index_table_storage_identity(
|
|
|
1260
1271
|
time_index_name: str,
|
|
1261
1272
|
index_names: Sequence[str],
|
|
1262
1273
|
storage_layout: Mapping[str, Any] | None,
|
|
1274
|
+
cadence: str | None,
|
|
1263
1275
|
) -> dict[str, Any]:
|
|
1264
1276
|
return _time_index_storage_identity(
|
|
1265
1277
|
table_storage_identity=_table_storage_identity(table),
|
|
1266
1278
|
time_index_name=time_index_name,
|
|
1267
1279
|
index_names=index_names,
|
|
1268
1280
|
storage_layout=storage_layout,
|
|
1281
|
+
cadence=cadence,
|
|
1269
1282
|
)
|
|
1270
1283
|
|
|
1271
1284
|
|
|
@@ -1275,12 +1288,14 @@ def _time_index_table_items_storage_identity(
|
|
|
1275
1288
|
time_index_name: str,
|
|
1276
1289
|
index_names: Sequence[str],
|
|
1277
1290
|
storage_layout: Mapping[str, Any] | None,
|
|
1291
|
+
cadence: str | None,
|
|
1278
1292
|
) -> dict[str, Any]:
|
|
1279
1293
|
return _time_index_storage_identity(
|
|
1280
1294
|
table_storage_identity=_table_items_storage_identity(table_items),
|
|
1281
1295
|
time_index_name=time_index_name,
|
|
1282
1296
|
index_names=index_names,
|
|
1283
1297
|
storage_layout=storage_layout,
|
|
1298
|
+
cadence=cadence,
|
|
1284
1299
|
)
|
|
1285
1300
|
|
|
1286
1301
|
|
|
@@ -1290,12 +1305,15 @@ def _time_index_storage_identity(
|
|
|
1290
1305
|
time_index_name: str,
|
|
1291
1306
|
index_names: Sequence[str],
|
|
1292
1307
|
storage_layout: Mapping[str, Any] | None,
|
|
1308
|
+
cadence: str | None,
|
|
1293
1309
|
) -> dict[str, Any]:
|
|
1294
1310
|
profile: dict[str, Any] = {
|
|
1295
1311
|
"kind": "time_indexed",
|
|
1296
1312
|
"time_index_name": str(time_index_name),
|
|
1297
1313
|
"index_names": [str(name) for name in index_names],
|
|
1298
1314
|
}
|
|
1315
|
+
if cadence:
|
|
1316
|
+
profile["cadence"] = cadence
|
|
1299
1317
|
if storage_layout:
|
|
1300
1318
|
profile["storage_layout"] = dict(storage_layout)
|
|
1301
1319
|
return {
|
|
@@ -1399,10 +1417,28 @@ def _resolve_time_index_storage_layout(
|
|
|
1399
1417
|
if not isinstance(resolved, Mapping):
|
|
1400
1418
|
raise ValueError(
|
|
1401
1419
|
"PlatformTimeIndexMetaTable storage_layout must be a mapping when provided."
|
|
1402
|
-
|
|
1420
|
+
)
|
|
1403
1421
|
return resolved
|
|
1404
1422
|
|
|
1405
1423
|
|
|
1424
|
+
def _resolve_time_index_cadence(model_or_table: Any) -> str | None:
|
|
1425
|
+
resolved = (
|
|
1426
|
+
getattr(model_or_table, "__cadence__", None)
|
|
1427
|
+
or getattr(model_or_table, "__dynamic_table_cadence__", None)
|
|
1428
|
+
)
|
|
1429
|
+
if resolved in (None, ""):
|
|
1430
|
+
return None
|
|
1431
|
+
normalized = str(resolved).strip().lower()
|
|
1432
|
+
if not normalized:
|
|
1433
|
+
return None
|
|
1434
|
+
if not _TIME_INDEXED_CADENCE_RE.fullmatch(normalized):
|
|
1435
|
+
raise ValueError(
|
|
1436
|
+
"PlatformTimeIndexMetaTable cadence must be an interval token such as "
|
|
1437
|
+
"1m, 5m, 1h, 1d, 1w, 1mo, 1q, or 1y."
|
|
1438
|
+
)
|
|
1439
|
+
return normalized
|
|
1440
|
+
|
|
1441
|
+
|
|
1406
1442
|
def _dynamic_table_info_value(model_or_table: Any, key: str) -> Any:
|
|
1407
1443
|
dynamic_table = _table_info_value(model_or_table, "dynamic_table")
|
|
1408
1444
|
if isinstance(dynamic_table, Mapping):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mainsequence"
|
|
7
|
-
version = "4.3.
|
|
7
|
+
version = "4.3.6"
|
|
8
8
|
description = "Main Sequence SDK "
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -60,6 +60,7 @@ Homepage = "https://github.com/mainsequence-sdk/mainsequence-sdk"
|
|
|
60
60
|
Issues = "https://github.com/mainsequence-sdk/mainsequence-sdk/issues"
|
|
61
61
|
[project.scripts]
|
|
62
62
|
mainsequence = "mainsequence.cli.cli:app"
|
|
63
|
+
ms = "mainsequence.cli.cli:app"
|
|
63
64
|
|
|
64
65
|
# PEP 621 dependencies (published requirements)
|
|
65
66
|
[dependency-groups]
|