openbb-cli 1.0.1__tar.gz → 1.0.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.
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/PKG-INFO +2 -2
- openbb_cli-1.0.3/openbb_cli/argparse_translator/argparse_argument.py +63 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/argparse_translator/argparse_class_processor.py +7 -7
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/argparse_translator/argparse_translator.py +70 -252
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/argparse_translator/obbject_registry.py +42 -9
- openbb_cli-1.0.3/openbb_cli/argparse_translator/reference_processor.py +142 -0
- openbb_cli-1.0.3/openbb_cli/argparse_translator/utils.py +76 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/cli.py +9 -1
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/base_controller.py +107 -39
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/base_platform_controller.py +72 -29
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/choices.py +22 -5
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/cli_controller.py +9 -16
- openbb_cli-1.0.3/openbb_cli/controllers/settings_controller.py +142 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/utils.py +54 -48
- openbb_cli-1.0.3/openbb_cli/models/settings.py +167 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/session.py +0 -3
- openbb_cli-1.0.3/openbb_cli/utils/utils.py +34 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/pyproject.toml +2 -2
- openbb_cli-1.0.1/openbb_cli/controllers/settings_controller.py +0 -373
- openbb_cli-1.0.1/openbb_cli/models/settings.py +0 -73
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/README.md +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/__init__.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/argparse_translator/__init__.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/routines/routine_example.openbb +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/Consolas.ttf +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/dark.mpfstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/dark.mplrc.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/dark.mplstyle +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/dark.pltstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/dark.richstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/light.mpfstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/light.mplrc.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/light.mplstyle +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/light.pltstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/light.richstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/default/tables.pltstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/assets/styles/user/openbb.richstyle.json +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/config/__init__.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/config/completer.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/config/console.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/config/constants.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/config/menu_text.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/config/setup.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/config/style.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/hub_service.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/platform_controller_factory.py +0 -0
- {openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/controllers/script_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: openbb-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
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,7 @@ 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.
|
|
15
|
+
Requires-Dist: openbb[all] (>=4.2.3,<5.0.0)
|
|
16
16
|
Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
|
|
17
17
|
Requires-Dist: prompt-toolkit (>=3.0.16,<4.0.0)
|
|
18
18
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
@@ -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]
|
{openbb_cli-1.0.1 → openbb_cli-1.0.3}/openbb_cli/argparse_translator/argparse_class_processor.py
RENAMED
|
@@ -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
|
-
|
|
9
|
-
|
|
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 =
|
|
80
|
+
rp = ReferenceToArgumentsProcessor(reference)
|
|
81
81
|
return rp.custom_groups.get(route, {}) # type: ignore
|
|
82
82
|
|
|
83
83
|
@classmethod
|
|
@@ -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,181 +20,37 @@ from typing import (
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
from openbb_core.app.model.field import OpenBBField
|
|
22
|
-
from pydantic import BaseModel
|
|
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
|
-
name: str
|
|
37
|
-
type: Optional[Any]
|
|
38
|
-
dest: str
|
|
39
|
-
default: Any
|
|
40
|
-
required: bool
|
|
41
|
-
action: Literal["store_true", "store"]
|
|
42
|
-
help: str
|
|
43
|
-
nargs: Optional[Literal["+"]]
|
|
44
|
-
choices: Optional[Tuple]
|
|
45
|
-
|
|
46
|
-
@model_validator(mode="after") # type: ignore
|
|
47
|
-
@classmethod
|
|
48
|
-
def validate_action(cls, values: "CustomArgument"):
|
|
49
|
-
if values.type is bool and values.action != "store_true":
|
|
50
|
-
raise ValueError('If type is bool, action must be "store_true"')
|
|
51
|
-
return values
|
|
52
|
-
|
|
53
|
-
@model_validator(mode="after") # type: ignore
|
|
54
|
-
@classmethod
|
|
55
|
-
def remove_props_on_store_true(cls, values: "CustomArgument"):
|
|
56
|
-
if values.action == "store_true":
|
|
57
|
-
values.type = None
|
|
58
|
-
values.nargs = None
|
|
59
|
-
values.choices = None
|
|
60
|
-
return values
|
|
61
|
-
|
|
62
|
-
# override
|
|
63
|
-
def model_dump(self, **kwargs):
|
|
64
|
-
res = super().model_dump(**kwargs)
|
|
65
|
-
|
|
66
|
-
# Check if choices is present and if it's an empty tuple remove it
|
|
67
|
-
if "choices" in res and not res["choices"]:
|
|
68
|
-
del res["choices"]
|
|
69
|
-
|
|
70
|
-
return res
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class CustomArgumentGroup(BaseModel):
|
|
74
|
-
name: str
|
|
75
|
-
arguments: List[CustomArgument]
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class ReferenceToCustomArgumentsProcessor:
|
|
79
|
-
def __init__(self, reference: Dict[str, Dict]):
|
|
80
|
-
"""Initializes the ReferenceToCustomArgumentsProcessor."""
|
|
81
|
-
self.reference = reference
|
|
82
|
-
self.custom_groups: Dict[str, List[CustomArgumentGroup]] = {}
|
|
83
|
-
|
|
84
|
-
self.build_custom_groups()
|
|
85
|
-
|
|
86
|
-
@staticmethod
|
|
87
|
-
def _make_type_parsable(type_: str) -> type:
|
|
88
|
-
"""Make the type parsable by removing the annotations."""
|
|
89
|
-
if "Union" in type_ and "str" in type_:
|
|
90
|
-
return str
|
|
91
|
-
if "Union" in type_ and "int" in type_:
|
|
92
|
-
return int
|
|
93
|
-
if type_ in ["date", "datetime.time", "time"]:
|
|
94
|
-
return str
|
|
95
|
-
|
|
96
|
-
if any(x in type_ for x in ["gt=", "ge=", "lt=", "le="]):
|
|
97
|
-
if "Annotated" in type_:
|
|
98
|
-
type_ = type_.replace("Annotated[", "").replace("]", "")
|
|
99
|
-
type_ = type_.split(",")[0]
|
|
100
|
-
|
|
101
|
-
return eval(type_) # noqa: S307, E501 pylint: disable=eval-used
|
|
102
|
-
|
|
103
|
-
def _parse_type(self, type_: str) -> type:
|
|
104
|
-
"""Parse the type from the string representation."""
|
|
105
|
-
type_ = self._make_type_parsable(type_) # type: ignore
|
|
106
|
-
|
|
107
|
-
if get_origin(type_) is Literal:
|
|
108
|
-
type_ = type(get_args(type_)[0]) # type: ignore
|
|
109
|
-
|
|
110
|
-
return type_ # type: ignore
|
|
111
|
-
|
|
112
|
-
def _get_nargs(self, type_: type) -> Optional[Union[int, str]]:
|
|
113
|
-
"""Get the nargs for the given type."""
|
|
114
|
-
if get_origin(type_) is list:
|
|
115
|
-
return "+"
|
|
116
|
-
return None
|
|
117
|
-
|
|
118
|
-
def _get_choices(self, type_: str, custom_choices: Any) -> Tuple:
|
|
119
|
-
"""Get the choices for the given type."""
|
|
120
|
-
type_ = self._make_type_parsable(type_) # type: ignore
|
|
121
|
-
type_origin = get_origin(type_)
|
|
122
|
-
|
|
123
|
-
choices = ()
|
|
124
|
-
|
|
125
|
-
if type_origin is Literal:
|
|
126
|
-
choices = get_args(type_)
|
|
127
|
-
|
|
128
|
-
if type_origin is list:
|
|
129
|
-
type_ = get_args(type_)[0]
|
|
130
|
-
|
|
131
|
-
if get_origin(type_) is Literal:
|
|
132
|
-
choices = get_args(type_)
|
|
133
|
-
|
|
134
|
-
if type_origin is Union and type(None) in get_args(type_):
|
|
135
|
-
# remove NoneType from the args
|
|
136
|
-
args = [arg for arg in get_args(type_) if arg != type(None)]
|
|
137
|
-
# if there is only one arg left, use it
|
|
138
|
-
if len(args) > 1:
|
|
139
|
-
raise ValueError("Union with NoneType should have only one type left")
|
|
140
|
-
type_ = args[0]
|
|
141
|
-
|
|
142
|
-
if get_origin(type_) is Literal:
|
|
143
|
-
choices = get_args(type_)
|
|
144
|
-
|
|
145
|
-
if custom_choices:
|
|
146
|
-
return tuple(custom_choices)
|
|
147
|
-
|
|
148
|
-
return choices
|
|
149
|
-
|
|
150
|
-
def build_custom_groups(self):
|
|
151
|
-
"""Build the custom groups from the reference."""
|
|
152
|
-
for route, v in self.reference.items():
|
|
153
|
-
for provider, args in v["parameters"].items():
|
|
154
|
-
if provider == "standard":
|
|
155
|
-
continue
|
|
156
|
-
|
|
157
|
-
custom_arguments = []
|
|
158
|
-
for arg in args:
|
|
159
|
-
if arg.get("standard"):
|
|
160
|
-
continue
|
|
161
|
-
|
|
162
|
-
type_ = self._parse_type(arg["type"])
|
|
163
|
-
|
|
164
|
-
custom_arguments.append(
|
|
165
|
-
CustomArgument(
|
|
166
|
-
name=arg["name"],
|
|
167
|
-
type=type_,
|
|
168
|
-
dest=arg["name"],
|
|
169
|
-
default=arg["default"],
|
|
170
|
-
required=not (arg["optional"]),
|
|
171
|
-
action="store" if type_ != bool else "store_true",
|
|
172
|
-
help=arg["description"],
|
|
173
|
-
nargs=self._get_nargs(type_), # type: ignore
|
|
174
|
-
choices=self._get_choices(
|
|
175
|
-
arg["type"], custom_choices=arg["choices"]
|
|
176
|
-
),
|
|
177
|
-
)
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
group = CustomArgumentGroup(name=provider, arguments=custom_arguments)
|
|
181
|
-
|
|
182
|
-
if route not in self.custom_groups:
|
|
183
|
-
self.custom_groups[route] = []
|
|
184
|
-
|
|
185
|
-
self.custom_groups[route].append(group)
|
|
186
|
-
|
|
187
|
-
|
|
188
43
|
class ArgparseTranslator:
|
|
44
|
+
"""Class to translate a function into an argparse program."""
|
|
45
|
+
|
|
189
46
|
def __init__(
|
|
190
47
|
self,
|
|
191
48
|
func: Callable,
|
|
192
|
-
custom_argument_groups: Optional[List[
|
|
49
|
+
custom_argument_groups: Optional[List[ArgparseArgumentGroupModel]] = None,
|
|
193
50
|
add_help: Optional[bool] = True,
|
|
194
51
|
):
|
|
195
52
|
"""
|
|
196
|
-
|
|
53
|
+
Initialize the ArgparseTranslator.
|
|
197
54
|
|
|
198
55
|
Args:
|
|
199
56
|
func (Callable): The function to translate into an argparse program.
|
|
@@ -225,45 +82,6 @@ class ArgparseTranslator:
|
|
|
225
82
|
def _handle_argument_in_groups(self, argument, group):
|
|
226
83
|
"""Handle the argument and add it to the parser."""
|
|
227
84
|
|
|
228
|
-
def _in_group(arg, group_title):
|
|
229
|
-
for action_group in self._parser._action_groups:
|
|
230
|
-
if action_group.title == group_title:
|
|
231
|
-
for action in action_group._group_actions:
|
|
232
|
-
opts = action.option_strings
|
|
233
|
-
if (opts and opts[0] == arg) or action.dest == arg:
|
|
234
|
-
return True
|
|
235
|
-
return False
|
|
236
|
-
|
|
237
|
-
def _remove_argument(arg) -> List[Optional[str]]:
|
|
238
|
-
groups_w_arg = []
|
|
239
|
-
|
|
240
|
-
# remove the argument from the parser
|
|
241
|
-
for action in self._parser._actions:
|
|
242
|
-
opts = action.option_strings
|
|
243
|
-
if (opts and opts[0] == arg) or action.dest == arg:
|
|
244
|
-
self._parser._remove_action(action)
|
|
245
|
-
break
|
|
246
|
-
|
|
247
|
-
# remove from all groups
|
|
248
|
-
for action_group in self._parser._action_groups:
|
|
249
|
-
for action in action_group._group_actions:
|
|
250
|
-
opts = action.option_strings
|
|
251
|
-
if (opts and opts[0] == arg) or action.dest == arg:
|
|
252
|
-
action_group._group_actions.remove(action)
|
|
253
|
-
groups_w_arg.append(action_group.title)
|
|
254
|
-
|
|
255
|
-
# remove from _action_groups dict
|
|
256
|
-
self._parser._option_string_actions.pop(f"--{arg}", None)
|
|
257
|
-
|
|
258
|
-
return groups_w_arg
|
|
259
|
-
|
|
260
|
-
def _get_arg_choices(arg) -> Tuple:
|
|
261
|
-
for action in self._parser._actions:
|
|
262
|
-
opts = action.option_strings
|
|
263
|
-
if (opts and opts[0] == arg) or action.dest == arg:
|
|
264
|
-
return tuple(action.choices or ())
|
|
265
|
-
return ()
|
|
266
|
-
|
|
267
85
|
def _update_providers(
|
|
268
86
|
input_string: str, new_provider: List[Optional[str]]
|
|
269
87
|
) -> str:
|
|
@@ -285,23 +103,27 @@ class ArgparseTranslator:
|
|
|
285
103
|
kwargs = argument.model_dump(exclude={"name"}, exclude_none=True)
|
|
286
104
|
model_choices = kwargs.get("choices", ()) or ()
|
|
287
105
|
# extend choices
|
|
288
|
-
|
|
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)
|
|
289
109
|
|
|
290
110
|
# check if the argument is in the required arguments
|
|
291
|
-
if
|
|
111
|
+
if in_group(self._parser, argument.name, group_title="required arguments"):
|
|
292
112
|
for action in self._required._group_actions:
|
|
293
113
|
if action.dest == argument.name and choices:
|
|
294
114
|
# update choices
|
|
295
115
|
action.choices = choices
|
|
116
|
+
set_optional_choices(action, optional_choices)
|
|
296
117
|
return
|
|
297
118
|
|
|
298
119
|
# check if the argument is in the optional arguments
|
|
299
|
-
if
|
|
120
|
+
if in_group(self._parser, argument.name, group_title="optional arguments"):
|
|
300
121
|
for action in self._parser._actions:
|
|
301
122
|
if action.dest == argument.name:
|
|
302
123
|
# update choices
|
|
303
124
|
if choices:
|
|
304
125
|
action.choices = choices
|
|
126
|
+
set_optional_choices(action, optional_choices)
|
|
305
127
|
if argument.name not in self.signature.parameters:
|
|
306
128
|
# update help
|
|
307
129
|
action.help = _update_providers(
|
|
@@ -309,9 +131,16 @@ class ArgparseTranslator:
|
|
|
309
131
|
)
|
|
310
132
|
return
|
|
311
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
|
+
|
|
312
141
|
# if the argument is in use, remove it from all groups
|
|
313
142
|
# and return the groups that had the argument
|
|
314
|
-
groups_w_arg =
|
|
143
|
+
groups_w_arg = remove_argument(self._parser, argument.name)
|
|
315
144
|
groups_w_arg.append(group.title) # add current group
|
|
316
145
|
|
|
317
146
|
# add it to the optional arguments group instead
|
|
@@ -319,16 +148,17 @@ class ArgparseTranslator:
|
|
|
319
148
|
kwargs["choices"] = choices # update choices
|
|
320
149
|
# add provider info to the help
|
|
321
150
|
kwargs["help"] = _update_providers(argument.help or "", groups_w_arg)
|
|
322
|
-
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)
|
|
323
153
|
|
|
324
154
|
@property
|
|
325
155
|
def parser(self) -> argparse.ArgumentParser:
|
|
156
|
+
"""Get the argparse parser."""
|
|
326
157
|
return deepcopy(self._parser)
|
|
327
158
|
|
|
328
159
|
@staticmethod
|
|
329
160
|
def _build_description(func_doc: str) -> str:
|
|
330
|
-
"""
|
|
331
|
-
|
|
161
|
+
"""Build the description of the argparse program from the function docstring."""
|
|
332
162
|
patterns = ["openbb\n ======", "Parameters\n ----------"]
|
|
333
163
|
|
|
334
164
|
if func_doc:
|
|
@@ -341,31 +171,34 @@ class ArgparseTranslator:
|
|
|
341
171
|
|
|
342
172
|
@staticmethod
|
|
343
173
|
def _param_is_default(param: inspect.Parameter) -> bool:
|
|
344
|
-
"""
|
|
174
|
+
"""Return True if the parameter has a default value."""
|
|
345
175
|
return param.default != inspect.Parameter.empty
|
|
346
176
|
|
|
347
177
|
def _get_action_type(self, param: inspect.Parameter) -> str:
|
|
348
|
-
"""
|
|
178
|
+
"""Return the argparse action type for the given parameter."""
|
|
349
179
|
param_type = self.type_hints[param.name]
|
|
180
|
+
type_origin = get_origin(param_type)
|
|
350
181
|
|
|
351
|
-
if param_type == bool
|
|
352
|
-
|
|
353
|
-
|
|
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"
|
|
354
187
|
|
|
355
188
|
def _get_type_and_choices(
|
|
356
189
|
self, param: inspect.Parameter
|
|
357
190
|
) -> Tuple[Type[Any], Tuple[Any, ...]]:
|
|
358
|
-
"""
|
|
191
|
+
"""Return the type and choices for the given parameter."""
|
|
359
192
|
param_type = self.type_hints[param.name]
|
|
360
193
|
type_origin = get_origin(param_type)
|
|
361
194
|
|
|
362
|
-
choices = ()
|
|
195
|
+
choices: tuple[Any, ...] = ()
|
|
363
196
|
|
|
364
197
|
if type_origin is Literal:
|
|
365
198
|
choices = get_args(param_type)
|
|
366
199
|
param_type = type(choices[0]) # type: ignore
|
|
367
200
|
|
|
368
|
-
if type_origin is list:
|
|
201
|
+
if type_origin is list:
|
|
369
202
|
param_type = get_args(param_type)[0]
|
|
370
203
|
|
|
371
204
|
if get_origin(param_type) is Literal:
|
|
@@ -413,32 +246,26 @@ class ArgparseTranslator:
|
|
|
413
246
|
|
|
414
247
|
@classmethod
|
|
415
248
|
def _get_argument_custom_help(cls, param: inspect.Parameter) -> Optional[str]:
|
|
416
|
-
"""
|
|
249
|
+
"""Return the help annotation for the given parameter."""
|
|
417
250
|
base_annotation = param.annotation
|
|
418
251
|
_, custom_annotations = cls._split_annotation(base_annotation, OpenBBField)
|
|
419
252
|
help_annotation = (
|
|
420
253
|
custom_annotations[0].description if custom_annotations else None
|
|
421
254
|
)
|
|
422
|
-
if not help_annotation:
|
|
423
|
-
# try to get it from the docstring
|
|
424
|
-
pass
|
|
425
255
|
return help_annotation
|
|
426
256
|
|
|
427
257
|
@classmethod
|
|
428
258
|
def _get_argument_custom_choices(cls, param: inspect.Parameter) -> Optional[str]:
|
|
429
|
-
"""
|
|
259
|
+
"""Return the help annotation for the given parameter."""
|
|
430
260
|
base_annotation = param.annotation
|
|
431
261
|
_, custom_annotations = cls._split_annotation(base_annotation, OpenBBField)
|
|
432
262
|
choices_annotation = (
|
|
433
263
|
custom_annotations[0].choices if custom_annotations else None
|
|
434
264
|
)
|
|
435
|
-
if not choices_annotation:
|
|
436
|
-
# try to get it from the docstring
|
|
437
|
-
pass
|
|
438
265
|
return choices_annotation
|
|
439
266
|
|
|
440
267
|
def _get_nargs(self, param: inspect.Parameter) -> Optional[str]:
|
|
441
|
-
"""
|
|
268
|
+
"""Return the nargs annotation for the given parameter."""
|
|
442
269
|
param_type = self.type_hints[param.name]
|
|
443
270
|
origin = get_origin(param_type)
|
|
444
271
|
|
|
@@ -453,11 +280,8 @@ class ArgparseTranslator:
|
|
|
453
280
|
return None
|
|
454
281
|
|
|
455
282
|
def _generate_argparse_arguments(self, parameters) -> None:
|
|
456
|
-
"""
|
|
283
|
+
"""Generate the argparse arguments from the function parameters."""
|
|
457
284
|
for param in parameters.values():
|
|
458
|
-
# TODO : how to handle kwargs?
|
|
459
|
-
# it's possible to add unknown arguments when parsing as follows:
|
|
460
|
-
# args, unknown_args = parser.parse_known_args()
|
|
461
285
|
if param.name == "kwargs":
|
|
462
286
|
continue
|
|
463
287
|
|
|
@@ -505,32 +329,27 @@ class ArgparseTranslator:
|
|
|
505
329
|
|
|
506
330
|
required = not self._param_is_default(param)
|
|
507
331
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if param_type == bool:
|
|
522
|
-
# store_true action does not accept the below kwargs
|
|
523
|
-
kwargs.pop("type")
|
|
524
|
-
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)
|
|
525
344
|
|
|
526
345
|
if required:
|
|
527
346
|
self._required.add_argument(
|
|
528
|
-
f"--{
|
|
347
|
+
f"--{argument.name}",
|
|
529
348
|
**kwargs,
|
|
530
349
|
)
|
|
531
350
|
else:
|
|
532
351
|
self._parser.add_argument(
|
|
533
|
-
f"--{
|
|
352
|
+
f"--{argument.name}",
|
|
534
353
|
**kwargs,
|
|
535
354
|
)
|
|
536
355
|
|
|
@@ -556,7 +375,6 @@ class ArgparseTranslator:
|
|
|
556
375
|
# for each argument in the signature that is a custom type, we need to
|
|
557
376
|
# update the kwargs with the custom type kwargs
|
|
558
377
|
for param in self.signature.parameters.values():
|
|
559
|
-
# TODO : how to handle kwargs?
|
|
560
378
|
if param.name == "kwargs":
|
|
561
379
|
continue
|
|
562
380
|
param_type, _ = self._get_type_and_choices(param)
|
|
@@ -571,7 +389,7 @@ class ArgparseTranslator:
|
|
|
571
389
|
parsed_args: Optional[argparse.Namespace] = None,
|
|
572
390
|
) -> Any:
|
|
573
391
|
"""
|
|
574
|
-
|
|
392
|
+
Execute the original function with the parsed arguments.
|
|
575
393
|
|
|
576
394
|
Args:
|
|
577
395
|
parsed_args (Optional[argparse.Namespace], optional): The parsed arguments. Defaults to None.
|
|
@@ -602,7 +420,7 @@ class ArgparseTranslator:
|
|
|
602
420
|
|
|
603
421
|
def parse_args_and_execute(self) -> Any:
|
|
604
422
|
"""
|
|
605
|
-
|
|
423
|
+
Parse the arguments and executes the original function.
|
|
606
424
|
|
|
607
425
|
Returns:
|
|
608
426
|
Any: The return value of the original function.
|
|
@@ -612,7 +430,7 @@ class ArgparseTranslator:
|
|
|
612
430
|
|
|
613
431
|
def translate(self) -> Callable:
|
|
614
432
|
"""
|
|
615
|
-
|
|
433
|
+
Wrap the original function with an argparse program.
|
|
616
434
|
|
|
617
435
|
Returns:
|
|
618
436
|
Callable: The original function wrapped with an argparse program.
|