openbb-cli 1.2.0__tar.gz → 1.3.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.
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/PKG-INFO +5 -31
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/README.md +2 -28
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/argparse_translator/argparse_argument.py +4 -7
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/argparse_translator/argparse_class_processor.py +11 -11
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/argparse_translator/argparse_translator.py +180 -114
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/argparse_translator/obbject_registry.py +8 -9
- openbb_cli-1.3.0/openbb_cli/argparse_translator/reference_processor.py +122 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/argparse_translator/utils.py +3 -6
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/cli.py +2 -1
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/config/completer.py +13 -20
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/config/console.py +3 -3
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/config/menu_text.py +3 -4
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/config/style.py +13 -15
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/base_controller.py +68 -184
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/base_platform_controller.py +12 -18
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/choices.py +5 -4
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/cli_controller.py +51 -79
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/platform_controller_factory.py +3 -5
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/script_parser.py +11 -18
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/settings_controller.py +5 -5
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/controllers/utils.py +33 -78
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/session.py +5 -9
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/pyproject.toml +3 -3
- openbb_cli-1.2.0/openbb_cli/argparse_translator/reference_processor.py +0 -142
- openbb_cli-1.2.0/openbb_cli/controllers/hub_service.py +0 -107
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/__init__.py +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/argparse_translator/__init__.py +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/routines/routine_example.openbb +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/Consolas.ttf +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/dark.mpfstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/dark.mplrc.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/dark.mplstyle +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/dark.pltstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/dark.richstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/light.mpfstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/light.mplrc.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/light.mplstyle +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/light.pltstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/light.richstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/default/tables.pltstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/assets/styles/user/openbb.richstyle.json +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/config/__init__.py +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/config/constants.py +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/config/setup.py +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/models/settings.py +0 -0
- {openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/utils/utils.py +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbb-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Investment Research for Everyone, Anywhere.
|
|
5
5
|
License: AGPL-3.0-only
|
|
6
6
|
Author: OpenBB
|
|
7
7
|
Author-email: hello@openbb.co
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.10,<3.14
|
|
9
9
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Requires-Dist: openbb[all] (>=4.
|
|
15
|
+
Requires-Dist: openbb[all] (>=4.6.0,<5.0.0)
|
|
16
16
|
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
|
|
17
17
|
Requires-Dist: prompt-toolkit (>=3.0.50,<4.0.0)
|
|
18
18
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
@@ -61,33 +61,7 @@ Which should result in the following output:
|
|
|
61
61
|
|
|
62
62
|

|
|
63
63
|
|
|
64
|
-
##
|
|
64
|
+
## Documentation
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
1. OpenBB Hub
|
|
69
|
-
2. Local file
|
|
70
|
-
|
|
71
|
-
### 1. OpenBB Hub
|
|
72
|
-
|
|
73
|
-
Set your keys at [OpenBB Hub](https://my.openbb.co/app/platform/credentials) and get your personal access token from <https://my.openbb.co/app/platform/pat> to connect with your account.
|
|
74
|
-
|
|
75
|
-
> Once you log in, on the Platform CLI (through the `/account` menu, all your credentials will be in sync with the OpenBB Hub.)
|
|
76
|
-
|
|
77
|
-
### 2. Local file
|
|
78
|
-
|
|
79
|
-
You can specify the keys directly in the `~/.openbb_platform/user_settings.json` file.
|
|
80
|
-
|
|
81
|
-
Populate this file with the following template and replace the values with your keys:
|
|
82
|
-
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"credentials": {
|
|
86
|
-
"fmp_api_key": "REPLACE_ME",
|
|
87
|
-
"polygon_api_key": "REPLACE_ME",
|
|
88
|
-
"benzinga_api_key": "REPLACE_ME",
|
|
89
|
-
"fred_api_key": "REPLACE_ME"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
```
|
|
66
|
+
View the user documentation for this package [here](https://docs.openbb.co/cli)
|
|
93
67
|
|
|
@@ -36,32 +36,6 @@ Which should result in the following output:
|
|
|
36
36
|
|
|
37
37
|

|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Documentation
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
1. OpenBB Hub
|
|
44
|
-
2. Local file
|
|
45
|
-
|
|
46
|
-
### 1. OpenBB Hub
|
|
47
|
-
|
|
48
|
-
Set your keys at [OpenBB Hub](https://my.openbb.co/app/platform/credentials) and get your personal access token from <https://my.openbb.co/app/platform/pat> to connect with your account.
|
|
49
|
-
|
|
50
|
-
> Once you log in, on the Platform CLI (through the `/account` menu, all your credentials will be in sync with the OpenBB Hub.)
|
|
51
|
-
|
|
52
|
-
### 2. Local file
|
|
53
|
-
|
|
54
|
-
You can specify the keys directly in the `~/.openbb_platform/user_settings.json` file.
|
|
55
|
-
|
|
56
|
-
Populate this file with the following template and replace the values with your keys:
|
|
57
|
-
|
|
58
|
-
```json
|
|
59
|
-
{
|
|
60
|
-
"credentials": {
|
|
61
|
-
"fmp_api_key": "REPLACE_ME",
|
|
62
|
-
"polygon_api_key": "REPLACE_ME",
|
|
63
|
-
"benzinga_api_key": "REPLACE_ME",
|
|
64
|
-
"fred_api_key": "REPLACE_ME"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
41
|
+
View the user documentation for this package [here](https://docs.openbb.co/cli)
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
4
|
Any,
|
|
5
|
-
List,
|
|
6
5
|
Literal,
|
|
7
|
-
Optional,
|
|
8
|
-
Tuple,
|
|
9
6
|
)
|
|
10
7
|
|
|
11
8
|
from pydantic import BaseModel, model_validator
|
|
@@ -22,9 +19,9 @@ class ArgparseArgumentModel(BaseModel):
|
|
|
22
19
|
default: Any
|
|
23
20
|
required: bool
|
|
24
21
|
action: Literal["store_true", "store"]
|
|
25
|
-
help:
|
|
26
|
-
nargs:
|
|
27
|
-
choices:
|
|
22
|
+
help: str | None
|
|
23
|
+
nargs: Literal["+"] | None
|
|
24
|
+
choices: tuple | None
|
|
28
25
|
|
|
29
26
|
@model_validator(mode="after") # type: ignore
|
|
30
27
|
@classmethod
|
|
@@ -60,4 +57,4 @@ class ArgparseArgumentGroupModel(BaseModel):
|
|
|
60
57
|
"""Pydantic model for a custom argument group."""
|
|
61
58
|
|
|
62
59
|
name: str
|
|
63
|
-
arguments:
|
|
60
|
+
arguments: list[ArgparseArgumentModel]
|
{openbb_cli-1.2.0 → openbb_cli-1.3.0}/openbb_cli/argparse_translator/argparse_class_processor.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Module for the ArgparseClassProcessor class."""
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
# TODO: this needs to be done differently
|
|
7
7
|
from openbb_core.app.static.container import Container
|
|
@@ -16,13 +16,13 @@ class ArgparseClassProcessor:
|
|
|
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
|
-
_reference:
|
|
19
|
+
_reference: dict[str, Any] = {}
|
|
20
20
|
|
|
21
21
|
def __init__(
|
|
22
22
|
self,
|
|
23
|
-
target_class:
|
|
23
|
+
target_class: type,
|
|
24
24
|
add_help: bool = False,
|
|
25
|
-
reference:
|
|
25
|
+
reference: dict[str, Any] | None = None,
|
|
26
26
|
):
|
|
27
27
|
"""
|
|
28
28
|
Initialize the ArgparseClassProcessor.
|
|
@@ -34,10 +34,10 @@ class ArgparseClassProcessor:
|
|
|
34
34
|
add_help : Optional[bool]
|
|
35
35
|
Whether to add help to the ArgparseTranslators.
|
|
36
36
|
"""
|
|
37
|
-
self._target_class:
|
|
37
|
+
self._target_class: type = target_class
|
|
38
38
|
self._add_help: bool = add_help
|
|
39
|
-
self._translators:
|
|
40
|
-
self._paths:
|
|
39
|
+
self._translators: dict[str, ArgparseTranslator] = {}
|
|
40
|
+
self._paths: dict[str, str] = {}
|
|
41
41
|
|
|
42
42
|
ArgparseClassProcessor._reference = reference or {}
|
|
43
43
|
|
|
@@ -48,7 +48,7 @@ class ArgparseClassProcessor:
|
|
|
48
48
|
self._build_paths(target=self._target_class)
|
|
49
49
|
|
|
50
50
|
@property
|
|
51
|
-
def translators(self) ->
|
|
51
|
+
def translators(self) -> dict[str, ArgparseTranslator]:
|
|
52
52
|
"""
|
|
53
53
|
Get the ArgparseTranslators associated with the target class.
|
|
54
54
|
|
|
@@ -60,7 +60,7 @@ class ArgparseClassProcessor:
|
|
|
60
60
|
return self._translators
|
|
61
61
|
|
|
62
62
|
@property
|
|
63
|
-
def paths(self) ->
|
|
63
|
+
def paths(self) -> dict[str, str]:
|
|
64
64
|
"""
|
|
65
65
|
Get the paths associated with the target class.
|
|
66
66
|
|
|
@@ -72,7 +72,7 @@ class ArgparseClassProcessor:
|
|
|
72
72
|
return self._paths
|
|
73
73
|
|
|
74
74
|
@classmethod
|
|
75
|
-
def _custom_groups_from_reference(cls, class_name: str, function_name: str) ->
|
|
75
|
+
def _custom_groups_from_reference(cls, class_name: str, function_name: str) -> dict:
|
|
76
76
|
route = f"/{class_name.replace('_', '/')}/{function_name}"
|
|
77
77
|
reference = {route: cls._reference[route]} if route in cls._reference else {}
|
|
78
78
|
if not reference:
|
|
@@ -85,7 +85,7 @@ class ArgparseClassProcessor:
|
|
|
85
85
|
cls,
|
|
86
86
|
target: type,
|
|
87
87
|
add_help: bool = False,
|
|
88
|
-
) ->
|
|
88
|
+
) -> dict[str, ArgparseTranslator]:
|
|
89
89
|
methods = {}
|
|
90
90
|
|
|
91
91
|
for name, member in inspect.getmembers(target):
|
|
@@ -3,16 +3,12 @@
|
|
|
3
3
|
import argparse
|
|
4
4
|
import inspect
|
|
5
5
|
import re
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from copy import deepcopy
|
|
7
8
|
from typing import (
|
|
9
|
+
Annotated,
|
|
8
10
|
Any,
|
|
9
|
-
Callable,
|
|
10
|
-
Dict,
|
|
11
|
-
List,
|
|
12
11
|
Literal,
|
|
13
|
-
Optional,
|
|
14
|
-
Tuple,
|
|
15
|
-
Type,
|
|
16
12
|
Union,
|
|
17
13
|
get_args,
|
|
18
14
|
get_origin,
|
|
@@ -21,7 +17,6 @@ from typing import (
|
|
|
21
17
|
|
|
22
18
|
from openbb_core.app.model.field import OpenBBField
|
|
23
19
|
from pydantic import BaseModel
|
|
24
|
-
from typing_extensions import Annotated
|
|
25
20
|
|
|
26
21
|
from openbb_cli.argparse_translator.argparse_argument import (
|
|
27
22
|
ArgparseArgumentGroupModel,
|
|
@@ -46,8 +41,8 @@ class ArgparseTranslator:
|
|
|
46
41
|
def __init__(
|
|
47
42
|
self,
|
|
48
43
|
func: Callable,
|
|
49
|
-
custom_argument_groups:
|
|
50
|
-
add_help:
|
|
44
|
+
custom_argument_groups: list[ArgparseArgumentGroupModel] | None = None,
|
|
45
|
+
add_help: bool | None = True,
|
|
51
46
|
):
|
|
52
47
|
"""
|
|
53
48
|
Initialize the ArgparseTranslator.
|
|
@@ -59,7 +54,7 @@ class ArgparseTranslator:
|
|
|
59
54
|
self.func = func
|
|
60
55
|
self.signature = inspect.signature(func)
|
|
61
56
|
self.type_hints = get_type_hints(func)
|
|
62
|
-
self.provider_parameters:
|
|
57
|
+
self.provider_parameters: dict[str, list[str]] = {}
|
|
63
58
|
|
|
64
59
|
self._parser = argparse.ArgumentParser(
|
|
65
60
|
prog=func.__name__,
|
|
@@ -82,9 +77,7 @@ class ArgparseTranslator:
|
|
|
82
77
|
def _handle_argument_in_groups(self, argument, group):
|
|
83
78
|
"""Handle the argument and add it to the parser."""
|
|
84
79
|
|
|
85
|
-
def _update_providers(
|
|
86
|
-
input_string: str, new_provider: List[Optional[str]]
|
|
87
|
-
) -> str:
|
|
80
|
+
def _update_providers(input_string: str, new_provider: list[str | None]) -> str:
|
|
88
81
|
pattern = r"\(provider:\s*(.*?)\)"
|
|
89
82
|
providers = re.findall(pattern, input_string)
|
|
90
83
|
providers.extend(new_provider)
|
|
@@ -159,119 +152,192 @@ class ArgparseTranslator:
|
|
|
159
152
|
@staticmethod
|
|
160
153
|
def _build_description(func_doc: str) -> str:
|
|
161
154
|
"""Build the description of the argparse program from the function docstring."""
|
|
162
|
-
|
|
155
|
+
# Remove only the Examples section and the openbb header if present
|
|
156
|
+
patterns_to_remove = [
|
|
157
|
+
(r"openbb\n\s+={3,}\n", ""), # Remove openbb header
|
|
158
|
+
(r"\n\s*Examples\n\s*-{3,}\n.*", ""), # Remove Examples section to end
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
for pattern, replacement in patterns_to_remove:
|
|
162
|
+
func_doc = re.sub(pattern, replacement, func_doc, flags=re.DOTALL)
|
|
163
|
+
|
|
164
|
+
# Clean up type annotations in docstrings
|
|
165
|
+
def clean_type_annotation(type_str: str) -> str:
|
|
166
|
+
"""Clean up a single type annotation."""
|
|
167
|
+
# First, handle the pipe union syntax: type1 | type2 -> type1 or type2
|
|
168
|
+
# Do this FIRST before other transformations
|
|
169
|
+
type_str = re.sub(r"\s*\|\s*", " or ", type_str)
|
|
163
170
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if pattern in func_doc:
|
|
167
|
-
func_doc = func_doc[: func_doc.index(pattern)].strip()
|
|
168
|
-
break
|
|
171
|
+
# Handle Annotated[type, ...] -> type
|
|
172
|
+
type_str = re.sub(r"Annotated\[([^,\]]+)(?:,\s*[^\]]+)?\]", r"\1", type_str)
|
|
169
173
|
|
|
170
|
-
|
|
174
|
+
# Handle Union[type1, type2, ...] -> type1 or type2 or ...
|
|
175
|
+
type_str = re.sub(
|
|
176
|
+
r"Union\[([^\]]+)\]",
|
|
177
|
+
lambda m: " or ".join(m.group(1).split(", ")),
|
|
178
|
+
type_str,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Handle Optional[type] -> type or None
|
|
182
|
+
type_str = re.sub(r"Optional\[([^\]]+)\]", r"\1 or None", type_str)
|
|
183
|
+
|
|
184
|
+
# Deduplicate "or" separated types
|
|
185
|
+
parts = [p.strip() for p in type_str.split(" or ")]
|
|
186
|
+
|
|
187
|
+
# Remove duplicates while preserving order (case-insensitive)
|
|
188
|
+
seen = set()
|
|
189
|
+
unique_parts = []
|
|
190
|
+
for part in parts:
|
|
191
|
+
part_lower = part.lower()
|
|
192
|
+
if part_lower not in seen:
|
|
193
|
+
seen.add(part_lower)
|
|
194
|
+
unique_parts.append(part)
|
|
195
|
+
|
|
196
|
+
# If None is present, move it to the end
|
|
197
|
+
if "None" in unique_parts:
|
|
198
|
+
unique_parts.remove("None")
|
|
199
|
+
unique_parts.append("None")
|
|
200
|
+
|
|
201
|
+
type_str = " or ".join(unique_parts)
|
|
202
|
+
|
|
203
|
+
return type_str
|
|
204
|
+
|
|
205
|
+
# Process each line that contains a type annotation (format: "name : type")
|
|
206
|
+
lines = func_doc.split("\n")
|
|
207
|
+
cleaned_lines = []
|
|
208
|
+
|
|
209
|
+
for line in lines:
|
|
210
|
+
# Match lines with type annotations (parameter_name : type_annotation)
|
|
211
|
+
if ":" in line and not line.strip().startswith("#"):
|
|
212
|
+
# Split only on the first colon to preserve any colons in the description
|
|
213
|
+
parts = line.split(":", 1)
|
|
214
|
+
if len(parts) == 2:
|
|
215
|
+
param_name = parts[0]
|
|
216
|
+
rest = parts[1].strip()
|
|
217
|
+
|
|
218
|
+
# Extract the type part (everything before the first real description line)
|
|
219
|
+
# Type annotations typically don't contain spaces at the start of continuation lines
|
|
220
|
+
type_match = re.match(r"^([^\n]+?)(?:\n\s{4,}|\s{2,}|$)", rest)
|
|
221
|
+
if type_match:
|
|
222
|
+
type_part = type_match.group(1).strip()
|
|
223
|
+
description_part = rest[len(type_match.group(1)) :]
|
|
224
|
+
|
|
225
|
+
# Clean the type annotation
|
|
226
|
+
cleaned_type = clean_type_annotation(type_part)
|
|
227
|
+
|
|
228
|
+
# Reconstruct the line
|
|
229
|
+
if description_part:
|
|
230
|
+
cleaned_lines.append(
|
|
231
|
+
f"{param_name}: {cleaned_type}{description_part}"
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
cleaned_lines.append(f"{param_name}: {cleaned_type}")
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
cleaned_lines.append(line)
|
|
238
|
+
|
|
239
|
+
func_doc = "\n".join(cleaned_lines)
|
|
240
|
+
|
|
241
|
+
return func_doc.strip()
|
|
171
242
|
|
|
172
243
|
@staticmethod
|
|
173
244
|
def _param_is_default(param: inspect.Parameter) -> bool:
|
|
174
245
|
"""Return True if the parameter has a default value."""
|
|
175
246
|
return param.default != inspect.Parameter.empty
|
|
176
247
|
|
|
177
|
-
def _get_action_type(
|
|
248
|
+
def _get_action_type(
|
|
249
|
+
self, param: inspect.Parameter
|
|
250
|
+
) -> Literal["store_true", "store"]:
|
|
178
251
|
"""Return the argparse action type for the given parameter."""
|
|
179
252
|
param_type = self.type_hints[param.name]
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
253
|
+
origin = get_origin(param_type)
|
|
254
|
+
args = get_args(param_type)
|
|
255
|
+
|
|
256
|
+
if param_type is bool:
|
|
257
|
+
return "store_true"
|
|
258
|
+
|
|
259
|
+
if origin is Union and bool in args:
|
|
260
|
+
return "store_true"
|
|
261
|
+
|
|
262
|
+
# Special case for Optional[bool] which is Union[bool, None]
|
|
263
|
+
if origin is Union and bool in args and type(None) in args:
|
|
184
264
|
return "store_true"
|
|
265
|
+
|
|
185
266
|
return "store"
|
|
186
267
|
|
|
187
|
-
def _get_type_and_choices(
|
|
268
|
+
def _get_type_and_choices(
|
|
188
269
|
self, param: inspect.Parameter
|
|
189
|
-
) ->
|
|
270
|
+
) -> tuple[type[Any], tuple[Any, ...]]:
|
|
190
271
|
"""Return the type and choices for the given parameter."""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return bool, ()
|
|
248
|
-
param_type = type(choices[0]) # type: ignore
|
|
249
|
-
elif len(args) > 1:
|
|
250
|
-
# Handle Union with multiple types (not just Optional)
|
|
251
|
-
# Try to extract Literal types again from the filtered args
|
|
252
|
-
literal_choices = []
|
|
253
|
-
for arg in args:
|
|
254
|
-
if get_origin(arg) is Literal:
|
|
255
|
-
literal_choices.extend(get_args(arg))
|
|
256
|
-
|
|
257
|
-
if literal_choices:
|
|
258
|
-
# Check if all choices are boolean
|
|
259
|
-
if all(isinstance(choice, bool) for choice in literal_choices):
|
|
260
|
-
return bool, ()
|
|
261
|
-
choices = tuple(set(literal_choices))
|
|
262
|
-
param_type = type(choices[0]) # type: ignore
|
|
263
|
-
|
|
264
|
-
# if there are custom choices, override
|
|
272
|
+
|
|
273
|
+
def get_base_type( # pylint: disable=R0911 # noqa:PLR0911
|
|
274
|
+
t: Any,
|
|
275
|
+
) -> type:
|
|
276
|
+
"""Recursively find the base type for argparse."""
|
|
277
|
+
origin = get_origin(t)
|
|
278
|
+
args = get_args(t)
|
|
279
|
+
|
|
280
|
+
if origin is Union or "types.UnionType" in str(type(t)):
|
|
281
|
+
non_none_args = [a for a in args if a is not type(None)]
|
|
282
|
+
if len(non_none_args) == 1:
|
|
283
|
+
return get_base_type(non_none_args[0])
|
|
284
|
+
# For Union[A, B, C], check for bool first, then default to str
|
|
285
|
+
if bool in non_none_args:
|
|
286
|
+
return bool
|
|
287
|
+
# If we have multiple types including str, prefer str as it's most flexible
|
|
288
|
+
if str in non_none_args:
|
|
289
|
+
return str
|
|
290
|
+
# Otherwise, try to get the first concrete type
|
|
291
|
+
for arg in non_none_args:
|
|
292
|
+
if arg not in (type(None), Any):
|
|
293
|
+
return get_base_type(arg)
|
|
294
|
+
return str
|
|
295
|
+
if origin is Literal:
|
|
296
|
+
return type(args[0]) if args else str
|
|
297
|
+
if origin is list:
|
|
298
|
+
return get_base_type(args[0]) if args else Any # type: ignore
|
|
299
|
+
if t is Any:
|
|
300
|
+
return str
|
|
301
|
+
# Handle actual type objects (like datetime.date)
|
|
302
|
+
if isinstance(t, type):
|
|
303
|
+
return t
|
|
304
|
+
return str
|
|
305
|
+
|
|
306
|
+
def get_choices(t: Any) -> tuple:
|
|
307
|
+
"""Recursively find the choices for argparse."""
|
|
308
|
+
origin = get_origin(t)
|
|
309
|
+
args = get_args(t)
|
|
310
|
+
|
|
311
|
+
if origin is Union or "types.UnionType" in str(type(t)):
|
|
312
|
+
non_none_args = [a for a in args if a is not type(None)]
|
|
313
|
+
all_choices: list = []
|
|
314
|
+
for arg in non_none_args:
|
|
315
|
+
all_choices.extend(get_choices(arg))
|
|
316
|
+
return tuple(set(all_choices))
|
|
317
|
+
if origin is Literal:
|
|
318
|
+
return args
|
|
319
|
+
if origin is list and args:
|
|
320
|
+
return get_choices(args[0])
|
|
321
|
+
return ()
|
|
322
|
+
|
|
323
|
+
param_type_hint = self.type_hints[param.name]
|
|
324
|
+
|
|
325
|
+
base_type = get_base_type(param_type_hint)
|
|
326
|
+
choices = get_choices(param_type_hint)
|
|
327
|
+
|
|
265
328
|
custom_choices = self._get_argument_custom_choices(param)
|
|
266
|
-
if custom_choices
|
|
329
|
+
if custom_choices:
|
|
267
330
|
choices = tuple(custom_choices)
|
|
268
331
|
|
|
269
|
-
|
|
332
|
+
if base_type is bool:
|
|
333
|
+
choices = ()
|
|
334
|
+
|
|
335
|
+
return base_type, choices
|
|
270
336
|
|
|
271
337
|
@staticmethod
|
|
272
338
|
def _split_annotation(
|
|
273
|
-
base_annotation:
|
|
274
|
-
) ->
|
|
339
|
+
base_annotation: type[Any], custom_annotation_type: type
|
|
340
|
+
) -> tuple[type[Any], list[Any]]:
|
|
275
341
|
"""Find the base annotation and the custom annotations, namely the OpenBBField."""
|
|
276
342
|
if get_origin(base_annotation) is not Annotated:
|
|
277
343
|
return base_annotation, []
|
|
@@ -283,7 +349,7 @@ class ArgparseTranslator:
|
|
|
283
349
|
]
|
|
284
350
|
|
|
285
351
|
@classmethod
|
|
286
|
-
def _get_argument_custom_help(cls, param: inspect.Parameter) ->
|
|
352
|
+
def _get_argument_custom_help(cls, param: inspect.Parameter) -> str | None:
|
|
287
353
|
"""Return the help annotation for the given parameter."""
|
|
288
354
|
base_annotation = param.annotation
|
|
289
355
|
_, custom_annotations = cls._split_annotation(base_annotation, OpenBBField)
|
|
@@ -293,7 +359,7 @@ class ArgparseTranslator:
|
|
|
293
359
|
return help_annotation
|
|
294
360
|
|
|
295
361
|
@classmethod
|
|
296
|
-
def _get_argument_custom_choices(cls, param: inspect.Parameter) ->
|
|
362
|
+
def _get_argument_custom_choices(cls, param: inspect.Parameter) -> str | None:
|
|
297
363
|
"""Return the help annotation for the given parameter."""
|
|
298
364
|
base_annotation = param.annotation
|
|
299
365
|
_, custom_annotations = cls._split_annotation(base_annotation, OpenBBField)
|
|
@@ -302,7 +368,7 @@ class ArgparseTranslator:
|
|
|
302
368
|
)
|
|
303
369
|
return choices_annotation
|
|
304
370
|
|
|
305
|
-
def _get_nargs(self, param: inspect.Parameter) ->
|
|
371
|
+
def _get_nargs(self, param: inspect.Parameter) -> Literal["+"] | None:
|
|
306
372
|
"""Return the nargs annotation for the given parameter."""
|
|
307
373
|
param_type = self.type_hints[param.name]
|
|
308
374
|
origin = get_origin(param_type)
|
|
@@ -339,7 +405,7 @@ class ArgparseTranslator:
|
|
|
339
405
|
sig = inspect.signature(param_type)
|
|
340
406
|
|
|
341
407
|
# add help to the annotation
|
|
342
|
-
annotated_parameters:
|
|
408
|
+
annotated_parameters: list[inspect.Parameter] = []
|
|
343
409
|
for child_param in sig.parameters.values():
|
|
344
410
|
new_child_param = child_param.replace(
|
|
345
411
|
name=f"{param.name}{SEP}{child_param.name}",
|
|
@@ -400,9 +466,9 @@ class ArgparseTranslator:
|
|
|
400
466
|
)
|
|
401
467
|
|
|
402
468
|
@staticmethod
|
|
403
|
-
def _unflatten_args(args: dict) ->
|
|
469
|
+
def _unflatten_args(args: dict) -> dict[str, Any]:
|
|
404
470
|
"""Unflatten the args that were flattened by the custom types."""
|
|
405
|
-
result:
|
|
471
|
+
result: dict[str, Any] = {}
|
|
406
472
|
for key, value in args.items():
|
|
407
473
|
if SEP in key:
|
|
408
474
|
parts = key.split(SEP)
|
|
@@ -416,7 +482,7 @@ class ArgparseTranslator:
|
|
|
416
482
|
result[key] = value
|
|
417
483
|
return result
|
|
418
484
|
|
|
419
|
-
def _update_with_custom_types(self, kwargs:
|
|
485
|
+
def _update_with_custom_types(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
420
486
|
"""Update the kwargs with the custom types."""
|
|
421
487
|
# for each argument in the signature that is a custom type, we need to
|
|
422
488
|
# update the kwargs with the custom type kwargs
|
|
@@ -432,7 +498,7 @@ class ArgparseTranslator:
|
|
|
432
498
|
|
|
433
499
|
def execute_func(
|
|
434
500
|
self,
|
|
435
|
-
parsed_args:
|
|
501
|
+
parsed_args: argparse.Namespace | None = None,
|
|
436
502
|
) -> Any:
|
|
437
503
|
"""
|
|
438
504
|
Execute the original function with the parsed arguments.
|
|
@@ -447,7 +513,7 @@ class ArgparseTranslator:
|
|
|
447
513
|
kwargs = self._unflatten_args(vars(parsed_args))
|
|
448
514
|
kwargs = self._update_with_custom_types(kwargs)
|
|
449
515
|
provider = kwargs.get("provider")
|
|
450
|
-
provider_args:
|
|
516
|
+
provider_args: list = []
|
|
451
517
|
if provider and provider in self.provider_parameters:
|
|
452
518
|
provider_args = self.provider_parameters[provider]
|
|
453
519
|
else:
|