mainsequence 2.0.0__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 (118) hide show
  1. mainsequence-2.0.0/LICENSE +40 -0
  2. mainsequence-2.0.0/PKG-INFO +105 -0
  3. mainsequence-2.0.0/README.md +1 -0
  4. mainsequence-2.0.0/mainsequence/__init__.py +0 -0
  5. mainsequence-2.0.0/mainsequence/__main__.py +9 -0
  6. mainsequence-2.0.0/mainsequence/cli/__init__.py +1 -0
  7. mainsequence-2.0.0/mainsequence/cli/api.py +157 -0
  8. mainsequence-2.0.0/mainsequence/cli/cli.py +442 -0
  9. mainsequence-2.0.0/mainsequence/cli/config.py +78 -0
  10. mainsequence-2.0.0/mainsequence/cli/ssh_utils.py +126 -0
  11. mainsequence-2.0.0/mainsequence/client/__init__.py +17 -0
  12. mainsequence-2.0.0/mainsequence/client/base.py +431 -0
  13. mainsequence-2.0.0/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  14. mainsequence-2.0.0/mainsequence/client/data_sources_interfaces/duckdb.py +1468 -0
  15. mainsequence-2.0.0/mainsequence/client/data_sources_interfaces/timescale.py +479 -0
  16. mainsequence-2.0.0/mainsequence/client/models_helpers.py +113 -0
  17. mainsequence-2.0.0/mainsequence/client/models_report_studio.py +412 -0
  18. mainsequence-2.0.0/mainsequence/client/models_tdag.py +2276 -0
  19. mainsequence-2.0.0/mainsequence/client/models_vam.py +1983 -0
  20. mainsequence-2.0.0/mainsequence/client/utils.py +387 -0
  21. mainsequence-2.0.0/mainsequence/dashboards/__init__.py +0 -0
  22. mainsequence-2.0.0/mainsequence/dashboards/streamlit/__init__.py +0 -0
  23. mainsequence-2.0.0/mainsequence/dashboards/streamlit/assets/config.toml +12 -0
  24. mainsequence-2.0.0/mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
  25. mainsequence-2.0.0/mainsequence/dashboards/streamlit/assets/logo.png +0 -0
  26. mainsequence-2.0.0/mainsequence/dashboards/streamlit/core/__init__.py +0 -0
  27. mainsequence-2.0.0/mainsequence/dashboards/streamlit/core/theme.py +212 -0
  28. mainsequence-2.0.0/mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
  29. mainsequence-2.0.0/mainsequence/dashboards/streamlit/scaffold.py +220 -0
  30. mainsequence-2.0.0/mainsequence/instrumentation/__init__.py +7 -0
  31. mainsequence-2.0.0/mainsequence/instrumentation/utils.py +101 -0
  32. mainsequence-2.0.0/mainsequence/instruments/__init__.py +1 -0
  33. mainsequence-2.0.0/mainsequence/instruments/data_interface/__init__.py +10 -0
  34. mainsequence-2.0.0/mainsequence/instruments/data_interface/data_interface.py +361 -0
  35. mainsequence-2.0.0/mainsequence/instruments/instruments/__init__.py +3 -0
  36. mainsequence-2.0.0/mainsequence/instruments/instruments/base_instrument.py +85 -0
  37. mainsequence-2.0.0/mainsequence/instruments/instruments/bond.py +447 -0
  38. mainsequence-2.0.0/mainsequence/instruments/instruments/european_option.py +74 -0
  39. mainsequence-2.0.0/mainsequence/instruments/instruments/interest_rate_swap.py +217 -0
  40. mainsequence-2.0.0/mainsequence/instruments/instruments/json_codec.py +585 -0
  41. mainsequence-2.0.0/mainsequence/instruments/instruments/knockout_fx_option.py +146 -0
  42. mainsequence-2.0.0/mainsequence/instruments/instruments/position.py +475 -0
  43. mainsequence-2.0.0/mainsequence/instruments/instruments/ql_fields.py +239 -0
  44. mainsequence-2.0.0/mainsequence/instruments/instruments/vanilla_fx_option.py +107 -0
  45. mainsequence-2.0.0/mainsequence/instruments/pricing_models/__init__.py +0 -0
  46. mainsequence-2.0.0/mainsequence/instruments/pricing_models/black_scholes.py +49 -0
  47. mainsequence-2.0.0/mainsequence/instruments/pricing_models/bond_pricer.py +182 -0
  48. mainsequence-2.0.0/mainsequence/instruments/pricing_models/fx_option_pricer.py +90 -0
  49. mainsequence-2.0.0/mainsequence/instruments/pricing_models/indices.py +350 -0
  50. mainsequence-2.0.0/mainsequence/instruments/pricing_models/knockout_fx_pricer.py +209 -0
  51. mainsequence-2.0.0/mainsequence/instruments/pricing_models/swap_pricer.py +502 -0
  52. mainsequence-2.0.0/mainsequence/instruments/settings.py +175 -0
  53. mainsequence-2.0.0/mainsequence/instruments/utils.py +29 -0
  54. mainsequence-2.0.0/mainsequence/logconf.py +284 -0
  55. mainsequence-2.0.0/mainsequence/reportbuilder/__init__.py +0 -0
  56. mainsequence-2.0.0/mainsequence/reportbuilder/__main__.py +0 -0
  57. mainsequence-2.0.0/mainsequence/reportbuilder/examples/ms_template_report.py +706 -0
  58. mainsequence-2.0.0/mainsequence/reportbuilder/model.py +713 -0
  59. mainsequence-2.0.0/mainsequence/reportbuilder/slide_templates.py +532 -0
  60. mainsequence-2.0.0/mainsequence/tdag/__init__.py +8 -0
  61. mainsequence-2.0.0/mainsequence/tdag/__main__.py +0 -0
  62. mainsequence-2.0.0/mainsequence/tdag/config.py +129 -0
  63. mainsequence-2.0.0/mainsequence/tdag/data_nodes/__init__.py +12 -0
  64. mainsequence-2.0.0/mainsequence/tdag/data_nodes/build_operations.py +751 -0
  65. mainsequence-2.0.0/mainsequence/tdag/data_nodes/data_nodes.py +1292 -0
  66. mainsequence-2.0.0/mainsequence/tdag/data_nodes/persist_managers.py +812 -0
  67. mainsequence-2.0.0/mainsequence/tdag/data_nodes/run_operations.py +543 -0
  68. mainsequence-2.0.0/mainsequence/tdag/data_nodes/utils.py +24 -0
  69. mainsequence-2.0.0/mainsequence/tdag/future_registry.py +25 -0
  70. mainsequence-2.0.0/mainsequence/tdag/utils.py +40 -0
  71. mainsequence-2.0.0/mainsequence/virtualfundbuilder/__init__.py +45 -0
  72. mainsequence-2.0.0/mainsequence/virtualfundbuilder/__main__.py +235 -0
  73. mainsequence-2.0.0/mainsequence/virtualfundbuilder/agent_interface.py +77 -0
  74. mainsequence-2.0.0/mainsequence/virtualfundbuilder/config_handling.py +86 -0
  75. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
  76. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/__init__.py +8 -0
  77. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +164 -0
  78. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +292 -0
  79. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +107 -0
  80. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/news_app.py +437 -0
  81. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +91 -0
  82. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +95 -0
  83. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +45 -0
  84. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +40 -0
  85. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +147 -0
  86. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +77 -0
  87. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +5 -0
  88. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +61 -0
  89. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +149 -0
  90. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +310 -0
  91. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +78 -0
  92. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +269 -0
  93. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/prices/__init__.py +1 -0
  94. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +810 -0
  95. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/prices/utils.py +11 -0
  96. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +1 -0
  97. mainsequence-2.0.0/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +313 -0
  98. mainsequence-2.0.0/mainsequence/virtualfundbuilder/data_nodes.py +637 -0
  99. mainsequence-2.0.0/mainsequence/virtualfundbuilder/enums.py +23 -0
  100. mainsequence-2.0.0/mainsequence/virtualfundbuilder/models.py +282 -0
  101. mainsequence-2.0.0/mainsequence/virtualfundbuilder/notebook_handling.py +42 -0
  102. mainsequence-2.0.0/mainsequence/virtualfundbuilder/portfolio_interface.py +272 -0
  103. mainsequence-2.0.0/mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
  104. mainsequence-2.0.0/mainsequence/virtualfundbuilder/resource_factory/app_factory.py +170 -0
  105. mainsequence-2.0.0/mainsequence/virtualfundbuilder/resource_factory/base_factory.py +238 -0
  106. mainsequence-2.0.0/mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +101 -0
  107. mainsequence-2.0.0/mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +183 -0
  108. mainsequence-2.0.0/mainsequence/virtualfundbuilder/utils.py +381 -0
  109. mainsequence-2.0.0/mainsequence.egg-info/PKG-INFO +105 -0
  110. mainsequence-2.0.0/mainsequence.egg-info/SOURCES.txt +116 -0
  111. mainsequence-2.0.0/mainsequence.egg-info/dependency_links.txt +1 -0
  112. mainsequence-2.0.0/mainsequence.egg-info/requires.txt +49 -0
  113. mainsequence-2.0.0/mainsequence.egg-info/top_level.txt +1 -0
  114. mainsequence-2.0.0/pyproject.toml +54 -0
  115. mainsequence-2.0.0/setup.cfg +4 -0
  116. mainsequence-2.0.0/tests/test_agent.py +21 -0
  117. mainsequence-2.0.0/tests/test_portfolio.py +23 -0
  118. mainsequence-2.0.0/tests/test_replicator.py +17 -0
