openbb-cli 1.0.0__tar.gz → 1.0.2__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 (45) hide show
  1. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/PKG-INFO +7 -6
  2. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/README.md +4 -4
  3. openbb_cli-1.0.2/openbb_cli/argparse_translator/argparse_argument.py +63 -0
  4. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/argparse_translator/argparse_class_processor.py +7 -8
  5. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/argparse_translator/argparse_translator.py +70 -255
  6. openbb_cli-1.0.2/openbb_cli/argparse_translator/reference_processor.py +137 -0
  7. openbb_cli-1.0.2/openbb_cli/argparse_translator/utils.py +71 -0
  8. openbb_cli-1.0.2/openbb_cli/cli.py +23 -0
  9. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/base_controller.py +35 -12
  10. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/base_platform_controller.py +1 -2
  11. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/choices.py +7 -5
  12. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/cli_controller.py +13 -9
  13. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/utils.py +5 -2
  14. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/models/settings.py +5 -2
  15. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/pyproject.toml +4 -6
  16. openbb_cli-1.0.0/openbb_cli/cli.py +0 -20
  17. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/__init__.py +0 -0
  18. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/argparse_translator/__init__.py +0 -0
  19. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/argparse_translator/obbject_registry.py +0 -0
  20. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/routines/routine_example.openbb +0 -0
  21. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/Consolas.ttf +0 -0
  22. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/dark.mpfstyle.json +0 -0
  23. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/dark.mplrc.json +0 -0
  24. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/dark.mplstyle +0 -0
  25. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/dark.pltstyle.json +0 -0
  26. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/dark.richstyle.json +0 -0
  27. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/light.mpfstyle.json +0 -0
  28. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/light.mplrc.json +0 -0
  29. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/light.mplstyle +0 -0
  30. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/light.pltstyle.json +0 -0
  31. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/light.richstyle.json +0 -0
  32. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/default/tables.pltstyle.json +0 -0
  33. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/assets/styles/user/openbb.richstyle.json +0 -0
  34. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/config/__init__.py +0 -0
  35. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/config/completer.py +0 -0
  36. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/config/console.py +0 -0
  37. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/config/constants.py +0 -0
  38. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/config/menu_text.py +0 -0
  39. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/config/setup.py +0 -0
  40. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/config/style.py +0 -0
  41. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/hub_service.py +0 -0
  42. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/platform_controller_factory.py +0 -0
  43. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/script_parser.py +0 -0
  44. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/controllers/settings_controller.py +0 -0
  45. {openbb_cli-1.0.0 → openbb_cli-1.0.2}/openbb_cli/session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openbb-cli
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Investment Research for Everyone, Anywhere.
5
5
  Home-page: https://openbb.co
6
6
  License: AGPL-3.0-only
@@ -12,7 +12,8 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
- Requires-Dist: openbb[all] (>=4.2.0,<5.0.0)
15
+ Requires-Dist: openbb-core (>=1.2.4,<2.0.0)
16
+ Requires-Dist: openbb[all] (>=4.2.1,<5.0.0)
16
17
  Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
17
18
  Requires-Dist: prompt-toolkit (>=3.0.16,<4.0.0)
18
19
  Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
@@ -33,11 +34,11 @@ Description-Content-Type: text/markdown
33
34
 
34
35
  ## Overview
35
36
 
