mainsequence 3.18.2__tar.gz → 3.18.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-3.18.2 → mainsequence-3.18.3}/PKG-INFO +1 -1
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/api.py +30 -2
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/cli.py +63 -3
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/config.py +3 -2
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/utils.py +117 -1
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/logconf.py +48 -9
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-3.18.2 → mainsequence-3.18.3}/pyproject.toml +1 -1
- mainsequence-3.18.3/tests/test_auth_precedence.py +537 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_cli.py +181 -2
- mainsequence-3.18.2/tests/test_auth_precedence.py +0 -214
- {mainsequence-3.18.2 → mainsequence-3.18.3}/LICENSE +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/README.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/data_publishing/simple_tables/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/maintenance/local_journal/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/markets_platform/assets_and_translation/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/markets_platform/instruments_and_pricing/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/markets_platform/virtualfundbuilder/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/__main__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/bootstrap.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/ui.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/agent.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/base.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/client.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/data_sources_interfaces/timescale.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_simple_tables.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_tdag.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_user.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_vam.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/compute_validation.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/config.toml +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_1_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_2_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_3_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_4_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_5_base64.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/logo.png +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/asset_select.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/date_settings.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/logged_user.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/core/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/core/theme.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/instruments/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/instruments/streamlit_form_factory.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/scaffold.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/data_interface/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/data_interface/data_interface.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/base_instrument.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/bond.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/callability.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/interest_rate_swap.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/json_codec.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/position.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/ql_fields.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/curve_codec.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/registry.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/bond_pricer.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/indices.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/indices_builders.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/swap_pricer.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/settings.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/__main__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/base_persist_managers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/config.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/configuration_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/build_operations.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/namespacing.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/future_registry.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/pydantic_metadata.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/filters.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/persist_managers.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/schema.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/table_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/prices/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/prices/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/enums.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/portfolio_nodes.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/utils.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/setup.cfg +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_client.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_command_center_models.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_dependency_extras.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_filter_normalization.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_instruments.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_logconf.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_logged_user_components.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_run_configuration.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_simple_tables_configuration_hashing.py +0 -0
- {mainsequence-3.18.2 → mainsequence-3.18.3}/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:
|
|
@@ -2038,6 +2060,10 @@ def login(
|
|
|
2038
2060
|
Interactive login uses browser-based authentication and finishes with
|
|
2039
2061
|
standard JWT access/refresh tokens persisted by the CLI.
|
|
2040
2062
|
|
|
2063
|
+
If `MAINSEQUENCE_AUTH_MODE=runtime_credential`, login exchanges the
|
|
2064
|
+
configured runtime credential for a short-lived access token instead of
|
|
2065
|
+
opening the browser or persisting CLI JWT tokens.
|
|
2066
|
+
|
|
2041
2067
|
Parameters
|
|
2042
2068
|
----------
|
|
2043
2069
|
backend:
|
|
@@ -2066,9 +2092,21 @@ def login(
|
|
|
2066
2092
|
mainsequence login --access-token "$TOKEN" --refresh-token "$REFRESH"
|
|
2067
2093
|
mainsequence login --access-token "$TOKEN" --refresh-token "$REFRESH" --backend http://127.0.0.1:8000 --projects-base mainsequence-dev
|
|
2068
2094
|
mainsequence login --export
|
|
2095
|
+
MAINSEQUENCE_AUTH_MODE=runtime_credential mainsequence login
|
|
2069
2096
|
```
|
|
2070
2097
|
"""
|
|
2071
2098
|
using_jwt = bool((access_token or "").strip() or (refresh_token or "").strip())
|
|
2099
|
+
using_runtime_credential = _runtime_credential_mode_enabled()
|
|
2100
|
+
|
|
2101
|
+
if using_runtime_credential and using_jwt:
|
|
2102
|
+
error(
|
|
2103
|
+
"Runtime credential login cannot be combined with "
|
|
2104
|
+
"--access-token/--refresh-token."
|
|
2105
|
+
)
|
|
2106
|
+
raise typer.Exit(1)
|
|
2107
|
+
|
|
2108
|
+
if using_runtime_credential and no_open:
|
|
2109
|
+
warn("--no-open is ignored when MAINSEQUENCE_AUTH_MODE=runtime_credential.")
|
|
2072
2110
|
|
|
2073
2111
|
if not using_jwt and backend and "@" in backend:
|
|
2074
2112
|
error(
|
|
@@ -2112,7 +2150,18 @@ def login(
|
|
|
2112
2150
|
os.environ["MAIN_SEQUENCE_BACKEND_URL"] = normalized_backend
|
|
2113
2151
|
|
|
2114
2152
|
try:
|
|
2115
|
-
if
|
|
2153
|
+
if using_runtime_credential:
|
|
2154
|
+
access = _exchange_runtime_credential_for_cli_login(normalized_backend or current_backend)
|
|
2155
|
+
persisted = cfg.save_tokens("", access, "")
|
|
2156
|
+
res = {
|
|
2157
|
+
"username": "",
|
|
2158
|
+
"backend": normalized_backend or current_backend,
|
|
2159
|
+
"access": access,
|
|
2160
|
+
"refresh": "",
|
|
2161
|
+
"persisted": bool(persisted),
|
|
2162
|
+
"auth_mode": "runtime_credential",
|
|
2163
|
+
}
|
|
2164
|
+
elif using_jwt:
|
|
2116
2165
|
os.environ.pop(cfg.ENV_USERNAME, None)
|
|
2117
2166
|
os.environ.pop(cfg.LEGACY_ENV_USERNAME, None)
|
|
2118
2167
|
persisted = cfg.save_tokens("", (access_token or "").strip(), (refresh_token or "").strip())
|
|
@@ -2122,6 +2171,7 @@ def login(
|
|
|
2122
2171
|
"access": (access_token or "").strip(),
|
|
2123
2172
|
"refresh": (refresh_token or "").strip(),
|
|
2124
2173
|
"persisted": bool(persisted),
|
|
2174
|
+
"auth_mode": "jwt",
|
|
2125
2175
|
}
|
|
2126
2176
|
else:
|
|
2127
2177
|
def _emit_auth_url(url: str) -> None:
|
|
@@ -2150,6 +2200,7 @@ def login(
|
|
|
2150
2200
|
"access": access,
|
|
2151
2201
|
"refresh": refresh,
|
|
2152
2202
|
"persisted": bool(persisted),
|
|
2203
|
+
"auth_mode": "jwt",
|
|
2153
2204
|
}
|
|
2154
2205
|
except BrowserAuthError as e:
|
|
2155
2206
|
error(f"Browser login failed: {e}")
|
|
@@ -2176,8 +2227,12 @@ def login(
|
|
|
2176
2227
|
access = (res.get("access") or "").replace('"', '\\"')
|
|
2177
2228
|
refresh = (res.get("refresh") or "").replace('"', '\\"')
|
|
2178
2229
|
username = (res.get("username") or "").replace('"', '\\"')
|
|
2230
|
+
auth_mode = (res.get("auth_mode") or "").replace('"', '\\"')
|
|
2231
|
+
if auth_mode:
|
|
2232
|
+
typer.echo(f'export MAINSEQUENCE_AUTH_MODE="{auth_mode}"')
|
|
2179
2233
|
typer.echo(f'export MAINSEQUENCE_ACCESS_TOKEN="{access}"')
|
|
2180
|
-
|
|
2234
|
+
if refresh:
|
|
2235
|
+
typer.echo(f'export MAINSEQUENCE_REFRESH_TOKEN="{refresh}"')
|
|
2181
2236
|
if username:
|
|
2182
2237
|
typer.echo(f'export MAINSEQUENCE_USERNAME="{username}"')
|
|
2183
2238
|
return
|
|
@@ -2188,11 +2243,16 @@ def login(
|
|
|
2188
2243
|
typer.echo("MAIN SEQUENCE")
|
|
2189
2244
|
if res.get("username"):
|
|
2190
2245
|
success(f"Signed in as {res['username']} (Backend: {res['backend']})")
|
|
2246
|
+
elif res.get("auth_mode") == "runtime_credential":
|
|
2247
|
+
success(f"Signed in with runtime credential (Backend: {res['backend']})")
|
|
2191
2248
|
else:
|
|
2192
2249
|
success(f"Signed in with JWT tokens (Backend: {res['backend']})")
|
|
2193
2250
|
info(f"Projects base folder: {base}")
|
|
2194
2251
|
auth_store_label = cfg.auth_persistence_label()
|
|
2195
|
-
if res.get("
|
|
2252
|
+
if res.get("auth_mode") == "runtime_credential":
|
|
2253
|
+
info(f"Runtime credential access token is persisted in {auth_store_label}; no CLI JWT refresh token exists.")
|
|
2254
|
+
info("When the access token expires, CLI will re-exchange the runtime credential automatically.")
|
|
2255
|
+
elif res.get("persisted", True):
|
|
2196
2256
|
info(f"Auth tokens are persisted in {auth_store_label} for subsequent CLI commands.")
|
|
2197
2257
|
else:
|
|
2198
2258
|
warn(f"Could not persist auth tokens in {auth_store_label}. Use --export for shell-based auth.")
|
|
@@ -350,12 +350,13 @@ def get_tokens() -> dict:
|
|
|
350
350
|
"""
|
|
351
351
|
Return auth tokens from environment variables, with persistent-store fallback.
|
|
352
352
|
"""
|
|
353
|
+
runtime_mode = (os.environ.get("MAINSEQUENCE_AUTH_MODE") or "").strip().lower() == "runtime_credential"
|
|
353
354
|
tokens = {
|
|
354
355
|
"username": os.environ.get(ENV_USERNAME) or os.environ.get(LEGACY_ENV_USERNAME, ""),
|
|
355
356
|
"access": os.environ.get(ENV_ACCESS) or os.environ.get(LEGACY_ENV_ACCESS, ""),
|
|
356
357
|
"refresh": os.environ.get(ENV_REFRESH) or os.environ.get(LEGACY_ENV_REFRESH, ""),
|
|
357
358
|
}
|
|
358
|
-
if tokens["access"] and tokens["refresh"]:
|
|
359
|
+
if tokens["access"] and (tokens["refresh"] or runtime_mode):
|
|
359
360
|
return tokens
|
|
360
361
|
|
|
361
362
|
for secret in (_read_secure_tokens(), _read_local_tokens()):
|
|
@@ -366,7 +367,7 @@ def get_tokens() -> dict:
|
|
|
366
367
|
"access": tokens["access"] or secret.get("access", ""),
|
|
367
368
|
"refresh": tokens["refresh"] or secret.get("refresh", ""),
|
|
368
369
|
}
|
|
369
|
-
if tokens["access"] and tokens["refresh"]:
|
|
370
|
+
if tokens["access"] and (tokens["refresh"] or runtime_mode):
|
|
370
371
|
break
|
|
371
372
|
return tokens
|
|
372
373
|
|
|
@@ -86,6 +86,9 @@ def _default_auth_provider_kind() -> str | None:
|
|
|
86
86
|
has_access = _env_has_value("MAINSEQUENCE_ACCESS_TOKEN")
|
|
87
87
|
has_refresh = _env_has_value("MAINSEQUENCE_REFRESH_TOKEN")
|
|
88
88
|
|
|
89
|
+
if mode == "runtime_credential":
|
|
90
|
+
return "runtime_credential"
|
|
91
|
+
|
|
89
92
|
if mode == "session_jwt":
|
|
90
93
|
if has_access or has_refresh:
|
|
91
94
|
return "session_jwt"
|
|
@@ -176,6 +179,114 @@ class SessionJWTAuthProvider(BaseAuthProvider):
|
|
|
176
179
|
return None
|
|
177
180
|
|
|
178
181
|
|
|
182
|
+
@dataclass
|
|
183
|
+
class RuntimeCredentialAuthProvider(BaseAuthProvider):
|
|
184
|
+
credential_id: str | None = None
|
|
185
|
+
credential_secret: str | None = None
|
|
186
|
+
token_url: str = f"{API_ENDPOINT}/pods/runtime-credentials/token/"
|
|
187
|
+
token_type: str = "Bearer"
|
|
188
|
+
refresh_skew_seconds: int = 30
|
|
189
|
+
timeout: tuple[float, float] = DEFAULT_TIMEOUT
|
|
190
|
+
expires_at: float | None = None
|
|
191
|
+
_lock: threading.RLock = field(default_factory=threading.RLock, init=False, repr=False)
|
|
192
|
+
|
|
193
|
+
def __post_init__(self):
|
|
194
|
+
if self.credential_id is None:
|
|
195
|
+
self.credential_id = os.getenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_ID")
|
|
196
|
+
if self.credential_secret is None:
|
|
197
|
+
self.credential_secret = os.getenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_SECRET")
|
|
198
|
+
|
|
199
|
+
def _current_access_token(self) -> str | None:
|
|
200
|
+
return (os.getenv("MAINSEQUENCE_ACCESS_TOKEN") or "").strip() or None
|
|
201
|
+
|
|
202
|
+
def _needs_exchange(self) -> bool:
|
|
203
|
+
access_token = self._current_access_token()
|
|
204
|
+
if not access_token:
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
if self.expires_at is not None:
|
|
208
|
+
return self.expires_at <= time.time() + self.refresh_skew_seconds
|
|
209
|
+
|
|
210
|
+
exp = _decode_jwt_exp(access_token)
|
|
211
|
+
if exp is None:
|
|
212
|
+
# Access-only JWT behavior: use opaque/uninspectable access until a 401 forces exchange.
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
return exp <= int(time.time()) + self.refresh_skew_seconds
|
|
216
|
+
|
|
217
|
+
def _require_credentials(self) -> tuple[str, str]:
|
|
218
|
+
credential_id = (self.credential_id or "").strip()
|
|
219
|
+
credential_secret = (self.credential_secret or "").strip()
|
|
220
|
+
if not credential_id:
|
|
221
|
+
raise AuthError(
|
|
222
|
+
"MAINSEQUENCE_RUNTIME_CREDENTIAL_ID is required when "
|
|
223
|
+
"MAINSEQUENCE_AUTH_MODE=runtime_credential."
|
|
224
|
+
)
|
|
225
|
+
if not credential_secret:
|
|
226
|
+
raise AuthError(
|
|
227
|
+
"MAINSEQUENCE_RUNTIME_CREDENTIAL_SECRET is required when "
|
|
228
|
+
"MAINSEQUENCE_AUTH_MODE=runtime_credential."
|
|
229
|
+
)
|
|
230
|
+
return credential_id, credential_secret
|
|
231
|
+
|
|
232
|
+
def refresh(
|
|
233
|
+
self,
|
|
234
|
+
*,
|
|
235
|
+
force: bool = False,
|
|
236
|
+
session: requests.Session | None = None,
|
|
237
|
+
) -> None:
|
|
238
|
+
_ = session
|
|
239
|
+
with self._lock:
|
|
240
|
+
if not force and not self._needs_exchange():
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
credential_id, credential_secret = self._require_credentials()
|
|
244
|
+
response = requests.post(
|
|
245
|
+
self.token_url,
|
|
246
|
+
json={
|
|
247
|
+
"credential_id": credential_id,
|
|
248
|
+
"credential_secret": credential_secret,
|
|
249
|
+
},
|
|
250
|
+
headers={"Content-Type": "application/json"},
|
|
251
|
+
timeout=self.timeout,
|
|
252
|
+
)
|
|
253
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
254
|
+
raise AuthError(
|
|
255
|
+
"Runtime credential exchange failed with status "
|
|
256
|
+
f"{response.status_code}."
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
data = response.json()
|
|
260
|
+
access = str(data.get("access") or "").strip()
|
|
261
|
+
if not access:
|
|
262
|
+
raise AuthError("Runtime credential exchange response did not include access token.")
|
|
263
|
+
|
|
264
|
+
token_type = str(data.get("token_type") or self.token_type or "Bearer").strip()
|
|
265
|
+
self.token_type = token_type or "Bearer"
|
|
266
|
+
|
|
267
|
+
expires_in_raw = data.get("expires_in")
|
|
268
|
+
try:
|
|
269
|
+
expires_in = int(expires_in_raw)
|
|
270
|
+
except (TypeError, ValueError):
|
|
271
|
+
expires_in = None
|
|
272
|
+
self.expires_at = time.time() + expires_in if expires_in and expires_in > 0 else None
|
|
273
|
+
os.environ["MAINSEQUENCE_ACCESS_TOKEN"] = access
|
|
274
|
+
|
|
275
|
+
def get_headers(self) -> CaseInsensitiveDict:
|
|
276
|
+
if self._needs_exchange():
|
|
277
|
+
self.refresh(force=False)
|
|
278
|
+
|
|
279
|
+
access_token = self._current_access_token()
|
|
280
|
+
if not access_token:
|
|
281
|
+
raise AuthError("MAINSEQUENCE_ACCESS_TOKEN is missing after runtime credential exchange.")
|
|
282
|
+
|
|
283
|
+
return CaseInsensitiveDict(
|
|
284
|
+
{
|
|
285
|
+
"Authorization": f"{self.token_type} {access_token}",
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
179
290
|
@dataclass
|
|
180
291
|
class JWTAuthProvider(BaseAuthProvider):
|
|
181
292
|
access_token: str | None = None
|
|
@@ -310,6 +421,9 @@ def build_default_auth_provider() -> BaseAuthProvider:
|
|
|
310
421
|
if provider_kind == "session_jwt":
|
|
311
422
|
return SessionJWTAuthProvider()
|
|
312
423
|
|
|
424
|
+
if provider_kind == "runtime_credential":
|
|
425
|
+
return RuntimeCredentialAuthProvider()
|
|
426
|
+
|
|
313
427
|
if provider_kind == "jwt":
|
|
314
428
|
return JWTAuthProvider()
|
|
315
429
|
|
|
@@ -330,7 +444,9 @@ class AuthLoaders:
|
|
|
330
444
|
def _provider(self) -> BaseAuthProvider:
|
|
331
445
|
provider_kind = _default_auth_provider_kind()
|
|
332
446
|
|
|
333
|
-
if provider_kind == "
|
|
447
|
+
if provider_kind == "runtime_credential" and not isinstance(self.provider, RuntimeCredentialAuthProvider):
|
|
448
|
+
self.provider = RuntimeCredentialAuthProvider()
|
|
449
|
+
elif provider_kind == "session_jwt" and not isinstance(self.provider, SessionJWTAuthProvider):
|
|
334
450
|
self.provider = SessionJWTAuthProvider()
|
|
335
451
|
elif provider_kind == "jwt" and not isinstance(self.provider, JWTAuthProvider):
|
|
336
452
|
self.provider = JWTAuthProvider()
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
3
4
|
import logging
|
|
4
5
|
import logging.config
|
|
5
6
|
import os
|
|
7
|
+
import sys
|
|
8
|
+
import traceback
|
|
9
|
+
from collections.abc import Mapping
|
|
6
10
|
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
7
12
|
|
|
8
13
|
import requests
|
|
9
14
|
import structlog
|
|
10
15
|
from requests.structures import CaseInsensitiveDict
|
|
16
|
+
from structlog.contextvars import bind_contextvars, unbind_contextvars
|
|
11
17
|
from structlog.dev import ConsoleRenderer
|
|
18
|
+
from structlog.stdlib import BoundLogger
|
|
12
19
|
|
|
13
20
|
from .instrumentation import OTelJSONRenderer
|
|
14
21
|
from .runtime_flags import is_running_in_pod
|
|
15
22
|
|
|
16
23
|
logger = None
|
|
17
|
-
import inspect
|
|
18
|
-
import sys
|
|
19
|
-
import traceback
|
|
20
|
-
from collections.abc import Mapping
|
|
21
|
-
from typing import Any
|
|
22
|
-
|
|
23
|
-
from structlog.contextvars import bind_contextvars, unbind_contextvars
|
|
24
|
-
from structlog.stdlib import BoundLogger
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
def ensure_dir(file_path):
|
|
@@ -85,7 +84,44 @@ def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
|
|
|
85
84
|
|
|
86
85
|
return headers, False
|
|
87
86
|
|
|
87
|
+
def _exchange_runtime_credential() -> bool:
|
|
88
|
+
credential_id = (os.getenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_ID") or "").strip()
|
|
89
|
+
credential_secret = (os.getenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_SECRET") or "").strip()
|
|
90
|
+
if not credential_id or not credential_secret:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
token_resp = requests.post(
|
|
95
|
+
f"{_backend_base_url()}/orm/api/pods/runtime-credentials/token/",
|
|
96
|
+
headers={"Content-Type": "application/json"},
|
|
97
|
+
json={
|
|
98
|
+
"credential_id": credential_id,
|
|
99
|
+
"credential_secret": credential_secret,
|
|
100
|
+
},
|
|
101
|
+
timeout=timeout_s,
|
|
102
|
+
)
|
|
103
|
+
except Exception:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
if token_resp.status_code < 200 or token_resp.status_code >= 300:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
data = token_resp.json()
|
|
111
|
+
except Exception:
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
access_token = str(data.get("access") or "").strip()
|
|
115
|
+
if not access_token:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
os.environ["MAINSEQUENCE_ACCESS_TOKEN"] = access_token
|
|
119
|
+
return True
|
|
120
|
+
|
|
88
121
|
def _refresh_access_token() -> bool:
|
|
122
|
+
if auth_mode == "runtime_credential":
|
|
123
|
+
return _exchange_runtime_credential()
|
|
124
|
+
|
|
89
125
|
if auth_mode == "session_jwt":
|
|
90
126
|
return False
|
|
91
127
|
|
|
@@ -127,12 +163,15 @@ def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
|
|
|
127
163
|
)
|
|
128
164
|
|
|
129
165
|
if (
|
|
130
|
-
auth_mode
|
|
166
|
+
auth_mode == "jwt"
|
|
131
167
|
and not os.getenv("MAINSEQUENCE_ACCESS_TOKEN")
|
|
132
168
|
and os.getenv("MAINSEQUENCE_REFRESH_TOKEN")
|
|
133
169
|
):
|
|
134
170
|
_refresh_access_token()
|
|
135
171
|
|
|
172
|
+
if auth_mode == "runtime_credential" and not os.getenv("MAINSEQUENCE_ACCESS_TOKEN"):
|
|
173
|
+
_refresh_access_token()
|
|
174
|
+
|
|
136
175
|
headers, using_jwt = _auth_headers()
|
|
137
176
|
|
|
138
177
|
job_run_id = (os.getenv("JOB_RUN_ID") or "").strip()
|