@@ -0,0 +1,40 @@
1
+ MainSequence GmbH SDK License Agreement
2
+ This License Agreement (the "License") governs the use, modification, and distribution of the software provided by MainSequence GmbH (the "Licensor"). The software (the "Software") is provided to you under the terms of this License. By using the Software, you agree to the terms of this License.
3
+
4
+ TERMS AND CONDITIONS
5
+ 0. Definitions
6
+ "Personal Use": Use by an individual for personal purposes that are not connected to any business, organization, or commercial activity.
7
+ "Internal Use": Use within a business, organization, or other entity, provided it is not made accessible to third parties or used for commercial purposes.
8
+ "Commercial Use": Use of the Software in exchange for monetary or other compensation, including hosting, offering Software as a service, selling the Software, or using it in a product or service for sale.
9
+ "License Agreement": The legally binding agreement between the Licensor and the licensee (you), subject to the terms outlined in this License.
10
+ 1. Grant of License
11
+ 1.1 Personal and Internal Use
12
+ The Licensor grants you a limited, non-exclusive, non-transferable, revocable license to use and modify the Software for personal or internal use only, provided that such use is strictly subject to this License Agreement and continues only while the License Agreement remains in effect.
13
+ Upon termination of this License Agreement, all rights to use the Software for personal or internal purposes shall be immediately revoked, and you must cease all use of the Software.
14
+ 1.2 Modification
15
+ You are permitted to modify the Software solely for your own personal or internal use, subject to the restrictions outlined in this License Agreement.
16
+ You are not permitted to distribute, sublicense, or otherwise transfer modified or unmodified versions of the Software to any third party.
17
+ 1.3 Prohibited Redistribution
18
+ You may not redistribute, sublicense, sell, lease, rent, or otherwise transfer the Software, whether in its original form or as modified by you, to any third party.
19
+ Any attempt to distribute or transfer the Software in any way, without explicit permission from MainSequence GmbH, is strictly prohibited.
20
+ 2. Prohibition of Commercial Use
21
+ The Software may not be used for any commercial purposes without obtaining a separate commercial license from MainSequence GmbH.
22
+ Examples of prohibited commercial use include, but are not limited to:
23
+ Hosting or offering the Software as a service to others, either modified or unmodified.
24
+ Using the Software as part of a commercial product or service provided to customers for a fee.
25
+ Using the Software in any production environment that generates income, directly or indirectly, from its use.
26
+ 3. Termination
27
+ This License Agreement will automatically terminate if you fail to comply with any of its terms.
28
+ Upon termination of this License, you must immediately cease all use of the Software, destroy all copies (modified or unmodified), and remove the Software from any devices or systems on which it is installed.
29
+ MainSequence GmbH reserves the right to terminate this License at its discretion for any violation of its terms or for any other reason.
30
+ 4. Commercial License
31
+ If you wish to use the Software for commercial purposes, you must contact MainSequence GmbH to negotiate and obtain a separate commercial license. The terms of the commercial license, including any fees, will be negotiated separately from this License Agreement.
32
+ Without a valid commercial license, you are strictly prohibited from using the Software for any commercial activity.
33
+ 5. Disclaimer of Warranty
34
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL MAINSEQUENCE GMBH OR THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
+
36
+ 6. Limitation of Liability
37
+ IN NO EVENT SHALL MAINSEQUENCE GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
38
+
39
+ 7. Governing Law
40
+ This License Agreement shall be governed by and construed in accordance with the laws of the jurisdiction where MainSequence GmbH is located, without regard to its conflict of law provisions.
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: mainsequence
3
+ Version: 2.0.0
4
+ Summary: Main Sequence SDK
5
+ Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
+ License: MainSequence GmbH SDK License Agreement
7
+ This License Agreement (the "License") governs the use, modification, and distribution of the software provided by MainSequence GmbH (the "Licensor"). The software (the "Software") is provided to you under the terms of this License. By using the Software, you agree to the terms of this License.
8
+
9
+ TERMS AND CONDITIONS
10
+ 0. Definitions
11
+ "Personal Use": Use by an individual for personal purposes that are not connected to any business, organization, or commercial activity.
12
+ "Internal Use": Use within a business, organization, or other entity, provided it is not made accessible to third parties or used for commercial purposes.
13
+ "Commercial Use": Use of the Software in exchange for monetary or other compensation, including hosting, offering Software as a service, selling the Software, or using it in a product or service for sale.
14
+ "License Agreement": The legally binding agreement between the Licensor and the licensee (you), subject to the terms outlined in this License.
15
+ 1. Grant of License
16
+ 1.1 Personal and Internal Use
17
+ The Licensor grants you a limited, non-exclusive, non-transferable, revocable license to use and modify the Software for personal or internal use only, provided that such use is strictly subject to this License Agreement and continues only while the License Agreement remains in effect.
18
+ Upon termination of this License Agreement, all rights to use the Software for personal or internal purposes shall be immediately revoked, and you must cease all use of the Software.
19
+ 1.2 Modification
20
+ You are permitted to modify the Software solely for your own personal or internal use, subject to the restrictions outlined in this License Agreement.
21
+ You are not permitted to distribute, sublicense, or otherwise transfer modified or unmodified versions of the Software to any third party.
22
+ 1.3 Prohibited Redistribution
23
+ You may not redistribute, sublicense, sell, lease, rent, or otherwise transfer the Software, whether in its original form or as modified by you, to any third party.
24
+ Any attempt to distribute or transfer the Software in any way, without explicit permission from MainSequence GmbH, is strictly prohibited.
25
+ 2. Prohibition of Commercial Use
26
+ The Software may not be used for any commercial purposes without obtaining a separate commercial license from MainSequence GmbH.
27
+ Examples of prohibited commercial use include, but are not limited to:
28
+ Hosting or offering the Software as a service to others, either modified or unmodified.
29
+ Using the Software as part of a commercial product or service provided to customers for a fee.
30
+ Using the Software in any production environment that generates income, directly or indirectly, from its use.
31
+ 3. Termination
32
+ This License Agreement will automatically terminate if you fail to comply with any of its terms.
33
+ Upon termination of this License, you must immediately cease all use of the Software, destroy all copies (modified or unmodified), and remove the Software from any devices or systems on which it is installed.
34
+ MainSequence GmbH reserves the right to terminate this License at its discretion for any violation of its terms or for any other reason.
35
+ 4. Commercial License
36
+ If you wish to use the Software for commercial purposes, you must contact MainSequence GmbH to negotiate and obtain a separate commercial license. The terms of the commercial license, including any fees, will be negotiated separately from this License Agreement.
37
+ Without a valid commercial license, you are strictly prohibited from using the Software for any commercial activity.
38
+ 5. Disclaimer of Warranty
39
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL MAINSEQUENCE GMBH OR THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40
+
41
+ 6. Limitation of Liability
42
+ IN NO EVENT SHALL MAINSEQUENCE GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
43
+
44
+ 7. Governing Law
45
+ This License Agreement shall be governed by and construed in accordance with the laws of the jurisdiction where MainSequence GmbH is located, without regard to its conflict of law provisions.
46
+ Project-URL: Homepage, https://github.com/mainsequence-sdk/mainsequence-sdk
47
+ Project-URL: Issues, https://github.com/mainsequence-sdk/mainsequence-sdk/issues
48
+ Classifier: Programming Language :: Python :: 3
49
+ Classifier: Programming Language :: Python :: 3 :: Only
50
+ Classifier: License :: OSI Approved :: MIT License
51
+ Classifier: Operating System :: OS Independent
52
+ Requires-Python: >=3.10
53
+ Description-Content-Type: text/markdown
54
+ License-File: LICENSE
55
+ Requires-Dist: cloudpickle
56
+ Requires-Dist: cryptography
57
+ Requires-Dist: fire
58
+ Requires-Dist: google-auth
59
+ Requires-Dist: joblib
60
+ Requires-Dist: matplotlib
61
+ Requires-Dist: nbconvert
62
+ Requires-Dist: numexpr
63
+ Requires-Dist: opentelemetry-api
64
+ Requires-Dist: opentelemetry-exporter-otlp
65
+ Requires-Dist: opentelemetry-sdk
66
+ Requires-Dist: pandas
67
+ Requires-Dist: pandas-market-calendars
68
+ Requires-Dist: psutil
69
+ Requires-Dist: pyarrow
70
+ Requires-Dist: pydantic
71
+ Requires-Dist: python-binance
72
+ Requires-Dist: python-dotenv
73
+ Requires-Dist: python-logstash
74
+ Requires-Dist: pyyaml
75
+ Requires-Dist: s3fs
76
+ Requires-Dist: scikit-learn
77
+ Requires-Dist: scipy
78
+ Requires-Dist: structlog
79
+ Requires-Dist: tqdm
80
+ Requires-Dist: colorama
81
+ Requires-Dist: kaleido
82
+ Requires-Dist: newspaper3k
83
+ Requires-Dist: lxml-html-clean
84
+ Requires-Dist: docstring-parser
85
+ Requires-Dist: concurrent-log-handler
86
+ Requires-Dist: duckdb
87
+ Requires-Dist: plotly
88
+ Requires-Dist: typer
89
+ Requires-Dist: QuantLib
90
+ Provides-Extra: dev
91
+ Requires-Dist: mkdocs; extra == "dev"
92
+ Requires-Dist: mkdocs-material; extra == "dev"
93
+ Requires-Dist: mkdocs-gen-files; extra == "dev"
94
+ Requires-Dist: mkdocs-literate-nav; extra == "dev"
95
+ Requires-Dist: mkdocs-autorefs; extra == "dev"
96
+ Requires-Dist: mkdocstrings[python]; extra == "dev"
97
+ Requires-Dist: mkdocs-mermaid2-plugin; extra == "dev"
98
+ Requires-Dist: pymdown-extensions; extra == "dev"
99
+ Requires-Dist: pipdeptree; extra == "dev"
100
+ Requires-Dist: pytest; extra == "dev"
101
+ Requires-Dist: pytest-freezegun; extra == "dev"
102
+ Requires-Dist: twine; extra == "dev"
103
+ Dynamic: license-file
104
+
105
+ # Main Sequence Python SDK
@@ -0,0 +1 @@
1
+ # Main Sequence Python SDK
File without changes
@@ -0,0 +1,9 @@
1
+ # mainsequence/__main__.py
2
+ from .cli import app
3
+
4
+ def main():
5
+ # Typer app is callable
6
+ app(prog_name="mainsequence")
7
+
8
+ if __name__ == "__main__":
9
+ main()
@@ -0,0 +1 @@
1
+ from .cli import app
@@ -0,0 +1,157 @@
1
+ # mainsequence/cli/api.py
2
+ from __future__ import annotations
3
+ import os, re, subprocess, json, platform, pathlib, shlex, sys
4
+ import requests
5
+ from .config import backend_url, get_tokens, save_tokens, set_env_access
6
+
7
+ AUTH_PATHS = {
8
+ "obtain": "/auth/jwt-token/token/",
9
+ "refresh": "/auth/jwt-token/token/refresh/",
10
+ "ping": "/auth/rest-auth/user/",
11
+ }
12
+
13
+ S = requests.Session()
14
+ S.headers.update({"Content-Type": "application/json"})
15
+
16
+ class ApiError(RuntimeError): ...
17
+ class NotLoggedIn(ApiError): ...
18
+
19
+ def _full(path: str) -> str:
20
+ p = "/" + path.lstrip("/")
21
+ return backend_url() + p
22
+
23
+ def _normalize_api_path(p: str) -> str:
24
+ p = "/" + (p or "").lstrip("/")
25
+ if not re.match(r"^/(api|auth|pods|orm|user)(/|$)", p):
26
+ raise ApiError("Only /api/*, /auth/*, /pods/*, /orm/*, /user/* allowed")
27
+ return p
28
+
29
+ def _access_token() -> str | None:
30
+ t = os.environ.get("MAIN_SEQUENCE_USER_TOKEN")
31
+ if t:
32
+ return t
33
+ tok = get_tokens()
34
+ return tok.get("access")
35
+
36
+ def _refresh_token() -> str | None:
37
+ tok = get_tokens()
38
+ return tok.get("refresh")
39
+
40
+ def login(email: str, password: str) -> dict:
41
+ url = _full(AUTH_PATHS["obtain"])
42
+ payload = {"email": email, "password": password} # server expects 'email'
43
+ r = S.post(url, data=json.dumps(payload))
44
+ try:
45
+ data = r.json()
46
+ except Exception:
47
+ data = {}
48
+ if not r.ok:
49
+ msg = data.get("detail") or data.get("message") or r.text
50
+ raise ApiError(f"{msg}")
51
+ access = data.get("access") or data.get("token") or data.get("jwt") or data.get("access_token")
52
+ refresh = data.get("refresh") or data.get("refresh_token")
53
+ if not access or not refresh:
54
+ raise ApiError("Server did not return expected tokens.")
55
+ save_tokens(email, access, refresh)
56
+ set_env_access(access)
57
+ return {"username": email, "backend": backend_url()}
58
+
59
+ def refresh_access() -> str:
60
+ refresh = _refresh_token()
61
+ if not refresh:
62
+ raise NotLoggedIn("Not logged in. Run `mainsequence login <email>`.")
63
+ r = S.post(_full(AUTH_PATHS["refresh"]), data=json.dumps({"refresh": refresh}))
64
+ data = r.json() if r.headers.get("content-type","").startswith("application/json") else {}
65
+ if not r.ok:
66
+ raise NotLoggedIn(data.get("detail") or "Token refresh failed.")
67
+ access = data.get("access")
68
+ if not access:
69
+ raise NotLoggedIn("Refresh succeeded but no access token returned.")
70
+ tokens = get_tokens()
71
+ save_tokens(tokens.get("username") or "", access, refresh)
72
+ set_env_access(access)
73
+ return access
74
+
75
+ def authed(method: str, api_path: str, body: dict | None = None) -> requests.Response:
76
+ api_path = _normalize_api_path(api_path)
77
+ access = _access_token()
78
+ if not access:
79
+ # try to refresh once
80
+ access = refresh_access()
81
+ headers = {"Authorization": f"Bearer {access}"}
82
+ r = S.request(method.upper(), _full(api_path), headers=headers,
83
+ data=None if method.upper() in {"GET","HEAD"} else json.dumps(body or {}))
84
+ if r.status_code == 401:
85
+ # retry after refresh
86
+ access = refresh_access()
87
+ headers = {"Authorization": f"Bearer {access}"}
88
+ r = S.request(method.upper(), _full(api_path), headers=headers,
89
+ data=None if method.upper() in {"GET","HEAD"} else json.dumps(body or {}))
90
+ if r.status_code == 401:
91
+ raise NotLoggedIn("Not logged in.")
92
+ return r
93
+
94
+ # ---------- Helper APIs ----------
95
+
96
+ def safe_slug(s: str) -> str:
97
+ x = re.sub(r"[^a-z0-9-_]+", "-", (s or "project").lower()).strip("-")
98
+ return x[:64] or "project"
99
+
100
+ def repo_name_from_git_url(url: str | None) -> str | None:
101
+ if not url: return None
102
+ s = re.sub(r"[?#].*$", "", url.strip())
103
+ last = s.split("/")[-1] if "/" in s else s
104
+ if last.lower().endswith(".git"): last = last[:-4]
105
+ return re.sub(r"[^A-Za-z0-9._-]+", "-", last)
106
+
107
+ def deep_find_repo_url(extra) -> str | None:
108
+ if not isinstance(extra, dict): return None
109
+ cand = ["ssh_url","git_ssh_url","repo_ssh_url","git_url","repo_url","repository","url"]
110
+ for k in cand:
111
+ v = extra.get(k)
112
+ if isinstance(v, str) and (v.startswith("git@") or re.search(r"\.git($|\?)", v)):
113
+ return v
114
+ if isinstance(v, dict):
115
+ for vv in v.values():
116
+ if isinstance(vv, str) and (vv.startswith("git@") or re.search(r"\.git($|\?)", vv)):
117
+ return vv
118
+ for v in extra.values():
119
+ if isinstance(v, dict):
120
+ found = deep_find_repo_url(v)
121
+ if found: return found
122
+ return None
123
+
124
+ def get_current_user_profile() -> dict:
125
+ who = authed("GET", AUTH_PATHS["ping"])
126
+ d = who.json() if who.ok else {}
127
+ uid = d.get("id") or d.get("pk") or (d.get("user") or {}).get("id") or d.get("user_id")
128
+ if not uid:
129
+ return {}
130
+ full = authed("GET", f"/user/api/user/{uid}/")
131
+ u = full.json() if full.ok else {}
132
+ org_name = (u.get("organization") or {}).get("name") or u.get("organization_name") or ""
133
+ return {"username": u.get("username") or "", "organization": org_name}
134
+
135
+ def get_projects() -> list[dict]:
136
+ r = authed("GET", "/orm/api/pods/projects/")
137
+ # If the API shape ever changes, still try to pull a list.
138
+ if not r.ok:
139
+ raise ApiError(f"Projects fetch failed ({r.status_code}).")
140
+ data = r.json() if r.headers.get("content-type","",).startswith("application/json") else {}
141
+ if isinstance(data, list):
142
+ return data
143
+ return data.get("results") or []
144
+
145
+ def fetch_project_env_text(project_id: int | str) -> str:
146
+ r = authed("GET", f"/orm/api/pods/projects/{project_id}/get_environment/")
147
+ raw = r.json() if r.headers.get("content-type","").startswith("application/json") else r.text
148
+ if isinstance(raw, dict):
149
+ raw = raw.get("environment") or raw.get("env") or raw.get("content") or raw.get("text") or ""
150
+ return (raw or "")
151
+
152
+ def add_deploy_key(project_id: int | str, key_title: str, public_key: str) -> None:
153
+ try:
154
+ authed("POST", f"/orm/api/pods/projects/{project_id}/add_deploy_key/",
155
+ {"key_title": key_title, "public_key": public_key})
156
+ except Exception:
157
+ pass