mainsequence 3.18.2__tar.gz → 3.18.7__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-3.18.2 → mainsequence-3.18.7}/PKG-INFO +1 -1
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/api.py +30 -2
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/cli.py +135 -25
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/config.py +7 -4
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/client.py +3 -1
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/command_center/workspace_snapshot.py +3 -1
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/models_simple_tables.py +7 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/models_tdag.py +60 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/models_user.py +2 -1
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/utils.py +121 -4
- mainsequence-3.18.7/mainsequence/defaults.py +3 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/logconf.py +50 -10
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence.egg-info/SOURCES.txt +1 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/pyproject.toml +1 -1
- mainsequence-3.18.7/tests/test_auth_precedence.py +537 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_cli.py +255 -14
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_filter_normalization.py +124 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_run_configuration.py +71 -2
- mainsequence-3.18.2/tests/test_auth_precedence.py +0 -214
- {mainsequence-3.18.2 → mainsequence-3.18.7}/LICENSE +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/README.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/data_publishing/simple_tables/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/maintenance/local_journal/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/markets_platform/assets_and_translation/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/markets_platform/instruments_and_pricing/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/markets_platform/virtualfundbuilder/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/__main__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/bootstrap.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/cli/ui.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/agent.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/base.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/data_sources_interfaces/timescale.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/models_vam.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/compute_validation.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/config.toml +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/image_1_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/image_2_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/image_3_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/image_4_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/image_5_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/assets/logo.png +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/components/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/components/asset_select.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/components/date_settings.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/components/logged_user.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/core/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/core/theme.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/instruments/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/instruments/streamlit_form_factory.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/dashboards/streamlit/scaffold.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/data_interface/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/data_interface/data_interface.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/base_instrument.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/bond.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/callability.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/interest_rate_swap.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/json_codec.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/position.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/instruments/ql_fields.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/interest_rates/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/interest_rates/etl/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/interest_rates/etl/curve_codec.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/interest_rates/etl/nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/interest_rates/etl/registry.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/pricing_models/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/pricing_models/bond_pricer.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/pricing_models/indices.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/pricing_models/indices_builders.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/pricing_models/swap_pricer.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/settings.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/instruments/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/__main__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/base_persist_managers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/config.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/configuration_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/build_operations.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/namespacing.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/data_nodes/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/future_registry.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/pydantic_metadata.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/simple_tables/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/simple_tables/filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/simple_tables/models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/simple_tables/persist_managers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/simple_tables/schema.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/simple_tables/table_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/tdag/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/prices/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/prices/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/enums.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/portfolio_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/virtualfundbuilder/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/setup.cfg +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_client.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_command_center_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_dependency_extras.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_instruments.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_logconf.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_logged_user_components.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_simple_tables_configuration_hashing.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.7}/tests/test_simple_tables_persistence.py +0 -0
|
@@ -178,6 +178,28 @@ def refresh_access() -> str:
|
|
|
178
178
|
NotLoggedIn: if refresh is missing or refresh fails
|
|
179
179
|
"""
|
|
180
180
|
refresh = _refresh_token()
|
|
181
|
+
runtime_mode = (os.environ.get("MAINSEQUENCE_AUTH_MODE") or "").strip().lower() == "runtime_credential"
|
|
182
|
+
|
|
183
|
+
if not refresh and runtime_mode:
|
|
184
|
+
try:
|
|
185
|
+
from mainsequence.client.utils import RuntimeCredentialAuthProvider
|
|
186
|
+
except Exception as exc:
|
|
187
|
+
raise NotLoggedIn(f"Runtime credential auth is unavailable: {exc}") from exc
|
|
188
|
+
|
|
189
|
+
token_url = f"{backend_url().rstrip('/')}/orm/api/pods/runtime-credentials/token/"
|
|
190
|
+
try:
|
|
191
|
+
RuntimeCredentialAuthProvider(token_url=token_url).refresh(force=True)
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
raise NotLoggedIn(f"Runtime credential exchange failed: {exc}") from exc
|
|
194
|
+
|
|
195
|
+
access = (os.environ.get("MAINSEQUENCE_ACCESS_TOKEN") or "").strip()
|
|
196
|
+
if not access:
|
|
197
|
+
raise NotLoggedIn("Runtime credential exchange did not produce MAINSEQUENCE_ACCESS_TOKEN.")
|
|
198
|
+
|
|
199
|
+
tokens = get_tokens()
|
|
200
|
+
save_tokens(tokens.get("username") or "", access, "")
|
|
201
|
+
return access
|
|
202
|
+
|
|
181
203
|
if not refresh:
|
|
182
204
|
raise NotLoggedIn("Not logged in. Run `mainsequence login`.")
|
|
183
205
|
|
|
@@ -422,7 +444,8 @@ def get_logged_user_details() -> dict[str, Any]:
|
|
|
422
444
|
|
|
423
445
|
The CLI does not naturally run inside a request context, so this bridge resolves
|
|
424
446
|
the current user id from the authenticated API session and temporarily binds
|
|
425
|
-
`X-User-ID`
|
|
447
|
+
`X-User-ID` plus `Authorization` into
|
|
448
|
+
`mainsequence.client.models_user._CURRENT_AUTH_HEADERS`
|
|
426
449
|
before calling the SDK method.
|
|
427
450
|
"""
|
|
428
451
|
tokens = get_tokens()
|
|
@@ -486,7 +509,12 @@ def get_logged_user_details() -> dict[str, Any]:
|
|
|
486
509
|
|
|
487
510
|
BaseObjectOrm.ROOT_URL = root_url
|
|
488
511
|
ClientUser.ROOT_URL = root_url
|
|
489
|
-
headers_token = current_auth_headers.set(
|
|
512
|
+
headers_token = current_auth_headers.set(
|
|
513
|
+
{
|
|
514
|
+
"X-User-ID": str(user_id),
|
|
515
|
+
"Authorization": f"Bearer {access}",
|
|
516
|
+
}
|
|
517
|
+
)
|
|
490
518
|
|
|
491
519
|
user = ClientUser.get_logged_user()
|
|
492
520
|
if isinstance(user, dict):
|
|
@@ -756,6 +756,28 @@ def _require_login() -> dict:
|
|
|
756
756
|
raise typer.Exit(1) from e
|
|
757
757
|
|
|
758
758
|
|
|
759
|
+
def _runtime_credential_mode_enabled() -> bool:
|
|
760
|
+
return (os.environ.get("MAINSEQUENCE_AUTH_MODE") or "").strip().lower() == "runtime_credential"
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def _exchange_runtime_credential_for_cli_login(backend_url: str) -> str:
|
|
764
|
+
try:
|
|
765
|
+
from mainsequence.client.utils import RuntimeCredentialAuthProvider
|
|
766
|
+
except Exception as exc:
|
|
767
|
+
raise ApiError(f"Runtime credential auth is unavailable: {exc}") from exc
|
|
768
|
+
|
|
769
|
+
token_url = f"{backend_url.rstrip('/')}/orm/api/pods/runtime-credentials/token/"
|
|
770
|
+
try:
|
|
771
|
+
RuntimeCredentialAuthProvider(token_url=token_url).refresh(force=True)
|
|
772
|
+
except Exception as exc:
|
|
773
|
+
raise ApiError(f"Runtime credential exchange failed: {exc}") from exc
|
|
774
|
+
|
|
775
|
+
access = (os.environ.get("MAINSEQUENCE_ACCESS_TOKEN") or "").strip()
|
|
776
|
+
if not access:
|
|
777
|
+
raise ApiError("Runtime credential exchange did not produce MAINSEQUENCE_ACCESS_TOKEN.")
|
|
778
|
+
return access
|
|
779
|
+
|
|
780
|
+
|
|
759
781
|
def _resolve_project_dir(project_id: int | None, path: str | None) -> pathlib.Path:
|
|
760
782
|
"""
|
|
761
783
|
Resolve project directory by:
|
|
@@ -2034,10 +2056,15 @@ def login(
|
|
|
2034
2056
|
Persists auth tokens in the active CLI auth store so subsequent
|
|
2035
2057
|
CLI invocations can run without re-authentication. Backend/base-folder
|
|
2036
2058
|
overrides passed to `login` are scoped to the current terminal session.
|
|
2059
|
+
When no backend is provided, login defaults to the standard production backend.
|
|
2037
2060
|
|
|
2038
2061
|
Interactive login uses browser-based authentication and finishes with
|
|
2039
2062
|
standard JWT access/refresh tokens persisted by the CLI.
|
|
2040
2063
|
|
|
2064
|
+
If `MAINSEQUENCE_AUTH_MODE=runtime_credential`, login exchanges the
|
|
2065
|
+
configured runtime credential for a short-lived access token instead of
|
|
2066
|
+
opening the browser or persisting CLI JWT tokens.
|
|
2067
|
+
|
|
2041
2068
|
Parameters
|
|
2042
2069
|
----------
|
|
2043
2070
|
backend:
|
|
@@ -2066,9 +2093,21 @@ def login(
|
|
|
2066
2093
|
mainsequence login --access-token "$TOKEN" --refresh-token "$REFRESH"
|
|
2067
2094
|
mainsequence login --access-token "$TOKEN" --refresh-token "$REFRESH" --backend http://127.0.0.1:8000 --projects-base mainsequence-dev
|
|
2068
2095
|
mainsequence login --export
|
|
2096
|
+
MAINSEQUENCE_AUTH_MODE=runtime_credential mainsequence login
|
|
2069
2097
|
```
|
|
2070
2098
|
"""
|
|
2071
2099
|
using_jwt = bool((access_token or "").strip() or (refresh_token or "").strip())
|
|
2100
|
+
using_runtime_credential = _runtime_credential_mode_enabled()
|
|
2101
|
+
|
|
2102
|
+
if using_runtime_credential and using_jwt:
|
|
2103
|
+
error(
|
|
2104
|
+
"Runtime credential login cannot be combined with "
|
|
2105
|
+
"--access-token/--refresh-token."
|
|
2106
|
+
)
|
|
2107
|
+
raise typer.Exit(1)
|
|
2108
|
+
|
|
2109
|
+
if using_runtime_credential and no_open:
|
|
2110
|
+
warn("--no-open is ignored when MAINSEQUENCE_AUTH_MODE=runtime_credential.")
|
|
2072
2111
|
|
|
2073
2112
|
if not using_jwt and backend and "@" in backend:
|
|
2074
2113
|
error(
|
|
@@ -2081,7 +2120,8 @@ def login(
|
|
|
2081
2120
|
if cfg.normalize_backend_url(backend) != cfg.normalize_backend_url(backend_option):
|
|
2082
2121
|
error("Pass backend either positionally or with --backend, not both.")
|
|
2083
2122
|
raise typer.Exit(1)
|
|
2084
|
-
|
|
2123
|
+
explicit_backend_input = backend_option if backend_option is not None else backend
|
|
2124
|
+
effective_backend_input = explicit_backend_input if explicit_backend_input is not None else cfg.STANDARD_BACKEND_URL
|
|
2085
2125
|
|
|
2086
2126
|
if projects_base and projects_base_option:
|
|
2087
2127
|
if cfg.normalize_mainsequence_path(projects_base) != cfg.normalize_mainsequence_path(projects_base_option):
|
|
@@ -2100,28 +2140,39 @@ def login(
|
|
|
2100
2140
|
raise typer.Exit(1)
|
|
2101
2141
|
|
|
2102
2142
|
current_backend = cfg.backend_url()
|
|
2103
|
-
normalized_backend = cfg.normalize_backend_url(effective_backend_input)
|
|
2143
|
+
normalized_backend = cfg.normalize_backend_url(effective_backend_input)
|
|
2104
2144
|
|
|
2105
|
-
if
|
|
2145
|
+
if explicit_backend_input is not None and normalized_backend != current_backend:
|
|
2106
2146
|
if not effective_projects_base_input:
|
|
2107
2147
|
error("When using a different backend, you must also specify a projects base folder.")
|
|
2108
2148
|
raise typer.Exit(1)
|
|
2109
2149
|
|
|
2110
2150
|
previous_backend_override = os.environ.get("MAIN_SEQUENCE_BACKEND_URL")
|
|
2111
|
-
|
|
2112
|
-
os.environ["MAIN_SEQUENCE_BACKEND_URL"] = normalized_backend
|
|
2151
|
+
os.environ["MAIN_SEQUENCE_BACKEND_URL"] = normalized_backend
|
|
2113
2152
|
|
|
2114
2153
|
try:
|
|
2115
|
-
if
|
|
2154
|
+
if using_runtime_credential:
|
|
2155
|
+
access = _exchange_runtime_credential_for_cli_login(normalized_backend)
|
|
2156
|
+
persisted = cfg.save_tokens("", access, "")
|
|
2157
|
+
res = {
|
|
2158
|
+
"username": "",
|
|
2159
|
+
"backend": normalized_backend,
|
|
2160
|
+
"access": access,
|
|
2161
|
+
"refresh": "",
|
|
2162
|
+
"persisted": bool(persisted),
|
|
2163
|
+
"auth_mode": "runtime_credential",
|
|
2164
|
+
}
|
|
2165
|
+
elif using_jwt:
|
|
2116
2166
|
os.environ.pop(cfg.ENV_USERNAME, None)
|
|
2117
2167
|
os.environ.pop(cfg.LEGACY_ENV_USERNAME, None)
|
|
2118
2168
|
persisted = cfg.save_tokens("", (access_token or "").strip(), (refresh_token or "").strip())
|
|
2119
2169
|
res = {
|
|
2120
2170
|
"username": "",
|
|
2121
|
-
"backend": normalized_backend
|
|
2171
|
+
"backend": normalized_backend,
|
|
2122
2172
|
"access": (access_token or "").strip(),
|
|
2123
2173
|
"refresh": (refresh_token or "").strip(),
|
|
2124
2174
|
"persisted": bool(persisted),
|
|
2175
|
+
"auth_mode": "jwt",
|
|
2125
2176
|
}
|
|
2126
2177
|
else:
|
|
2127
2178
|
def _emit_auth_url(url: str) -> None:
|
|
@@ -2146,10 +2197,11 @@ def login(
|
|
|
2146
2197
|
|
|
2147
2198
|
res = {
|
|
2148
2199
|
"username": username,
|
|
2149
|
-
"backend": normalized_backend
|
|
2200
|
+
"backend": normalized_backend,
|
|
2150
2201
|
"access": access,
|
|
2151
2202
|
"refresh": refresh,
|
|
2152
2203
|
"persisted": bool(persisted),
|
|
2204
|
+
"auth_mode": "jwt",
|
|
2153
2205
|
}
|
|
2154
2206
|
except BrowserAuthError as e:
|
|
2155
2207
|
error(f"Browser login failed: {e}")
|
|
@@ -2158,26 +2210,26 @@ def login(
|
|
|
2158
2210
|
error(f"Login failed: {e}")
|
|
2159
2211
|
raise typer.Exit(1) from e
|
|
2160
2212
|
finally:
|
|
2161
|
-
if
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
os.environ["MAIN_SEQUENCE_BACKEND_URL"] = previous_backend_override
|
|
2213
|
+
if previous_backend_override is None:
|
|
2214
|
+
os.environ.pop("MAIN_SEQUENCE_BACKEND_URL", None)
|
|
2215
|
+
else:
|
|
2216
|
+
os.environ["MAIN_SEQUENCE_BACKEND_URL"] = previous_backend_override
|
|
2166
2217
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
)
|
|
2172
|
-
else:
|
|
2173
|
-
cfg.clear_session_overrides()
|
|
2218
|
+
cfg.set_session_overrides(
|
|
2219
|
+
backend_url=normalized_backend,
|
|
2220
|
+
mainsequence_path=effective_projects_base_input,
|
|
2221
|
+
)
|
|
2174
2222
|
|
|
2175
2223
|
if export:
|
|
2176
2224
|
access = (res.get("access") or "").replace('"', '\\"')
|
|
2177
2225
|
refresh = (res.get("refresh") or "").replace('"', '\\"')
|
|
2178
2226
|
username = (res.get("username") or "").replace('"', '\\"')
|
|
2227
|
+
auth_mode = (res.get("auth_mode") or "").replace('"', '\\"')
|
|
2228
|
+
if auth_mode:
|
|
2229
|
+
typer.echo(f'export MAINSEQUENCE_AUTH_MODE="{auth_mode}"')
|
|
2179
2230
|
typer.echo(f'export MAINSEQUENCE_ACCESS_TOKEN="{access}"')
|
|
2180
|
-
|
|
2231
|
+
if refresh:
|
|
2232
|
+
typer.echo(f'export MAINSEQUENCE_REFRESH_TOKEN="{refresh}"')
|
|
2181
2233
|
if username:
|
|
2182
2234
|
typer.echo(f'export MAINSEQUENCE_USERNAME="{username}"')
|
|
2183
2235
|
return
|
|
@@ -2188,11 +2240,16 @@ def login(
|
|
|
2188
2240
|
typer.echo("MAIN SEQUENCE")
|
|
2189
2241
|
if res.get("username"):
|
|
2190
2242
|
success(f"Signed in as {res['username']} (Backend: {res['backend']})")
|
|
2243
|
+
elif res.get("auth_mode") == "runtime_credential":
|
|
2244
|
+
success(f"Signed in with runtime credential (Backend: {res['backend']})")
|
|
2191
2245
|
else:
|
|
2192
2246
|
success(f"Signed in with JWT tokens (Backend: {res['backend']})")
|
|
2193
2247
|
info(f"Projects base folder: {base}")
|
|
2194
2248
|
auth_store_label = cfg.auth_persistence_label()
|
|
2195
|
-
if res.get("
|
|
2249
|
+
if res.get("auth_mode") == "runtime_credential":
|
|
2250
|
+
info(f"Runtime credential access token is persisted in {auth_store_label}; no CLI JWT refresh token exists.")
|
|
2251
|
+
info("When the access token expires, CLI will re-exchange the runtime credential automatically.")
|
|
2252
|
+
elif res.get("persisted", True):
|
|
2196
2253
|
info(f"Auth tokens are persisted in {auth_store_label} for subsequent CLI commands.")
|
|
2197
2254
|
else:
|
|
2198
2255
|
warn(f"Could not persist auth tokens in {auth_store_label}. Use --export for shell-based auth.")
|
|
@@ -2815,7 +2872,7 @@ def settings_set_base(path: str = typer.Argument(..., help="New projects base fo
|
|
|
2815
2872
|
|
|
2816
2873
|
@settings.command("set-backend")
|
|
2817
2874
|
def settings_set_backend(
|
|
2818
|
-
url: str = typer.Argument(..., help="Backend base URL, e.g.
|
|
2875
|
+
url: str = typer.Argument(..., help=f"Backend base URL, e.g. {cfg.STANDARD_BACKEND_URL}")
|
|
2819
2876
|
):
|
|
2820
2877
|
"""
|
|
2821
2878
|
Set backend base URL used by CLI API calls.
|
|
@@ -2823,12 +2880,12 @@ def settings_set_backend(
|
|
|
2823
2880
|
Parameters
|
|
2824
2881
|
----------
|
|
2825
2882
|
url:
|
|
2826
|
-
Backend base URL
|
|
2883
|
+
Backend base URL.
|
|
2827
2884
|
|
|
2828
2885
|
Examples
|
|
2829
2886
|
--------
|
|
2830
2887
|
```bash
|
|
2831
|
-
mainsequence settings set-backend
|
|
2888
|
+
mainsequence settings set-backend <backend-url>
|
|
2832
2889
|
```
|
|
2833
2890
|
"""
|
|
2834
2891
|
out = cfg.set_backend_url(url)
|
|
@@ -2837,6 +2894,59 @@ def settings_set_backend(
|
|
|
2837
2894
|
success(f"Backend URL set to: {out.get('backend_url')}")
|
|
2838
2895
|
|
|
2839
2896
|
|
|
2897
|
+
def _settings_reset_impl() -> dict:
|
|
2898
|
+
"""
|
|
2899
|
+
Reset persistent CLI settings to standard defaults and clear session overrides.
|
|
2900
|
+
"""
|
|
2901
|
+
standard_backend = cfg.normalize_backend_url(cfg.STANDARD_BACKEND_URL)
|
|
2902
|
+
standard_base = cfg.normalize_mainsequence_path(cfg.DEFAULTS.get("mainsequence_path"))
|
|
2903
|
+
pathlib.Path(standard_base).mkdir(parents=True, exist_ok=True)
|
|
2904
|
+
out = cfg.set_config(
|
|
2905
|
+
{
|
|
2906
|
+
"backend_url": standard_backend,
|
|
2907
|
+
"mainsequence_path": standard_base,
|
|
2908
|
+
}
|
|
2909
|
+
)
|
|
2910
|
+
cfg.clear_session_overrides()
|
|
2911
|
+
return out
|
|
2912
|
+
|
|
2913
|
+
|
|
2914
|
+
@settings.command("reset")
|
|
2915
|
+
def settings_reset():
|
|
2916
|
+
"""
|
|
2917
|
+
Reset CLI settings to standard defaults.
|
|
2918
|
+
|
|
2919
|
+
Resets backend URL to the standard production backend, base folder to the
|
|
2920
|
+
default `~/mainsequence`, and clears current terminal session overrides.
|
|
2921
|
+
|
|
2922
|
+
Examples
|
|
2923
|
+
--------
|
|
2924
|
+
```bash
|
|
2925
|
+
mainsequence settings reset
|
|
2926
|
+
```
|
|
2927
|
+
"""
|
|
2928
|
+
out = _settings_reset_impl()
|
|
2929
|
+
if _emit_json(out):
|
|
2930
|
+
return
|
|
2931
|
+
success("Settings reset to standard defaults.")
|
|
2932
|
+
info(f"Backend URL: {out.get('backend_url')}")
|
|
2933
|
+
info(f"Projects base folder: {out.get('mainsequence_path')}")
|
|
2934
|
+
|
|
2935
|
+
|
|
2936
|
+
@settings.command("refresh")
|
|
2937
|
+
def settings_refresh():
|
|
2938
|
+
"""
|
|
2939
|
+
Alias for `settings reset`.
|
|
2940
|
+
|
|
2941
|
+
Examples
|
|
2942
|
+
--------
|
|
2943
|
+
```bash
|
|
2944
|
+
mainsequence settings refresh
|
|
2945
|
+
```
|
|
2946
|
+
"""
|
|
2947
|
+
settings_reset()
|
|
2948
|
+
|
|
2949
|
+
|
|
2840
2950
|
# ---------- sdk group ----------
|
|
2841
2951
|
|
|
2842
2952
|
|
|
@@ -21,6 +21,8 @@ import subprocess
|
|
|
21
21
|
import sys
|
|
22
22
|
import time
|
|
23
23
|
|
|
24
|
+
from mainsequence.defaults import STANDARD_BACKEND_URL
|
|
25
|
+
|
|
24
26
|
APP_NAME = "MainSequenceCLI"
|
|
25
27
|
|
|
26
28
|
|
|
@@ -62,7 +64,7 @@ KEYCHAIN_SERVICE = "MainSequenceCLI.auth"
|
|
|
62
64
|
KEYCHAIN_ACCOUNT = "default"
|
|
63
65
|
|
|
64
66
|
DEFAULTS = {
|
|
65
|
-
"backend_url": os.environ.get("MAIN_SEQUENCE_BACKEND_URL", "
|
|
67
|
+
"backend_url": os.environ.get("MAIN_SEQUENCE_BACKEND_URL", f"{STANDARD_BACKEND_URL}/"),
|
|
66
68
|
"mainsequence_path": str(pathlib.Path.home() / "mainsequence"),
|
|
67
69
|
"version": 1,
|
|
68
70
|
}
|
|
@@ -212,7 +214,7 @@ def set_backend_url(url: str) -> dict:
|
|
|
212
214
|
Convenience helper to set backend_url in config.json.
|
|
213
215
|
|
|
214
216
|
Args:
|
|
215
|
-
url: Backend base URL
|
|
217
|
+
url: Backend base URL.
|
|
216
218
|
|
|
217
219
|
Returns:
|
|
218
220
|
dict: updated config
|
|
@@ -350,12 +352,13 @@ def get_tokens() -> dict:
|
|
|
350
352
|
"""
|
|
351
353
|
Return auth tokens from environment variables, with persistent-store fallback.
|
|
352
354
|
"""
|
|
355
|
+
runtime_mode = (os.environ.get("MAINSEQUENCE_AUTH_MODE") or "").strip().lower() == "runtime_credential"
|
|
353
356
|
tokens = {
|
|
354
357
|
"username": os.environ.get(ENV_USERNAME) or os.environ.get(LEGACY_ENV_USERNAME, ""),
|
|
355
358
|
"access": os.environ.get(ENV_ACCESS) or os.environ.get(LEGACY_ENV_ACCESS, ""),
|
|
356
359
|
"refresh": os.environ.get(ENV_REFRESH) or os.environ.get(LEGACY_ENV_REFRESH, ""),
|
|
357
360
|
}
|
|
358
|
-
if tokens["access"] and tokens["refresh"]:
|
|
361
|
+
if tokens["access"] and (tokens["refresh"] or runtime_mode):
|
|
359
362
|
return tokens
|
|
360
363
|
|
|
361
364
|
for secret in (_read_secure_tokens(), _read_local_tokens()):
|
|
@@ -366,7 +369,7 @@ def get_tokens() -> dict:
|
|
|
366
369
|
"access": tokens["access"] or secret.get("access", ""),
|
|
367
370
|
"refresh": tokens["refresh"] or secret.get("refresh", ""),
|
|
368
371
|
}
|
|
369
|
-
if tokens["access"] and tokens["refresh"]:
|
|
372
|
+
if tokens["access"] and (tokens["refresh"] or runtime_mode):
|
|
370
373
|
break
|
|
371
374
|
return tokens
|
|
372
375
|
|
|
@@ -7,6 +7,8 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
9
|
|
|
10
|
+
from mainsequence.defaults import STANDARD_BACKEND_URL
|
|
11
|
+
|
|
10
12
|
from .exceptions import raise_for_response
|
|
11
13
|
from .utils import AuthLoaders, build_session
|
|
12
14
|
from .utils import loaders as _global_loaders
|
|
@@ -19,7 +21,7 @@ class MainSequenceClientConfig:
|
|
|
19
21
|
endpoint: str = (
|
|
20
22
|
os.getenv("MAINSEQUENCE_ENDPOINT")
|
|
21
23
|
or os.getenv("TDAG_ENDPOINT")
|
|
22
|
-
or
|
|
24
|
+
or STANDARD_BACKEND_URL
|
|
23
25
|
)
|
|
24
26
|
api_prefix: str = "/orm/api"
|
|
25
27
|
# (connect, read) — align with your utils DEFAULT_TIMEOUT behavior
|
{mainsequence-3.18.2 → mainsequence-3.18.7}/mainsequence/client/command_center/workspace_snapshot.py
RENAMED
|
@@ -7,6 +7,8 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
from urllib.parse import urlparse
|
|
9
9
|
|
|
10
|
+
from mainsequence.defaults import STANDARD_BACKEND_URL
|
|
11
|
+
|
|
10
12
|
from ..client import MainSequenceClientConfig
|
|
11
13
|
from ..utils import AuthLoaders
|
|
12
14
|
|
|
@@ -78,7 +80,7 @@ def _infer_command_center_url_from_endpoint(endpoint: str) -> str:
|
|
|
78
80
|
if host in {"localhost", "127.0.0.1"}:
|
|
79
81
|
return f"{scheme}://localhost:5173"
|
|
80
82
|
|
|
81
|
-
if host ==
|
|
83
|
+
if host == (urlparse(STANDARD_BACKEND_URL).hostname or ""):
|
|
82
84
|
return f"{scheme}://main-sequence.app"
|
|
83
85
|
|
|
84
86
|
if host.startswith("api."):
|
|
@@ -649,6 +649,13 @@ class SimpleTableUpdate(TableUpdateNode, BaseObjectOrm):
|
|
|
649
649
|
description="Simple table storage referenced by this update.",
|
|
650
650
|
)
|
|
651
651
|
tags: list[str] | None = Field(default=[], description="List of tags")
|
|
652
|
+
labels: list[str] = Field(
|
|
653
|
+
default_factory=list,
|
|
654
|
+
description=(
|
|
655
|
+
"Organizational labels attached to the simple table update. "
|
|
656
|
+
"These are helpers for grouping and discovery only and do not change runtime behavior or functionality."
|
|
657
|
+
),
|
|
658
|
+
)
|
|
652
659
|
description: str | None = Field(None, description="Optional HTML description")
|
|
653
660
|
update_details: SimpleTableUpdateDetails | int | None = None
|
|
654
661
|
run_configuration: SimpleTableRunConfiguration | None = None
|
|
@@ -275,6 +275,13 @@ class DataNodeUpdate(TableUpdateNode, BaseObjectOrm):
|
|
|
275
275
|
|
|
276
276
|
data_node_storage: int | DataNodeStorage
|
|
277
277
|
tags: list[str] | None = Field(default=[], description="List of tags")
|
|
278
|
+
labels: list[str] = Field(
|
|
279
|
+
default_factory=list,
|
|
280
|
+
description=(
|
|
281
|
+
"Organizational labels attached to the local time series update. "
|
|
282
|
+
"These are helpers for grouping and discovery only and do not change runtime behavior or functionality."
|
|
283
|
+
),
|
|
284
|
+
)
|
|
278
285
|
description: str | None = Field(None, description="Optional HTML description")
|
|
279
286
|
update_details: DataNodeUpdateDetails | int | None = None
|
|
280
287
|
run_configuration: RunConfiguration | None = None
|
|
@@ -1160,6 +1167,59 @@ class DataNodeStorage(AbstractTable, LabelableObjectMixin, ShareableObjectMixin,
|
|
|
1160
1167
|
|
|
1161
1168
|
return r.json() if r.content else None
|
|
1162
1169
|
|
|
1170
|
+
def delete_after_date(
|
|
1171
|
+
self,
|
|
1172
|
+
after_date: str | datetime.datetime,
|
|
1173
|
+
*,
|
|
1174
|
+
unique_identifier: str | None = None,
|
|
1175
|
+
unique_identifier_list: list[str] | None = None,
|
|
1176
|
+
timeout: int | None = None,
|
|
1177
|
+
) -> dict[str, Any]:
|
|
1178
|
+
"""
|
|
1179
|
+
Delete rows at or after a cutoff timestamp from this dynamic table.
|
|
1180
|
+
|
|
1181
|
+
This is a backend tail-delete operation:
|
|
1182
|
+
|
|
1183
|
+
- it hits `POST /orm/api/ts_manager/dynamic_table/{id}/delete_after_date/`
|
|
1184
|
+
- `after_date` is the inclusive cutoff
|
|
1185
|
+
- there is no `end_date`; this is not arbitrary range deletion
|
|
1186
|
+
- for multi-index tables, pass either `unique_identifier` for one
|
|
1187
|
+
identifier or `unique_identifier_list` for multiple identifiers to
|
|
1188
|
+
scope the tail delete
|
|
1189
|
+
|
|
1190
|
+
The authenticated user must have edit access to this DynamicTableMetaData.
|
|
1191
|
+
|
|
1192
|
+
The returned payload contains the authoritative post-delete table stats,
|
|
1193
|
+
including `deleted_count`, `table_empty`, and index metadata. Consumers
|
|
1194
|
+
can use those stats to update visible table metadata or refetch the table
|
|
1195
|
+
detail after the delete.
|
|
1196
|
+
"""
|
|
1197
|
+
if self.id is None:
|
|
1198
|
+
raise ValueError("DataNodeStorage must have an id before deleting rows after a date.")
|
|
1199
|
+
if unique_identifier is not None and unique_identifier_list is not None:
|
|
1200
|
+
raise ValueError("Pass either unique_identifier or unique_identifier_list, not both.")
|
|
1201
|
+
|
|
1202
|
+
payload_body: dict[str, Any] = {
|
|
1203
|
+
"after_date": after_date.isoformat() if isinstance(after_date, datetime.datetime) else after_date
|
|
1204
|
+
}
|
|
1205
|
+
if unique_identifier is not None:
|
|
1206
|
+
payload_body["unique_identifier"] = unique_identifier
|
|
1207
|
+
if unique_identifier_list is not None:
|
|
1208
|
+
payload_body["unique_identifier_list"] = unique_identifier_list
|
|
1209
|
+
|
|
1210
|
+
cls = type(self)
|
|
1211
|
+
url = f"{cls.get_object_url()}/{self.id}/delete_after_date/"
|
|
1212
|
+
r = make_request(
|
|
1213
|
+
s=cls.build_session(),
|
|
1214
|
+
loaders=cls.LOADERS,
|
|
1215
|
+
r_type="POST",
|
|
1216
|
+
url=url,
|
|
1217
|
+
payload={"json": payload_body},
|
|
1218
|
+
time_out=timeout,
|
|
1219
|
+
)
|
|
1220
|
+
raise_for_response(r, payload=payload_body)
|
|
1221
|
+
return r.json()
|
|
1222
|
+
|
|
1163
1223
|
def delete_table(self):
|
|
1164
1224
|
data_source = PodDataSource._get_duck_db()
|
|
1165
1225
|
duckdb_dynamic_data_source = DynamicTableDataSource.get_or_create_duck_db(
|
|
@@ -8,6 +8,7 @@ from typing import Any, ClassVar, Literal
|
|
|
8
8
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
|
+
from mainsequence.defaults import STANDARD_BACKEND_URL
|
|
11
12
|
from mainsequence.logconf import logger
|
|
12
13
|
|
|
13
14
|
from .base import (
|
|
@@ -141,7 +142,7 @@ class Organization(BasePydanticModel):
|
|
|
141
142
|
None,
|
|
142
143
|
title="Organization URL",
|
|
143
144
|
description="Optional public or internal URL for the organization.",
|
|
144
|
-
examples=[
|
|
145
|
+
examples=[STANDARD_BACKEND_URL],
|
|
145
146
|
)
|
|
146
147
|
organization_domain: str = Field(
|
|
147
148
|
...,
|