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.
Files changed (179) hide show
  1. {mainsequence-3.18.2 → mainsequence-3.18.3}/PKG-INFO +1 -1
  2. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/api.py +30 -2
  3. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/cli.py +63 -3
  4. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/config.py +3 -2
  5. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/utils.py +117 -1
  6. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/logconf.py +48 -9
  7. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/PKG-INFO +1 -1
  8. {mainsequence-3.18.2 → mainsequence-3.18.3}/pyproject.toml +1 -1
  9. mainsequence-3.18.3/tests/test_auth_precedence.py +537 -0
  10. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_cli.py +181 -2
  11. mainsequence-3.18.2/tests/test_auth_precedence.py +0 -214
  12. {mainsequence-3.18.2 → mainsequence-3.18.3}/LICENSE +0 -0
  13. {mainsequence-3.18.2 → mainsequence-3.18.3}/README.md +0 -0
  14. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/AGENTS.md +0 -0
  15. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  16. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  17. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  18. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  19. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  20. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  21. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  22. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  23. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/data_publishing/simple_tables/SKILL.md +0 -0
  24. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  25. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/maintenance/local_journal/SKILL.md +0 -0
  26. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/markets_platform/assets_and_translation/SKILL.md +0 -0
  27. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/markets_platform/instruments_and_pricing/SKILL.md +0 -0
  28. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/markets_platform/virtualfundbuilder/SKILL.md +0 -0
  29. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  30. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  31. {mainsequence-3.18.2 → mainsequence-3.18.3}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  32. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/__init__.py +0 -0
  33. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/__main__.py +0 -0
  34. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/bootstrap.py +0 -0
  35. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/__init__.py +0 -0
  36. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/browser_auth.py +0 -0
  37. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/docker_utils.py +0 -0
  38. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/doctor.py +0 -0
  39. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/local_ops.py +0 -0
  40. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/model_filters.py +0 -0
  41. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/project_status.py +0 -0
  42. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/pydantic_cli.py +0 -0
  43. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/sdk_utils.py +0 -0
  44. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/ssh_utils.py +0 -0
  45. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/cli/ui.py +0 -0
  46. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/__init__.py +0 -0
  47. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/agent.py +0 -0
  48. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/agent_runtime_models.py +0 -0
  49. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/base.py +0 -0
  50. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/client.py +0 -0
  51. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/__init__.py +0 -0
  52. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/app_component.py +0 -0
  53. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/data_models.py +0 -0
  54. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/workspace.py +0 -0
  55. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  56. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  57. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  58. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/data_sources_interfaces/timescale.py +0 -0
  59. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/exceptions.py +0 -0
  60. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/fastapi/__init__.py +0 -0
  61. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/fastapi/auth.py +0 -0
  62. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_helpers.py +0 -0
  63. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_simple_tables.py +0 -0
  64. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_tdag.py +0 -0
  65. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_user.py +0 -0
  66. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/client/models_vam.py +0 -0
  67. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/compute_validation.py +0 -0
  68. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/__init__.py +0 -0
  69. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/__init__.py +0 -0
  70. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/config.toml +0 -0
  71. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
  72. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_1_base64.txt +0 -0
  73. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_2_base64.txt +0 -0
  74. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_3_base64.txt +0 -0
  75. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_4_base64.txt +0 -0
  76. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/image_5_base64.txt +0 -0
  77. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/assets/logo.png +0 -0
  78. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/__init__.py +0 -0
  79. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/asset_select.py +0 -0
  80. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/date_settings.py +0 -0
  81. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/components/logged_user.py +0 -0
  82. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/core/__init__.py +0 -0
  83. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/core/theme.py +0 -0
  84. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/instruments/__init__.py +0 -0
  85. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/instruments/streamlit_form_factory.py +0 -0
  86. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
  87. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/dashboards/streamlit/scaffold.py +0 -0
  88. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instrumentation/__init__.py +0 -0
  89. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instrumentation/utils.py +0 -0
  90. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/__init__.py +0 -0
  91. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/data_interface/__init__.py +0 -0
  92. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/data_interface/data_interface.py +0 -0
  93. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/__init__.py +0 -0
  94. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/base_instrument.py +0 -0
  95. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/bond.py +0 -0
  96. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/callability.py +0 -0
  97. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/interest_rate_swap.py +0 -0
  98. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/json_codec.py +0 -0
  99. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/position.py +0 -0
  100. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/instruments/ql_fields.py +0 -0
  101. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/__init__.py +0 -0
  102. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/__init__.py +0 -0
  103. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/curve_codec.py +0 -0
  104. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/nodes.py +0 -0
  105. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/interest_rates/etl/registry.py +0 -0
  106. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/__init__.py +0 -0
  107. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/bond_pricer.py +0 -0
  108. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/indices.py +0 -0
  109. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/indices_builders.py +0 -0
  110. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/pricing_models/swap_pricer.py +0 -0
  111. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/settings.py +0 -0
  112. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/instruments/utils.py +0 -0
  113. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/runtime_flags.py +0 -0
  114. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/__init__.py +0 -0
  115. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/__main__.py +0 -0
  116. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/base_persist_managers.py +0 -0
  117. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/config.py +0 -0
  118. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/configuration_models.py +0 -0
  119. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/__init__.py +0 -0
  120. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/build_operations.py +0 -0
  121. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
  122. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/filters.py +0 -0
  123. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/models.py +0 -0
  124. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/namespacing.py +0 -0
  125. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
  126. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
  127. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/data_nodes/utils.py +0 -0
  128. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/filters.py +0 -0
  129. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/future_registry.py +0 -0
  130. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/pydantic_metadata.py +0 -0
  131. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/__init__.py +0 -0
  132. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/filters.py +0 -0
  133. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/models.py +0 -0
  134. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/persist_managers.py +0 -0
  135. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/schema.py +0 -0
  136. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/simple_tables/table_nodes.py +0 -0
  137. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/tdag/utils.py +0 -0
  138. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/__init__.py +0 -0
  139. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
  140. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +0 -0
  141. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +0 -0
  142. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +0 -0
  143. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +0 -0
  144. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +0 -0
  145. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +0 -0
  146. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/prices/__init__.py +0 -0
  147. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +0 -0
  148. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/prices/utils.py +0 -0
  149. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +0 -0
  150. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +0 -0
  151. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/enums.py +0 -0
  152. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/models.py +0 -0
  153. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/portfolio_nodes.py +0 -0
  154. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
  155. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +0 -0
  156. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +0 -0
  157. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence/virtualfundbuilder/utils.py +0 -0
  158. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/SOURCES.txt +0 -0
  159. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/dependency_links.txt +0 -0
  160. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/entry_points.txt +0 -0
  161. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/requires.txt +0 -0
  162. {mainsequence-3.18.2 → mainsequence-3.18.3}/mainsequence.egg-info/top_level.txt +0 -0
  163. {mainsequence-3.18.2 → mainsequence-3.18.3}/setup.cfg +0 -0
  164. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_build_operations_hashing.py +0 -0
  165. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_cli_browser_auth.py +0 -0
  166. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_client.py +0 -0
  167. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_command_center_app_component_models.py +0 -0
  168. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_command_center_models.py +0 -0
  169. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_dependency_extras.py +0 -0
  170. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_filter_normalization.py +0 -0
  171. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_instruments.py +0 -0
  172. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_logconf.py +0 -0
  173. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_logged_user_components.py +0 -0
  174. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_models_user_request_bound_auth.py +0 -0
  175. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_pod_project_resolution.py +0 -0
  176. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_project_batch_jobs_from_file.py +0 -0
  177. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_run_configuration.py +0 -0
  178. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_simple_tables_configuration_hashing.py +0 -0
  179. {mainsequence-3.18.2 → mainsequence-3.18.3}/tests/test_simple_tables_persistence.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 3.18.2
3
+ Version: 3.18.3
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -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` into `mainsequence.client.models_user._CURRENT_AUTH_HEADERS`
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({"X-User-ID": str(user_id)})
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 using_jwt:
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
- typer.echo(f'export MAINSEQUENCE_REFRESH_TOKEN="{refresh}"')
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("persisted", True):
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 == "session_jwt" and not isinstance(self.provider, SessionJWTAuthProvider):
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 != "session_jwt"
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 3.18.2
3
+ Version: 3.18.3
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "3.18.2"
7
+ version = "3.18.3"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"