36
- The OpenBB CLI is a command line interface that wraps [OpenBB Platform](https://docs.openbb.co/platform).
37
+ The OpenBB Platform CLI is a command line interface that wraps [OpenBB Platform](https://docs.openbb.co/platform).
37
38
 
38
39
  It offers a convenient way to interact with the OpenBB Platform and its extensions, as well as automate data collection via OpenBB Routine Scripts.
39
40
 
40
- Find the most complete documentation, examples, and usage guides for the OpenBB Platform CLI [here](https://my.openbb.co/app/cli).
41
+ Find the most complete documentation, examples, and usage guides for the OpenBB Platform CLI [here](https://docs.openbb.co/cli).
41
42
 
42
43
  ## Installation
43
44
 
@@ -47,9 +48,9 @@ The command below provides access to the all the available OpenBB extensions beh
47
48
  pip install openbb-cli
48
49
  ```
49
50
 
50
- > Note: Find the most complete installation hints and tips [here](https://my.openbb.co/app/cli/installation).
51
+ > Note: Find the most complete installation hints and tips [here](https://docs.openbb.co/cli/installation).
51
52
 
52
- After the installation is complete, you can deploy the OpenBB CLI by running the following command:
53
+ After the installation is complete, you can deploy the OpenBB Platform CLI by running the following command:
53
54
 
54
55
  ```bash
55
56
  openbb
@@ -10,11 +10,11 @@
10
10
 
11
11
  ## Overview
12
12
 
13
- The OpenBB CLI is a command line interface that wraps [OpenBB Platform](https://docs.openbb.co/platform).
13
+ The OpenBB Platform CLI is a command line interface that wraps [OpenBB Platform](https://docs.openbb.co/platform).
14
14
 
15
15
  It offers a convenient way to interact with the OpenBB Platform and its extensions, as well as automate data collection via OpenBB Routine Scripts.
16
16
 
17
- Find the most complete documentation, examples, and usage guides for the OpenBB Platform CLI [here](https://my.openbb.co/app/cli).
17
+ Find the most complete documentation, examples, and usage guides for the OpenBB Platform CLI [here](https://docs.openbb.co/cli).
18
18
 
19
19
  ## Installation
20
20
 
@@ -24,9 +24,9 @@ The command below provides access to the all the available OpenBB extensions beh
24
24
  pip install openbb-cli
25
25
  ```
26
26
 
27
- > Note: Find the most complete installation hints and tips [here](https://my.openbb.co/app/cli/installation).
27
+ > Note: Find the most complete installation hints and tips [here](https://docs.openbb.co/cli/installation).
28
28
 
29
- After the installation is complete, you can deploy the OpenBB CLI by running the following command:
29
+ After the installation is complete, you can deploy the OpenBB Platform CLI by running the following command:
30
30
 
31
31
  ```bash
32
32
  openbb
@@ -0,0 +1,63 @@
1
+ """Pydantic models for argparse arguments and argument groups."""
2
+
3
+ from typing import (
4
+ Any,
5
+ List,
6
+ Literal,
7
+ Optional,
8
+ Tuple,
9
+ )
10
+
11
+ from pydantic import BaseModel, model_validator
12
+
13
+ SEP = "__"
14
+
15
+
16
+ class ArgparseArgumentModel(BaseModel):
17
+ """Pydantic model for an argparse argument."""
18
+
19
+ name: str
20
+ type: Any
21
+ dest: str
22
+ default: Any
23
+ required: bool
24
+ action: Literal["store_true", "store"]
25
+ help: Optional[str]
26
+ nargs: Optional[Literal["+"]]
27
+ choices: Optional[Tuple]
28
+
29
+ @model_validator(mode="after") # type: ignore
30
+ @classmethod
31
+ def validate_action(cls, values: "ArgparseArgumentModel"):
32
+ """Validate the action based on the type."""
33
+ if values.type is bool and values.action != "store_true":
34
+ raise ValueError('If type is bool, action must be "store_true"')
35
+ return values
36
+
37
+ @model_validator(mode="after") # type: ignore
38
+ @classmethod
39
+ def remove_props_on_store_true(cls, values: "ArgparseArgumentModel"):
40
+ """Remove type, nargs, and choices if action is store_true."""
41
+ if values.action == "store_true":
42
+ values.type = None
43
+ values.nargs = None
44
+ values.choices = None
45
+ return values
46
+
47
+ # override
48
+ def model_dump(self, **kwargs):
49
+ """Override the model_dump method to remove empty choices."""
50
+ res = super().model_dump(**kwargs)
51
+
52
+ # Check if choices is present and if it's an empty tuple remove it
53
+ if "choices" in res and not res["choices"]:
54
+ del res["choices"]
55
+
56
+ return res
57
+
58
+
59
+ class ArgparseArgumentGroupModel(BaseModel):
60
+ """Pydantic model for a custom argument group."""
61
+
62
+ name: str
63
+ arguments: List[ArgparseArgumentModel]
@@ -1,19 +1,19 @@
1
+ """Module for the ArgparseClassProcessor class."""
2
+
1
3
  import inspect
2
4
  from typing import Any, Dict, Optional, Type
3
5
 
4
6
  # TODO: this needs to be done differently
5
7
  from openbb_core.app.static.container import Container
6
8
 
7
- from openbb_cli.argparse_translator.argparse_translator import (
8
- ArgparseTranslator,
9
- ReferenceToCustomArgumentsProcessor,
9
+ from openbb_cli.argparse_translator.argparse_translator import ArgparseTranslator
10
+ from openbb_cli.argparse_translator.reference_processor import (
11
+ ReferenceToArgumentsProcessor,
10
12
  )
11
13
 
12
14
 
13
15
  class ArgparseClassProcessor:
14
- """
15
- Process a target class to create ArgparseTranslators for its methods.
16
- """
16
+ """Process a target class to create ArgparseTranslators for its methods."""
17
17
 
18
18
  # reference variable used to create custom groups for the ArgpaseTranslators
19
19
  _reference: Dict[str, Any] = {}
@@ -77,7 +77,7 @@ class ArgparseClassProcessor:
77
77
  reference = {route: cls._reference[route]} if route in cls._reference else {}
78
78
  if not reference:
79
79
  return {}
80
- rp = ReferenceToCustomArgumentsProcessor(reference)
80
+ rp = ReferenceToArgumentsProcessor(reference)
81
81
  return rp.custom_groups.get(route, {}) # type: ignore
82
82
 
83
83
  @classmethod
@@ -92,7 +92,6 @@ class ArgparseClassProcessor:
92
92
  if name.startswith("__") or name.startswith("_"):
93
93
  continue
94
94
  if inspect.ismethod(member):
95
-
96
95
  class_name = cls._get_class_name(target)
97
96
  methods[f"{class_name}_{name}"] = ArgparseTranslator(
98
97
  func=member,
@@ -1,8 +1,9 @@
1
+ """Module for translating a function into an argparse program."""
2
+
1
3
  import argparse
2
4
  import inspect
3
5
  import re
4
6
  from copy import deepcopy
5
- from enum import Enum
6
7
  from typing import (
7
8
  Any,
8
9
  Callable,
@@ -19,184 +20,37 @@ from typing import (
19
20
  )
20
21
 
21
22
  from openbb_core.app.model.field import OpenBBField
22
- from pydantic import BaseModel, model_validator
23
+ from pydantic import BaseModel
23
24
  from typing_extensions import Annotated
24
25
 
26
+ from openbb_cli.argparse_translator.argparse_argument import (
27
+ ArgparseArgumentGroupModel,
28
+ ArgparseArgumentModel,
29
+ )
30
+ from openbb_cli.argparse_translator.utils import (
31
+ get_argument_choices,
32
+ get_argument_optional_choices,
33
+ in_group,
34
+ remove_argument,
35
+ set_optional_choices,
36
+ )
37
+
25
38
  # pylint: disable=protected-access
26
39
 
27
40
  SEP = "__"
28
41
 
29
42
 
30
- class ArgparseActionType(Enum):
31
- store = "store"
32
- store_true = "store_true"
33
-
34
-
35
- class CustomArgument(BaseModel):
36
-
37
- name: str
38
- type: Optional[Any]
39
- dest: str
40
- default: Any
41
- required: bool
42
- action: Literal["store_true", "store"]
43
- help: str
44
- nargs: Optional[Literal["+"]]
45
- choices: Optional[Tuple]
46
-
47
- @model_validator(mode="after") # type: ignore
48
- @classmethod
49
- def validate_action(cls, values: "CustomArgument"):
50
- if values.type is bool and values.action != "store_true":
51
- raise ValueError('If type is bool, action must be "store_true"')
52
- return values
53
-
54
- @model_validator(mode="after") # type: ignore
55
- @classmethod
56
- def remove_props_on_store_true(cls, values: "CustomArgument"):
57
- if values.action == "store_true":
58
- values.type = None
59
- values.nargs = None
60
- values.choices = None
61
- return values
62
-
63
- # override
64
- def model_dump(self, **kwargs):
65
-
66
- res = super().model_dump(**kwargs)
67
-
68
- # Check if choices is present and if it's an empty tuple remove it
69
- if "choices" in res and not res["choices"]:
70
- del res["choices"]
71
-
72
- return res
73
-
74
-
75
- class CustomArgumentGroup(BaseModel):
76
- name: str
77
- arguments: List[CustomArgument]
78
-
79
-
80
- class ReferenceToCustomArgumentsProcessor:
81
- def __init__(self, reference: Dict[str, Dict]):
82
- """Initializes the ReferenceToCustomArgumentsProcessor."""
83
- self.reference = reference
84
- self.custom_groups: Dict[str, List[CustomArgumentGroup]] = {}
85
-
86
- self.build_custom_groups()
87
-
88
- @staticmethod
89
- def _make_type_parsable(type_: str) -> type:
90
- """Make the type parsable by removing the annotations."""
91
- if "Union" in type_ and "str" in type_:
92
- return str
93
- if "Union" in type_ and "int" in type_:
94
- return int
95
- if type_ in ["date", "datetime.time", "time"]:
96
- return str
97
-
98
- if any(x in type_ for x in ["gt=", "ge=", "lt=", "le="]):
99
- if "Annotated" in type_:
100
- type_ = type_.replace("Annotated[", "").replace("]", "")
101
- type_ = type_.split(",")[0]
102
-
103
- return eval(type_) # noqa: S307, E501 pylint: disable=eval-used
104
-
105
- def _parse_type(self, type_: str) -> type:
106
- """Parse the type from the string representation."""
107
- type_ = self._make_type_parsable(type_) # type: ignore
108
-
109
- if get_origin(type_) is Literal:
110
- type_ = type(get_args(type_)[0]) # type: ignore
111
-
112
- return type_ # type: ignore
113
-
114
- def _get_nargs(self, type_: type) -> Optional[Union[int, str]]:
115
- """Get the nargs for the given type."""
116
- if get_origin(type_) is list:
117
- return "+"
118
- return None
119
-
120
- def _get_choices(self, type_: str, custom_choices: Any) -> Tuple:
121
- """Get the choices for the given type."""
122
- type_ = self._make_type_parsable(type_) # type: ignore
123
- type_origin = get_origin(type_)
124
-
125
- choices = ()
126
-
127
- if type_origin is Literal:
128
- choices = get_args(type_)
129
-
130
- if type_origin is list:
131
- type_ = get_args(type_)[0]
132
-
133
- if get_origin(type_) is Literal:
134
- choices = get_args(type_)
135
-
136
- if type_origin is Union and type(None) in get_args(type_):
137
- # remove NoneType from the args
138
- args = [arg for arg in get_args(type_) if arg != type(None)]
139
- # if there is only one arg left, use it
140
- if len(args) > 1:
141
- raise ValueError("Union with NoneType should have only one type left")
142
- type_ = args[0]
143
-
144
- if get_origin(type_) is Literal:
145
- choices = get_args(type_)
146
-
147
- if custom_choices:
148
- return tuple(custom_choices)
149
-
150
- return choices
151
-
152
- def build_custom_groups(self):
153
- """Build the custom groups from the reference."""
154
- for route, v in self.reference.items():
155
-
156
- for provider, args in v["parameters"].items():
157
- if provider == "standard":
158
- continue
159
-
160
- custom_arguments = []
161
- for arg in args:
162
- if arg.get("standard"):
163
- continue
164
-
165
- type_ = self._parse_type(arg["type"])
166
-
167
- custom_arguments.append(
168
- CustomArgument(
169
- name=arg["name"],
170
- type=type_,
171
- dest=arg["name"],
172
- default=arg["default"],
173
- required=not (arg["optional"]),
174
- action="store" if type_ != bool else "store_true",
175
- help=arg["description"],
176
- nargs=self._get_nargs(type_), # type: ignore
177
- choices=self._get_choices(
178
- arg["type"], custom_choices=arg["choices"]
179
- ),
180
- )
181
- )
182
-
183
- group = CustomArgumentGroup(name=provider, arguments=custom_arguments)
184
-
185
- if route not in self.custom_groups:
186
- self.custom_groups[route] = []
187
-
188
- self.custom_groups[route].append(group)
189
-
190
-
191
43
  class ArgparseTranslator:
44
+ """Class to translate a function into an argparse program."""
45
+
192
46
  def __init__(
193
47
  self,
194
48
  func: Callable,
195
- custom_argument_groups: Optional[List[CustomArgumentGroup]] = None,
49
+ custom_argument_groups: Optional[List[ArgparseArgumentGroupModel]] = None,
196
50
  add_help: Optional[bool] = True,
197
51
  ):
198
52
  """
199
- Initializes the ArgparseTranslator.
53
+ Initialize the ArgparseTranslator.
200
54
 
201
55
  Args:
202
56
  func (Callable): The function to translate into an argparse program.
@@ -228,45 +82,6 @@ class ArgparseTranslator:
228
82
  def _handle_argument_in_groups(self, argument, group):
229
83
  """Handle the argument and add it to the parser."""
230
84
 
231
- def _in_group(arg, group_title):
232
- for action_group in self._parser._action_groups:
233
- if action_group.title == group_title:
234
- for action in action_group._group_actions:
235
- opts = action.option_strings
236
- if (opts and opts[0] == arg) or action.dest == arg:
237
- return True
238
- return False
239
-
240
- def _remove_argument(arg) -> List[Optional[str]]:
241
- groups_w_arg = []
242
-
243
- # remove the argument from the parser
244
- for action in self._parser._actions:
245
- opts = action.option_strings
246
- if (opts and opts[0] == arg) or action.dest == arg:
247
- self._parser._remove_action(action)
248
- break
249
-
250
- # remove from all groups
251
- for action_group in self._parser._action_groups:
252
- for action in action_group._group_actions:
253
- opts = action.option_strings
254
- if (opts and opts[0] == arg) or action.dest == arg:
255
- action_group._group_actions.remove(action)
256
- groups_w_arg.append(action_group.title)
257
-
258
- # remove from _action_groups dict
259
- self._parser._option_string_actions.pop(f"--{arg}", None)
260
-
261
- return groups_w_arg
262
-
263
- def _get_arg_choices(arg) -> Tuple:
264
- for action in self._parser._actions:
265
- opts = action.option_strings
266
- if (opts and opts[0] == arg) or action.dest == arg:
267
- return tuple(action.choices or ())
268
- return ()
269
-
270
85
  def _update_providers(
271
86
  input_string: str, new_provider: List[Optional[str]]
272
87
  ) -> str:
@@ -288,23 +103,27 @@ class ArgparseTranslator:
288
103
  kwargs = argument.model_dump(exclude={"name"}, exclude_none=True)
289
104
  model_choices = kwargs.get("choices", ()) or ()
290
105
  # extend choices
291
- choices = tuple(set(_get_arg_choices(argument.name) + model_choices))
106
+ existing_choices = get_argument_choices(self._parser, argument.name)
107
+ choices = tuple(set(existing_choices + model_choices))
108
+ optional_choices = bool(existing_choices and not model_choices)
292
109
 
293
110
  # check if the argument is in the required arguments
294
- if _in_group(argument.name, group_title="required arguments"):
111
+ if in_group(self._parser, argument.name, group_title="required arguments"):
295
112
  for action in self._required._group_actions:
296
113
  if action.dest == argument.name and choices:
297
114
  # update choices
298
115
  action.choices = choices
116
+ set_optional_choices(action, optional_choices)
299
117
  return
300
118
 
301
119
  # check if the argument is in the optional arguments
302
- if _in_group(argument.name, group_title="optional arguments"):
120
+ if in_group(self._parser, argument.name, group_title="optional arguments"):
303
121
  for action in self._parser._actions:
304
122
  if action.dest == argument.name:
305
123
  # update choices
306
124
  if choices:
307
125
  action.choices = choices
126
+ set_optional_choices(action, optional_choices)
308
127
  if argument.name not in self.signature.parameters:
309
128
  # update help
310
129
  action.help = _update_providers(
@@ -312,9 +131,16 @@ class ArgparseTranslator:
312
131
  )
313
132
  return
314
133
 
134
+ # we need to check if the optional choices were set in other group
135
+ # before we remove the argument from the group, otherwise we will lose info
136
+ if not optional_choices:
137
+ optional_choices = get_argument_optional_choices(
138
+ self._parser, argument.name
139
+ )
140
+
315
141
  # if the argument is in use, remove it from all groups
316
142
  # and return the groups that had the argument
317
- groups_w_arg = _remove_argument(argument.name)
143
+ groups_w_arg = remove_argument(self._parser, argument.name)
318
144
  groups_w_arg.append(group.title) # add current group
319
145
 
320
146
  # add it to the optional arguments group instead
@@ -322,16 +148,17 @@ class ArgparseTranslator:
322
148
  kwargs["choices"] = choices # update choices
323
149
  # add provider info to the help
324
150
  kwargs["help"] = _update_providers(argument.help or "", groups_w_arg)
325
- self._parser.add_argument(f"--{argument.name}", **kwargs)
151
+ action = self._parser.add_argument(f"--{argument.name}", **kwargs)
152
+ set_optional_choices(action, optional_choices)
326
153
 
327
154
  @property
328
155
  def parser(self) -> argparse.ArgumentParser:
156
+ """Get the argparse parser."""
329
157
  return deepcopy(self._parser)
330
158
 
331
159
  @staticmethod
332
160
  def _build_description(func_doc: str) -> str:
333
- """Builds the description of the argparse program from the function docstring."""
334
-
161
+ """Build the description of the argparse program from the function docstring."""
335
162
  patterns = ["openbb\n ======", "Parameters\n ----------"]
336
163
 
337
164
  if func_doc:
@@ -344,31 +171,34 @@ class ArgparseTranslator:
344
171
 
345
172
  @staticmethod
346
173
  def _param_is_default(param: inspect.Parameter) -> bool:
347
- """Returns True if the parameter has a default value."""
174
+ """Return True if the parameter has a default value."""
348
175
  return param.default != inspect.Parameter.empty
349
176
 
350
177
  def _get_action_type(self, param: inspect.Parameter) -> str:
351
- """Returns the argparse action type for the given parameter."""
178
+ """Return the argparse action type for the given parameter."""
352
179
  param_type = self.type_hints[param.name]
180
+ type_origin = get_origin(param_type)
353
181
 
354
- if param_type == bool:
355
- return ArgparseActionType.store_true.value
356
- return ArgparseActionType.store.value
182
+ if param_type == bool or (
183
+ type_origin is Union and bool in get_args(param_type)
184
+ ):
185
+ return "store_true"
186
+ return "store"
357
187
 
358
188
  def _get_type_and_choices(
359
189
  self, param: inspect.Parameter
360
190
  ) -> Tuple[Type[Any], Tuple[Any, ...]]:
361
- """Returns the type and choices for the given parameter."""
191
+ """Return the type and choices for the given parameter."""
362
192
  param_type = self.type_hints[param.name]
363
193
  type_origin = get_origin(param_type)
364
194
 
365
- choices = ()
195
+ choices: tuple[Any, ...] = ()
366
196
 
367
197
  if type_origin is Literal:
368
198
  choices = get_args(param_type)
369
199
  param_type = type(choices[0]) # type: ignore
370
200
 
371
- if type_origin is list: # TODO: dict should also go here
201
+ if type_origin is list:
372
202
  param_type = get_args(param_type)[0]
373
203
 
374
204
  if get_origin(param_type) is Literal:
@@ -416,32 +246,26 @@ class ArgparseTranslator:
416
246
 
417
247
  @classmethod
418
248
  def _get_argument_custom_help(cls, param: inspect.Parameter) -> Optional[str]:
419
- """Returns the help annotation for the given parameter."""
249
+ """Return the help annotation for the given parameter."""
420
250
  base_annotation = param.annotation
421
251
  _, custom_annotations = cls._split_annotation(base_annotation, OpenBBField)
422
252
  help_annotation = (
423
253
  custom_annotations[0].description if custom_annotations else None
424
254
  )
425
- if not help_annotation:
426
- # try to get it from the docstring
427
- pass
428
255
  return help_annotation
429
256
 
430
257
  @classmethod
431
258
  def _get_argument_custom_choices(cls, param: inspect.Parameter) -> Optional[str]:
432
- """Returns the help annotation for the given parameter."""
259
+ """Return the help annotation for the given parameter."""
433
260
  base_annotation = param.annotation
434
261
  _, custom_annotations = cls._split_annotation(base_annotation, OpenBBField)
435
262
  choices_annotation = (
436
263
  custom_annotations[0].choices if custom_annotations else None
437
264
  )
438
- if not choices_annotation:
439
- # try to get it from the docstring
440
- pass
441
265
  return choices_annotation
442
266
 
443
267
  def _get_nargs(self, param: inspect.Parameter) -> Optional[str]:
444
- """Returns the nargs annotation for the given parameter."""
268
+ """Return the nargs annotation for the given parameter."""
445
269
  param_type = self.type_hints[param.name]
446
270
  origin = get_origin(param_type)
447
271
 
@@ -456,11 +280,8 @@ class ArgparseTranslator:
456
280
  return None
457
281
 
458
282
  def _generate_argparse_arguments(self, parameters) -> None:
459
- """Generates the argparse arguments from the function parameters."""
283
+ """Generate the argparse arguments from the function parameters."""
460
284
  for param in parameters.values():
461
- # TODO : how to handle kwargs?
462
- # it's possible to add unknown arguments when parsing as follows:
463
- # args, unknown_args = parser.parse_known_args()
464
285
  if param.name == "kwargs":
465
286
  continue
466
287
 
@@ -508,32 +329,27 @@ class ArgparseTranslator:
508
329
 
509
330
  required = not self._param_is_default(param)
510
331
 
511
- kwargs = {
512
- "type": param_type,
513
- "dest": param.name,
514
- "default": param.default,
515
- "required": required,
516
- "action": self._get_action_type(param),
517
- "help": self._get_argument_custom_help(param),
518
- "nargs": self._get_nargs(param),
519
- }
520
-
521
- if choices:
522
- kwargs["choices"] = choices
523
-
524
- if param_type == bool:
525
- # store_true action does not accept the below kwargs
526
- kwargs.pop("type")
527
- kwargs.pop("nargs")
332
+ argument = ArgparseArgumentModel(
333
+ name=param.name,
334
+ type=param_type,
335
+ dest=param.name,
336
+ default=param.default,
337
+ required=required,
338
+ action=self._get_action_type(param),
339
+ help=self._get_argument_custom_help(param),
340
+ nargs=self._get_nargs(param),
341
+ choices=choices,
342
+ )
343
+ kwargs = argument.model_dump(exclude={"name"}, exclude_none=True)
528
344
 
529
345
  if required:
530
346
  self._required.add_argument(
531
- f"--{param.name}",
347
+ f"--{argument.name}",
532
348
  **kwargs,
533
349
  )
534
350
  else:
535
351
  self._parser.add_argument(
536
- f"--{param.name}",
352
+ f"--{argument.name}",
537
353
  **kwargs,
538
354
  )
539
355
 
@@ -559,7 +375,6 @@ class ArgparseTranslator:
559
375
  # for each argument in the signature that is a custom type, we need to
560
376
  # update the kwargs with the custom type kwargs
561
377
  for param in self.signature.parameters.values():
562
- # TODO : how to handle kwargs?
563
378
  if param.name == "kwargs":
564
379
  continue
565
380
  param_type, _ = self._get_type_and_choices(param)
@@ -574,7 +389,7 @@ class ArgparseTranslator:
574
389
  parsed_args: Optional[argparse.Namespace] = None,
575
390
  ) -> Any:
576
391
  """
577
- Executes the original function with the parsed arguments.
392
+ Execute the original function with the parsed arguments.
578
393
 
579
394
  Args:
580
395
  parsed_args (Optional[argparse.Namespace], optional): The parsed arguments. Defaults to None.
@@ -605,7 +420,7 @@ class ArgparseTranslator:
605
420
 
606
421
  def parse_args_and_execute(self) -> Any:
607
422
  """
608
- Parses the arguments and executes the original function.
423
+ Parse the arguments and executes the original function.
609
424
 
610
425
  Returns:
611
426
  Any: The return value of the original function.
@@ -615,7 +430,7 @@ class ArgparseTranslator:
615
430
 
616
431
  def translate(self) -> Callable:
617
432
  """
618
- Wraps the original function with an argparse program.
433
+ Wrap the original function with an argparse program.
619
434
 
620
435
  Returns:
621
436
  Callable: The original function wrapped with an argparse program.
@@ -0,0 +1,137 @@
1
+ """Module for the ReferenceToArgumentsProcessor class."""
2
+
3
+ from typing import (
4
+ Any,
5
+ Dict,
6
+ List,
7
+ Literal,
8
+ Optional,
9
+ Tuple,
10
+ Union,
11
+ get_args,
12
+ get_origin,
13
+ )
14
+
15
+ from openbb_cli.argparse_translator.argparse_argument import (
16
+ ArgparseArgumentGroupModel,
17
+ ArgparseArgumentModel,
18
+ )
19
+
20
+
21
+ class ReferenceToArgumentsProcessor:
22
+ """Class to process the reference and build custom argument groups."""
23
+
24
+ def __init__(self, reference: Dict[str, Dict]):
25
+ """Initialize the ReferenceToArgumentsProcessor."""
26
+ self._reference = reference
27
+ self._custom_groups: Dict[str, List[ArgparseArgumentGroupModel]] = {}
28
+
29
+ self._build_custom_groups()
30
+
31
+ @property
32
+ def custom_groups(self) -> Dict[str, List[ArgparseArgumentGroupModel]]:
33
+ """Get the custom groups."""
34
+ return self._custom_groups
35
+
36
+ @staticmethod
37
+ def _make_type_parsable(type_: str) -> type:
38
+ """Make the type parsable by removing the annotations."""
39
+ if "Union" in type_ and "str" in type_:
40
+ return str
41
+ if "Union" in type_ and "int" in type_:
42
+ return int
43
+ if type_ in ["date", "datetime.time", "time"]:
44
+ return str
45
+
46
+ if any(x in type_ for x in ["gt=", "ge=", "lt=", "le="]):
47
+ if "Annotated" in type_:
48
+ type_ = type_.replace("Annotated[", "").replace("]", "")
49
+ type_ = type_.split(",")[0]
50
+
51
+ return eval(type_) # noqa: S307, E501 pylint: disable=eval-used
52
+
53
+ def _parse_type(self, type_: str) -> type:
54
+ """Parse the type from the string representation."""
55
+ type_ = self._make_type_parsable(type_) # type: ignore
56
+
57
+ if get_origin(type_) is Literal:
58
+ type_ = type(get_args(type_)[0]) # type: ignore
59
+
60
+ return type_ # type: ignore
61
+
62
+ def _get_nargs(self, type_: type) -> Optional[Union[int, str]]:
63
+ """Get the nargs for the given type."""
64
+ if get_origin(type_) is list:
65
+ return "+"
66
+ return None
67
+
68
+ def _get_choices(self, type_: str, custom_choices: Any) -> Tuple:
69
+ """Get the choices for the given type."""
70
+ type_ = self._make_type_parsable(type_) # type: ignore
71
+ type_origin = get_origin(type_)
72
+
73
+ choices: tuple[Any, ...] = ()
74
+
75
+ if type_origin is Literal:
76
+ choices = get_args(type_)
77
+
78
+ if type_origin is list:
79
+ type_ = get_args(type_)[0]
80
+
81
+ if get_origin(type_) is Literal:
82
+ choices = get_args(type_)
83
+
84
+ if type_origin is Union and type(None) in get_args(type_):
85
+ # remove NoneType from the args
86
+ args = [arg for arg in get_args(type_) if arg != type(None)]
87
+ # if there is only one arg left, use it
88
+ if len(args) > 1:
89
+ raise ValueError("Union with NoneType should have only one type left")
90
+ type_ = args[0]
91
+
92
+ if get_origin(type_) is Literal:
93
+ choices = get_args(type_)
94
+
95
+ if custom_choices:
96
+ return tuple(custom_choices)
97
+
98
+ return choices
99
+
100
+ def _build_custom_groups(self):
101
+ """Build the custom groups from the reference."""
102
+ for route, v in self._reference.items():
103
+ for provider, args in v["parameters"].items():
104
+ if provider == "standard":
105
+ continue
106
+
107
+ custom_arguments = []
108
+ for arg in args:
109
+ if arg.get("standard"):
110
+ continue
111
+
112
+ type_ = self._parse_type(arg["type"])
113
+
114
+ custom_arguments.append(
115
+ ArgparseArgumentModel(
116
+ name=arg["name"],
117
+ type=type_,
118
+ dest=arg["name"],
119
+ default=arg["default"],
120
+ required=not (arg["optional"]),
121
+ action="store" if type_ != bool else "store_true",
122
+ help=arg["description"],
123
+ nargs=self._get_nargs(type_), # type: ignore
124
+ choices=self._get_choices(
125
+ arg["type"], custom_choices=arg["choices"]
126
+ ),
127
+ )
128
+ )
129
+
130
+ group = ArgparseArgumentGroupModel(
131
+ name=provider, arguments=custom_arguments
132
+ )
133
+
134
+ if route not in self._custom_groups:
135
+ self._custom_groups[route] = []
136
+
137
+ self._custom_groups[route].append(group)
@@ -0,0 +1,71 @@
1
+ """Utilities for argparse_translator module."""
2
+
3
+ from argparse import Action, ArgumentParser
4
+ from typing import List, Optional, Tuple
5
+
6
+
7
+ def in_group(parser: ArgumentParser, argument_name: str, group_title: str) -> bool:
8
+ """Check if an argument is in a group of an ArgumentParser."""
9
+ for action_group in parser._action_groups: # pylint: disable=protected-access
10
+ if action_group.title == group_title:
11
+ for (
12
+ action
13
+ ) in action_group._group_actions: # pylint: disable=protected-access
14
+ opts = action.option_strings
15
+ if (opts and opts[0] == argument_name) or action.dest == argument_name:
16
+ return True
17
+ return False
18
+
19
+
20
+ def remove_argument(parser: ArgumentParser, argument_name: str) -> List[Optional[str]]:
21
+ """Remove an argument from an ArgumentParser."""
22
+ groups_w_arg = []
23
+
24
+ # remove the argument from the parser
25
+ for action in parser._actions: # pylint: disable=protected-access
26
+ opts = action.option_strings
27
+ if (opts and opts[0] == argument_name) or action.dest == argument_name:
28
+ parser._remove_action(action) # pylint: disable=protected-access
29
+ break
30
+
31
+ # remove from all groups
32
+ for action_group in parser._action_groups: # pylint: disable=protected-access
33
+ for action in action_group._group_actions: # pylint: disable=protected-access
34
+ opts = action.option_strings
35
+ if (opts and opts[0] == argument_name) or action.dest == argument_name:
36
+ action_group._group_actions.remove( # pylint: disable=protected-access
37
+ action
38
+ )
39
+ groups_w_arg.append(action_group.title)
40
+
41
+ # remove from _action_groups dict
42
+ parser._option_string_actions.pop( # pylint: disable=protected-access
43
+ f"--{argument_name}", None
44
+ )
45
+
46
+ return groups_w_arg
47
+
48
+
49
+ def get_argument_choices(parser: ArgumentParser, argument_name: str) -> Tuple:
50
+ """Get the choices of an argument from an ArgumentParser."""
51
+ for action in parser._actions: # pylint: disable=protected-access
52
+ opts = action.option_strings
53
+ if (opts and opts[0] == argument_name) or action.dest == argument_name:
54
+ return tuple(action.choices or ())
55
+ return ()
56
+
57
+
58
+ def get_argument_optional_choices(parser: ArgumentParser, argument_name: str) -> bool:
59
+ """Get the optional_choices attribute of an argument from an ArgumentParser."""
60
+ for action in parser._actions: # pylint: disable=protected-access
61
+ opts = action.option_strings
62
+ if (opts and opts[0] == argument_name) or action.dest == argument_name:
63
+ if hasattr(action, "optional_choices"):
64
+ return action.optional_choices
65
+ return False
66
+
67
+
68
+ def set_optional_choices(action: Action, optional_choices: bool):
69
+ """Set the optional_choices attribute of an action."""
70
+ if not hasattr(action, "optional_choices") and optional_choices:
71
+ setattr(action, "optional_choices", optional_choices)
@@ -0,0 +1,23 @@
1
+ """OpenBB Platform CLI entry point."""
2
+
3
+ import sys
4
+
5
+
6
+ def main():
7
+ """Use the main entry point for the OpenBB Platform CLI."""
8
+ print("Loading...\n") # noqa: T201
9
+
10
+ # pylint: disable=import-outside-toplevel
11
+ from openbb_cli.config.setup import bootstrap
12
+ from openbb_cli.controllers.cli_controller import launch
13
+
14
+ bootstrap()
15
+
16
+ dev = "--dev" in sys.argv[1:]
17
+ debug = "--debug" in sys.argv[1:]
18
+
19
+ launch(dev, debug)
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -4,6 +4,7 @@ import argparse
4
4
  import difflib
5
5
  import os
6
6
  import re
7
+ import shlex
7
8
  from abc import ABCMeta, abstractmethod
8
9
  from datetime import datetime
9
10
  from pathlib import Path
@@ -240,7 +241,7 @@ class BaseController(metaclass=ABCMeta):
240
241
  else:
241
242
  try:
242
243
  (known_args, other_args) = self.parser.parse_known_args(
243
- an_input.split()
244
+ shlex.split(an_input)
244
245
  )
245
246
  except Exception as exc:
246
247
  raise SystemExit from exc
@@ -742,13 +743,15 @@ class BaseController(metaclass=ABCMeta):
742
743
 
743
744
  if export_allowed == "raw_data_only":
744
745
  choices_export = ["csv", "json", "xlsx"]
745
- help_export = "Export raw data into csv, json, xlsx"
746
+ help_export = "Export raw data into csv, json or xlsx."
746
747
  elif export_allowed == "figures_only":
747
- choices_export = ["png", "jpg", "pdf", "svg"]
748
- help_export = "Export figure into png, jpg, pdf, svg "
748
+ choices_export = ["png", "jpg"]
749
+ help_export = "Export figure into png or jpg."
749
750
  else:
750
- choices_export = ["csv", "json", "xlsx", "png", "jpg", "pdf", "svg"]
751
- help_export = "Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg "
751
+ choices_export = ["csv", "json", "xlsx", "png", "jpg"]
752
+ help_export = (
753
+ "Export raw data into csv, json, xlsx and figure into png or jpg."
754
+ )
752
755
 
753
756
  parser.add_argument(
754
757
  "--export",
@@ -798,11 +801,31 @@ class BaseController(metaclass=ABCMeta):
798
801
  return None
799
802
 
800
803
  try:
801
- # If the user uses a comma separated list of arguments, split them
802
- for index, arg in enumerate(other_args):
803
- if "," in arg:
804
- parts = arg.split(",")
805
- other_args[index : index + 1] = parts
804
+ # Determine the index of the routine arguments
805
+ routine_args_index = next(
806
+ (
807
+ i + 1
808
+ for i, arg in enumerate(other_args)
809
+ if arg in ("-i", "--input")
810
+ and "routine_args"
811
+ in [
812
+ action.dest
813
+ for action in parser._actions # pylint: disable=protected-access
814
+ ]
815
+ ),
816
+ -1,
817
+ )
818
+ # Split comma-separated arguments, except for the argument at routine_args_index
819
+ other_args = [
820
+ part
821
+ for index, arg in enumerate(other_args)
822
+ for part in (arg.split(",") if index != routine_args_index else [arg])
823
+ ]
824
+
825
+ # Check if the action has optional choices, if yes, remove them
826
+ for action in parser._actions: # pylint: disable=protected-access
827
+ if hasattr(action, "optional_choices") and action.optional_choices:
828
+ action.choices = None
806
829
 
807
830
  (ns_parser, l_unknown_args) = parser.parse_known_args(other_args)
808
831
 
@@ -811,7 +834,7 @@ class BaseController(metaclass=ABCMeta):
811
834
  "raw_data_and_figures",
812
835
  ]:
813
836
  ns_parser.is_image = any(
814
- ext in ns_parser.export for ext in ["png", "svg", "jpg", "pdf"]
837
+ ext in ns_parser.export for ext in ["png", "jpg"]
815
838
  )
816
839
 
817
840
  except SystemExit:
@@ -154,11 +154,10 @@ class PlatformController(BaseController):
154
154
 
155
155
  obbject = translator.execute_func(parsed_args=ns_parser)
156
156
  df: pd.DataFrame = pd.DataFrame()
157
- fig: OpenBBFigure = None
157
+ fig: Optional[OpenBBFigure] = None
158
158
  title = f"{self.PATH}{translator.func.__name__}"
159
159
 
160
160
  if obbject:
161
-
162
161
  if isinstance(obbject, OBBject):
163
162
  if session.max_obbjects_exceeded() and obbject.results:
164
163
  session.obbject_registry.remove()
@@ -56,13 +56,15 @@ def __mock_parse_known_args_and_warn(
56
56
 
57
57
  if export_allowed == "raw_data_only":
58
58
  choices_export = ["csv", "json", "xlsx"]
59
- help_export = "Export raw data into csv, json, xlsx"
59
+ help_export = "Export raw data into csv, json or xlsx."
60
60
  elif export_allowed == "figures_only":
61
- choices_export = ["png", "jpg", "pdf", "svg"]
62
- help_export = "Export figure into png, jpg, pdf, svg "
61
+ choices_export = ["png", "jpg"]
62
+ help_export = "Export figure into png or jpg."
63
63
  else:
64
- choices_export = ["csv", "json", "xlsx", "png", "jpg", "pdf", "svg"]
65
- help_export = "Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg "
64
+ choices_export = ["csv", "json", "xlsx", "png", "jpg"]
65
+ help_export = (
66
+ "Export raw data into csv, json, xlsx and figure into png or jpg."
67
+ )
66
68
 
67
69
  parser.add_argument(
68
70
  "--export",
@@ -355,7 +355,7 @@ class CLIController(BaseController):
355
355
  "--input",
356
356
  help="Select multiple inputs to be replaced in the routine and separated by commas. E.g. GME,AMC,BTC-USD",
357
357
  dest="routine_args",
358
- type=lambda s: [str(item) for item in s.split(",")],
358
+ type=str,
359
359
  )
360
360
  parser.add_argument(
361
361
  "-e",
@@ -431,17 +431,22 @@ class CLIController(BaseController):
431
431
  with open(routine_path) as fp:
432
432
  raw_lines = list(fp)
433
433
 
434
+ script_inputs = []
434
435
  # Capture ARGV either as list if args separated by commas or as single value
435
- if ns_parser.routine_args:
436
- script_inputs = (
437
- ns_parser.routine_args
438
- if "," not in ns_parser.routine_args
439
- else ns_parser.routine_args.split(",")
436
+ if routine_args := ns_parser.routine_args:
437
+ pattern = r"\[(.*?)\]"
438
+ matches = re.findall(pattern, routine_args)
439
+
440
+ for match in matches:
441
+ routine_args = routine_args.replace(f"[{match}]", "")
442
+ script_inputs.append(match)
443
+
444
+ script_inputs.extend(
445
+ [val for val in routine_args.split(",") if val]
440
446
  )
441
447
 
442
448
  err, parsed_script = parse_openbb_script(
443
- raw_lines=raw_lines,
444
- script_inputs=script_inputs if ns_parser.routine_args else None,
449
+ raw_lines=raw_lines, script_inputs=script_inputs
445
450
  )
446
451
 
447
452
  # If there err output is not an empty string then it means there was an
@@ -546,7 +551,6 @@ def run_cli(jobs_cmds: Optional[List[str]] = None, test_mode=False):
546
551
  t_controller.print_help()
547
552
 
548
553
  while ret_code:
549
-
550
554
  # There is a command in the queue
551
555
  if t_controller.queue and len(t_controller.queue) > 0:
552
556
  # If the command is quitting the menu we want to return in here
@@ -901,7 +901,7 @@ def export_data(
901
901
  else:
902
902
  save_to_excel(df, saved_path, sheet_name)
903
903
 
904
- elif saved_path.suffix in [".jpg", ".pdf", ".png", ".svg"]:
904
+ elif saved_path.suffix in [".jpg", ".png"]:
905
905
  if figure is None:
906
906
  Session().console.print("No plot to export.")
907
907
  continue
@@ -910,7 +910,10 @@ def export_data(
910
910
  Session().console.print("Wrong export file specified.")
911
911
  continue
912
912
 
913
- Session().console.print(f"Saved file: {saved_path}")
913
+ if saved_path.exists():
914
+ Session().console.print(f"Saved file: {saved_path}")
915
+ else:
916
+ Session().console.print(f"Failed to save file: {saved_path}")
914
917
 
915
918
  if figure is not None:
916
919
  figure._exported = True # pylint: disable=protected-access
@@ -1,17 +1,20 @@
1
- """Settings model"""
1
+ """Settings model."""
2
2
 
3
3
  from typing import Any
4
4
 
5
5
  from dotenv import dotenv_values, set_key
6
6
  from openbb_cli.config.constants import ENV_FILE_SETTINGS
7
+ from openbb_core.app.version import get_package_version
7
8
  from pydantic import BaseModel, ConfigDict, model_validator
8
9
 
10
+ VERSION = get_package_version("openbb-cli")
11
+
9
12
 
10
13
  class Settings(BaseModel):
11
14
  """Settings model."""
12
15
 
13
16
  # Platform CLI version
14
- VERSION: str = "1.0.0"
17
+ VERSION: str = VERSION
15
18
 
16
19
  # DEVELOPMENT FLAGS
17
20
  TEST_MODE: bool = False
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openbb-cli"
3
- version = "1.0.0"
3
+ version = "1.0.2"
4
4
  description = "Investment Research for Everyone, Anywhere."
5
5
  authors = ["OpenBB <hello@openbb.co>"]
6
6
  packages = [{ include = "openbb_cli" }]
@@ -17,16 +17,14 @@ openbb = 'openbb_cli.cli:main'
17
17
  python = "^3.8.1,<3.12"
18
18
 
19
19
  # OpenBB dependencies
20
- openbb = { version = "^4.2.0", extras = ["all"] }
20
+ openbb = { version = "^4.2.1", extras = ["all"] }
21
21
 
22
- # Terminal dependencies
22
+ # CLI dependencies
23
23
  prompt-toolkit = "^3.0.16"
24
24
  rich = "^13"
25
25
  python-dotenv = "^1.0.0"
26
26
  openpyxl = "^3.1.2"
27
-
28
- [tool.poetry.group.dev.dependencies]
29
- openbb-devtools = "^1.1.3"
27
+ openbb-core = "^1.2.4"
30
28
 
31
29
  [build-system]
32
30
  requires = ["setuptools<65.5.0", "poetry-core>=1.0.0"]
@@ -1,20 +0,0 @@
1
- """OpenBB Platform CLI entry point."""
2
-
3
- import sys
4
-
5
- from openbb_cli.config.setup import bootstrap
6
- from openbb_cli.controllers.cli_controller import launch
7
-
8
-
9
- def main():
10
- """Use the main entry point for the OpenBB CLI."""
11
- bootstrap()
12
-
13
- dev = "--dev" in sys.argv[1:]
14
- debug = "--debug" in sys.argv[1:]
15
-
16
- launch(dev, debug)
17
-
18
-
19
- if __name__ == "__main__":
20
- main()