File size: 4,830 Bytes
b93364a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
"""Module for the ReferenceToArgumentsProcessor class."""
# `ForwardRef`needs to be imported because the usage of `eval()`,
# which creates a ForwardRef
# which would raise a not defined error if it's not imported here.
# pylint: disable=unused-import
from typing import (
Any,
Dict,
ForwardRef, # noqa: F401
List,
Literal,
Optional,
Tuple,
Union,
get_args,
get_origin,
)
from openbb_cli.argparse_translator.argparse_argument import (
ArgparseArgumentGroupModel,
ArgparseArgumentModel,
)
class ReferenceToArgumentsProcessor:
"""Class to process the reference and build custom argument groups."""
def __init__(self, reference: Dict[str, Dict]):
"""Initialize the ReferenceToArgumentsProcessor."""
self._reference = reference
self._custom_groups: Dict[str, List[ArgparseArgumentGroupModel]] = {}
self._build_custom_groups()
@property
def custom_groups(self) -> Dict[str, List[ArgparseArgumentGroupModel]]:
"""Get the custom groups."""
return self._custom_groups
@staticmethod
def _make_type_parsable(type_: str) -> type:
"""Make the type parsable by removing the annotations."""
if "Union" in type_ and "str" in type_:
return str
if "Union" in type_ and "int" in type_:
return int
if type_ in ["date", "datetime.time", "time"]:
return str
if any(x in type_ for x in ["gt=", "ge=", "lt=", "le="]):
if "Annotated" in type_:
type_ = type_.replace("Annotated[", "").replace("]", "")
type_ = type_.split(",")[0]
return eval(type_) # noqa: S307, E501 pylint: disable=eval-used
def _parse_type(self, type_: str) -> type:
"""Parse the type from the string representation."""
type_ = self._make_type_parsable(type_) # type: ignore
if get_origin(type_) is Literal:
type_ = type(get_args(type_)[0]) # type: ignore
return type_ # type: ignore
def _get_nargs(self, type_: type) -> Optional[Union[int, str]]:
"""Get the nargs for the given type."""
if get_origin(type_) is list:
return "+"
return None
def _get_choices(self, type_: str, custom_choices: Any) -> Tuple:
"""Get the choices for the given type."""
type_ = self._make_type_parsable(type_) # type: ignore
type_origin = get_origin(type_)
choices: tuple[Any, ...] = ()
if type_origin is Literal:
choices = get_args(type_)
if type_origin is list:
type_ = get_args(type_)[0]
if get_origin(type_) is Literal:
choices = get_args(type_)
if type_origin is Union and type(None) in get_args(type_):
# remove NoneType from the args
args = [arg for arg in get_args(type_) if arg != type(None)]
# if there is only one arg left, use it
if len(args) > 1:
raise ValueError("Union with NoneType should have only one type left")
type_ = args[0]
if get_origin(type_) is Literal:
choices = get_args(type_)
if custom_choices:
return tuple(custom_choices)
return choices
def _build_custom_groups(self):
"""Build the custom groups from the reference."""
for route, v in self._reference.items():
for provider, args in v["parameters"].items():
if provider == "standard":
continue
custom_arguments = []
for arg in args:
if arg.get("standard"):
continue
type_ = self._parse_type(arg["type"])
custom_arguments.append(
ArgparseArgumentModel(
name=arg["name"],
type=type_,
dest=arg["name"],
default=arg["default"],
required=not (arg["optional"]),
action="store" if type_ != bool else "store_true",
help=arg["description"],
nargs=self._get_nargs(type_), # type: ignore
choices=self._get_choices(
arg["type"], custom_choices=arg["choices"]
),
)
)
group = ArgparseArgumentGroupModel(
name=provider, arguments=custom_arguments
)
if route not in self._custom_groups:
self._custom_groups[route] = []
self._custom_groups[route].append(group)
|