openbb-cli 1.0.0__py3-none-any.whl
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.
- openbb_cli/__init__.py +1 -0
- openbb_cli/argparse_translator/__init__.py +0 -0
- openbb_cli/argparse_translator/argparse_class_processor.py +148 -0
- openbb_cli/argparse_translator/argparse_translator.py +627 -0
- openbb_cli/argparse_translator/obbject_registry.py +94 -0
- openbb_cli/assets/routines/routine_example.openbb +21 -0
- openbb_cli/assets/styles/default/Consolas.ttf +0 -0
- openbb_cli/assets/styles/default/dark.mpfstyle.json +47 -0
- openbb_cli/assets/styles/default/dark.mplrc.json +7 -0
- openbb_cli/assets/styles/default/dark.mplstyle +96 -0
- openbb_cli/assets/styles/default/dark.pltstyle.json +132 -0
- openbb_cli/assets/styles/default/dark.richstyle.json +9 -0
- openbb_cli/assets/styles/default/light.mpfstyle.json +47 -0
- openbb_cli/assets/styles/default/light.mplrc.json +7 -0
- openbb_cli/assets/styles/default/light.mplstyle +95 -0
- openbb_cli/assets/styles/default/light.pltstyle.json +871 -0
- openbb_cli/assets/styles/default/light.richstyle.json +9 -0
- openbb_cli/assets/styles/default/tables.pltstyle.json +102 -0
- openbb_cli/assets/styles/user/openbb.richstyle.json +9 -0
- openbb_cli/cli.py +20 -0
- openbb_cli/config/__init__.py +1 -0
- openbb_cli/config/completer.py +427 -0
- openbb_cli/config/console.py +93 -0
- openbb_cli/config/constants.py +80 -0
- openbb_cli/config/menu_text.py +165 -0
- openbb_cli/config/setup.py +11 -0
- openbb_cli/config/style.py +108 -0
- openbb_cli/controllers/base_controller.py +941 -0
- openbb_cli/controllers/base_platform_controller.py +349 -0
- openbb_cli/controllers/choices.py +324 -0
- openbb_cli/controllers/cli_controller.py +946 -0
- openbb_cli/controllers/hub_service.py +107 -0
- openbb_cli/controllers/platform_controller_factory.py +58 -0
- openbb_cli/controllers/script_parser.py +488 -0
- openbb_cli/controllers/settings_controller.py +373 -0
- openbb_cli/controllers/utils.py +967 -0
- openbb_cli/models/settings.py +73 -0
- openbb_cli/session.py +92 -0
- openbb_cli-1.0.0.dist-info/METADATA +91 -0
- openbb_cli-1.0.0.dist-info/RECORD +42 -0
- openbb_cli-1.0.0.dist-info/WHEEL +4 -0
- openbb_cli-1.0.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Constants module."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Paths
|
|
6
|
+
HOME_DIRECTORY = Path.home()
|
|
7
|
+
REPOSITORY_DIRECTORY = Path(__file__).parent.parent.parent.parent
|
|
8
|
+
SRC_DIRECTORY = Path(__file__).parent.parent
|
|
9
|
+
SETTINGS_DIRECTORY = HOME_DIRECTORY / ".openbb_platform"
|
|
10
|
+
ASSETS_DIRECTORY = SRC_DIRECTORY / "assets"
|
|
11
|
+
STYLES_DIRECTORY = ASSETS_DIRECTORY / "styles"
|
|
12
|
+
ENV_FILE_SETTINGS = SETTINGS_DIRECTORY / ".cli.env"
|
|
13
|
+
HIST_FILE_PROMPT = SETTINGS_DIRECTORY / ".cli.his"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DEFAULT_ROUTINES_URL = "https://openbb-cms.directus.app/items/Routines"
|
|
17
|
+
TIMEOUT = 30
|
|
18
|
+
CONNECTION_ERROR_MSG = "[red]Connection error.[/red]"
|
|
19
|
+
CONNECTION_TIMEOUT_MSG = "[red]Connection timeout.[/red]"
|
|
20
|
+
SCRIPT_TAGS = [
|
|
21
|
+
"stocks",
|
|
22
|
+
"crypto",
|
|
23
|
+
"etf",
|
|
24
|
+
"economy",
|
|
25
|
+
"forex",
|
|
26
|
+
"fixed income",
|
|
27
|
+
"alternative",
|
|
28
|
+
"funds",
|
|
29
|
+
"bonds",
|
|
30
|
+
"macro",
|
|
31
|
+
"mutual funds",
|
|
32
|
+
"equities",
|
|
33
|
+
"options",
|
|
34
|
+
"dark pool",
|
|
35
|
+
"shorts",
|
|
36
|
+
"insider",
|
|
37
|
+
"behavioral analysis",
|
|
38
|
+
"fundamental analysis",
|
|
39
|
+
"technical analysis",
|
|
40
|
+
"quantitative analysis",
|
|
41
|
+
"forecasting",
|
|
42
|
+
"government",
|
|
43
|
+
"comparison",
|
|
44
|
+
"nft",
|
|
45
|
+
"on chain",
|
|
46
|
+
"off chain",
|
|
47
|
+
"screener",
|
|
48
|
+
"report",
|
|
49
|
+
"overview",
|
|
50
|
+
"rates",
|
|
51
|
+
"econometrics",
|
|
52
|
+
"portfolio",
|
|
53
|
+
"real estate",
|
|
54
|
+
]
|
|
55
|
+
AVAILABLE_FLAIRS = {
|
|
56
|
+
":openbb": "(🦋)",
|
|
57
|
+
":bug": "(🐛)",
|
|
58
|
+
":rocket": "(🚀)",
|
|
59
|
+
":diamond": "(💎)",
|
|
60
|
+
":stars": "(✨)",
|
|
61
|
+
":baseball": "(⚾)",
|
|
62
|
+
":boat": "(⛵)",
|
|
63
|
+
":phone": "(☎)",
|
|
64
|
+
":mercury": "(☿)",
|
|
65
|
+
":hidden": "",
|
|
66
|
+
":sun": "(☼)",
|
|
67
|
+
":moon": "(🌕)",
|
|
68
|
+
":nuke": "(☢)",
|
|
69
|
+
":hazard": "(☣)",
|
|
70
|
+
":tunder": "(☈)",
|
|
71
|
+
":king": "(♔)",
|
|
72
|
+
":queen": "(♕)",
|
|
73
|
+
":knight": "(♘)",
|
|
74
|
+
":recycle": "(♻)",
|
|
75
|
+
":scales": "(⚖)",
|
|
76
|
+
":ball": "(⚽)",
|
|
77
|
+
":golf": "(⛳)",
|
|
78
|
+
":peace": "(☮)",
|
|
79
|
+
":yy": "(☯)",
|
|
80
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Rich Module."""
|
|
2
|
+
|
|
3
|
+
__docformat__ = "numpy"
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from openbb import obb
|
|
8
|
+
|
|
9
|
+
# https://rich.readthedocs.io/en/stable/appendix/colors.html#appendix-colors
|
|
10
|
+
# https://rich.readthedocs.io/en/latest/highlighting.html#custom-highlighters
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
RICH_TAGS = [
|
|
14
|
+
"[menu]",
|
|
15
|
+
"[/menu]",
|
|
16
|
+
"[cmds]",
|
|
17
|
+
"[/cmds]",
|
|
18
|
+
"[info]",
|
|
19
|
+
"[/info]",
|
|
20
|
+
"[param]",
|
|
21
|
+
"[/param]",
|
|
22
|
+
"[src]",
|
|
23
|
+
"[/src]",
|
|
24
|
+
"[help]",
|
|
25
|
+
"[/help]",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MenuText:
|
|
30
|
+
"""Create menu text with rich colors to be displayed by CLI."""
|
|
31
|
+
|
|
32
|
+
CMD_NAME_LENGTH = 23
|
|
33
|
+
CMD_DESCRIPTION_LENGTH = 65
|
|
34
|
+
CMD_PROVIDERS_LENGTH = 23
|
|
35
|
+
SECTION_SPACING = 4
|
|
36
|
+
|
|
37
|
+
def __init__(self, path: str = ""):
|
|
38
|
+
"""Initialize menu help."""
|
|
39
|
+
self.menu_text = ""
|
|
40
|
+
self.menu_path = path
|
|
41
|
+
self.warnings: List[Dict[str, str]] = []
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _get_providers(command_path: str) -> List:
|
|
45
|
+
"""Return the preferred provider for the given command.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
command_path: str
|
|
50
|
+
The command to find the provider for. E.g. "/equity/price/historical
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
List
|
|
55
|
+
The list of providers for the given command.
|
|
56
|
+
"""
|
|
57
|
+
command_reference = obb.reference.get("paths", {}).get(command_path, {}) # type: ignore
|
|
58
|
+
if command_reference:
|
|
59
|
+
providers = list(command_reference["parameters"].keys())
|
|
60
|
+
return [provider for provider in providers if provider != "standard"]
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
def _format_cmd_name(self, name: str) -> str:
|
|
64
|
+
"""Truncate command name length if it is too long."""
|
|
65
|
+
if len(name) > self.CMD_NAME_LENGTH:
|
|
66
|
+
new_name = name[: self.CMD_NAME_LENGTH]
|
|
67
|
+
|
|
68
|
+
if "_" in name:
|
|
69
|
+
name_split = name.split("_")
|
|
70
|
+
|
|
71
|
+
new_name = (
|
|
72
|
+
"_".join(name_split[:2]) if len(name_split) > 2 else name_split[0]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if len(new_name) > self.CMD_NAME_LENGTH:
|
|
76
|
+
new_name = new_name[: self.CMD_NAME_LENGTH]
|
|
77
|
+
|
|
78
|
+
if new_name != name:
|
|
79
|
+
self.warnings.append(
|
|
80
|
+
{
|
|
81
|
+
"warning": "Command name too long",
|
|
82
|
+
"command": name,
|
|
83
|
+
"trimmed_command": new_name,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
name = new_name
|
|
87
|
+
|
|
88
|
+
return name
|
|
89
|
+
|
|
90
|
+
def _format_cmd_description(
|
|
91
|
+
self, name: str, description: str, trim: bool = True
|
|
92
|
+
) -> str:
|
|
93
|
+
"""Truncate command description length if it is too long."""
|
|
94
|
+
if not description or description == f"{self.menu_path}{name}":
|
|
95
|
+
description = ""
|
|
96
|
+
return (
|
|
97
|
+
description[: self.CMD_DESCRIPTION_LENGTH - 3] + "..."
|
|
98
|
+
if len(description) > self.CMD_DESCRIPTION_LENGTH and trim
|
|
99
|
+
else description
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def add_raw(self, text: str, left_spacing: bool = False):
|
|
103
|
+
"""Append raw text (without translation)."""
|
|
104
|
+
if left_spacing:
|
|
105
|
+
self.menu_text += f"{self.SECTION_SPACING * ' '}{text}\n"
|
|
106
|
+
else:
|
|
107
|
+
self.menu_text += text
|
|
108
|
+
|
|
109
|
+
def add_info(self, text: str):
|
|
110
|
+
"""Append information text (after translation)."""
|
|
111
|
+
self.menu_text += f"[info]{text}:[/info]\n"
|
|
112
|
+
|
|
113
|
+
def add_cmd(self, name: str, description: str = "", disable: bool = False):
|
|
114
|
+
"""Append command text (after translation)."""
|
|
115
|
+
formatted_name = self._format_cmd_name(name)
|
|
116
|
+
name_padding = (self.CMD_NAME_LENGTH - len(formatted_name)) * " "
|
|
117
|
+
providers = self._get_providers(f"{self.menu_path}{name}")
|
|
118
|
+
formatted_description = self._format_cmd_description(
|
|
119
|
+
formatted_name,
|
|
120
|
+
description,
|
|
121
|
+
bool(providers),
|
|
122
|
+
)
|
|
123
|
+
description_padding = (
|
|
124
|
+
self.CMD_DESCRIPTION_LENGTH - len(formatted_description)
|
|
125
|
+
) * " "
|
|
126
|
+
spacing = self.SECTION_SPACING * " "
|
|
127
|
+
description_padding = (
|
|
128
|
+
self.CMD_DESCRIPTION_LENGTH - len(formatted_description)
|
|
129
|
+
) * " "
|
|
130
|
+
cmd = f"{spacing}{formatted_name + name_padding}{spacing}{formatted_description+description_padding}"
|
|
131
|
+
cmd = f"[unvl]{cmd}[/unvl]" if disable else f"[cmds]{cmd}[/cmds]"
|
|
132
|
+
|
|
133
|
+
if providers:
|
|
134
|
+
cmd += rf"{spacing}[src]\[{', '.join(providers)}][/src]"
|
|
135
|
+
|
|
136
|
+
self.menu_text += cmd + "\n"
|
|
137
|
+
|
|
138
|
+
def add_menu(
|
|
139
|
+
self,
|
|
140
|
+
name: str,
|
|
141
|
+
description: str = "",
|
|
142
|
+
disable: bool = False,
|
|
143
|
+
):
|
|
144
|
+
"""Append menu text (after translation)."""
|
|
145
|
+
spacing = (self.CMD_NAME_LENGTH - len(name) + self.SECTION_SPACING) * " "
|
|
146
|
+
|
|
147
|
+
if not description or description == f"{self.menu_path}{name}":
|
|
148
|
+
description = ""
|
|
149
|
+
|
|
150
|
+
if len(description) > self.CMD_DESCRIPTION_LENGTH:
|
|
151
|
+
description = description[: self.CMD_DESCRIPTION_LENGTH - 3] + "..."
|
|
152
|
+
|
|
153
|
+
menu = f"{name}{spacing}{description}"
|
|
154
|
+
tag = "unvl" if disable else "menu"
|
|
155
|
+
self.menu_text += f"[{tag}]> {menu}[/{tag}]\n"
|
|
156
|
+
|
|
157
|
+
def add_setting(self, name: str, status: bool = True, description: str = ""):
|
|
158
|
+
"""Append menu text (after translation)."""
|
|
159
|
+
spacing = (self.CMD_NAME_LENGTH - len(name) + self.SECTION_SPACING) * " "
|
|
160
|
+
indentation = self.SECTION_SPACING * " "
|
|
161
|
+
color = "green" if status else "red"
|
|
162
|
+
|
|
163
|
+
self.menu_text += (
|
|
164
|
+
f"[{color}]{indentation}{name}{spacing}{description}[/{color}]\n"
|
|
165
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Configuration for the CLI."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from openbb_cli.config.constants import ENV_FILE_SETTINGS, SETTINGS_DIRECTORY
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def bootstrap():
|
|
9
|
+
"""Setup pre-launch configurations for the CLI."""
|
|
10
|
+
SETTINGS_DIRECTORY.mkdir(parents=True, exist_ok=True)
|
|
11
|
+
Path(ENV_FILE_SETTINGS).touch(exist_ok=True)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Chart and style helpers for Plotly."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=C0302,R0902,W3301
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from openbb_cli.config.constants import STYLES_DIRECTORY
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Style:
|
|
16
|
+
"""The class that helps with handling of style configurations.
|
|
17
|
+
|
|
18
|
+
It serves styles for 2 libraries. For `Plotly` this class serves absolute paths
|
|
19
|
+
to the .pltstyle files. For `Plotly` and `Rich` this class serves custom
|
|
20
|
+
styles as python dictionaries.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
STYLES_REPO = STYLES_DIRECTORY
|
|
24
|
+
|
|
25
|
+
console_styles_available: Dict[str, Path] = {}
|
|
26
|
+
console_style: Dict[str, Any] = {}
|
|
27
|
+
|
|
28
|
+
line_color: str = ""
|
|
29
|
+
up_color: str = ""
|
|
30
|
+
down_color: str = ""
|
|
31
|
+
up_colorway: List[str] = []
|
|
32
|
+
down_colorway: List[str] = []
|
|
33
|
+
up_color_transparent: str = ""
|
|
34
|
+
down_color_transparent: str = ""
|
|
35
|
+
|
|
36
|
+
line_width: float = 1.5
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
style: Optional[str] = "",
|
|
41
|
+
directory: Optional[Path] = None,
|
|
42
|
+
):
|
|
43
|
+
"""Initialize the class."""
|
|
44
|
+
self._load(directory)
|
|
45
|
+
self.apply(style, directory)
|
|
46
|
+
|
|
47
|
+
def apply(
|
|
48
|
+
self, style: Optional[str] = None, directory: Optional[Path] = None
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Apply the style to the console."""
|
|
51
|
+
if style:
|
|
52
|
+
if style in self.console_styles_available:
|
|
53
|
+
json_path: Optional[Path] = self.console_styles_available[style]
|
|
54
|
+
else:
|
|
55
|
+
self._load(directory)
|
|
56
|
+
if style in self.console_styles_available:
|
|
57
|
+
json_path = self.console_styles_available[style]
|
|
58
|
+
else:
|
|
59
|
+
console.print(f"\nInvalid console style '{style}', using default.")
|
|
60
|
+
json_path = self.console_styles_available.get("dark", None)
|
|
61
|
+
|
|
62
|
+
if json_path:
|
|
63
|
+
self.console_style = self._from_json(json_path)
|
|
64
|
+
else:
|
|
65
|
+
console.print("Error loading default.")
|
|
66
|
+
|
|
67
|
+
def _from_directory(self, folder: Optional[Path]) -> None:
|
|
68
|
+
"""Load custom styles from folder.
|
|
69
|
+
|
|
70
|
+
Parses the styles/default and styles/user folders and loads style files.
|
|
71
|
+
To be recognized files need to follow a naming convention:
|
|
72
|
+
*.pltstyle - plotly stylesheets
|
|
73
|
+
*.richstyle.json - rich stylesheets
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
folder : str
|
|
78
|
+
Path to the folder containing the stylesheets
|
|
79
|
+
"""
|
|
80
|
+
if not folder or not folder.exists():
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
for attr, ext in zip(
|
|
84
|
+
["console_styles_available"],
|
|
85
|
+
[".richstyle.json"],
|
|
86
|
+
):
|
|
87
|
+
for file in folder.rglob(f"*{ext}"):
|
|
88
|
+
getattr(self, attr)[file.name.replace(ext, "")] = file
|
|
89
|
+
|
|
90
|
+
def _load(self, directory: Optional[Path] = None) -> None:
|
|
91
|
+
"""Load custom styles from default and user folders."""
|
|
92
|
+
self._from_directory(self.STYLES_REPO)
|
|
93
|
+
self._from_directory(directory)
|
|
94
|
+
|
|
95
|
+
def _from_json(self, file: Path) -> Dict[str, Any]:
|
|
96
|
+
"""Load style from json file."""
|
|
97
|
+
with open(file) as f:
|
|
98
|
+
json_style: dict = json.load(f)
|
|
99
|
+
for key, value in json_style.items():
|
|
100
|
+
json_style[key] = value.replace(
|
|
101
|
+
" ", ""
|
|
102
|
+
) # remove whitespaces so Rich can parse it
|
|
103
|
+
return json_style
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def available_styles(self) -> List[str]:
|
|
107
|
+
"""Return available styles."""
|
|
108
|
+
return list(self.console_styles_available.keys())
|