diff options
49 files changed, 343 insertions, 279 deletions
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index edf6025d597..ccabe3a349d 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -68,8 +68,8 @@ jobs: - run: | # Run linters for openbb_platform if [ -n "${{ env.platform_files }}" ]; then - # TODO: Add mypy to this part of the linting workflow once we're ready pylint ${{ env.platform_files }} + mypy ${{ env.platform_files }} --ignore-missing-imports --check-untyped-defs else echo "No Python files changed in openbb_platform" fi diff --git a/openbb_platform/core/openbb_core/api/router/commands.py b/openbb_platform/core/openbb_core/api/router/commands.py index 96334f549fa..7b644cca372 100644 --- a/openbb_platform/core/openbb_core/api/router/commands.py +++ b/openbb_platform/core/openbb_core/api/router/commands.py @@ -134,19 +134,26 @@ def validate_output(c_out: OBBject) -> OBBject: json_schema_extra = field.json_schema_extra if field else None # case where 1st layer field needs to be excluded - if json_schema_extra and json_schema_extra.get("exclude_from_api", None): + if ( + json_schema_extra + and isinstance(json_schema_extra, dict) + and json_schema_extra.get("exclude_from_api", None) + ): delattr(c_out, key) # if it's a model with nested fields elif is_model(type_): for field_name, field in type_.__fields__.items(): - if field.json_schema_extra and field.json_schema_extra.get( - "exclude_from_api", None + extra = getattr(field, "json_schema_extra", None) + if ( + extra + and isinstance(extra, dict) + and extra.get("exclude_from_api", None) ): delattr(value, field_name) # if it's a yet a nested model we need to go deeper in the recursion - elif is_model(field.annotation): + elif is_model(getattr(field, "annotation", None)): exclude_fields_from_api(field_name, getattr(value, field_name)) for k, v in c_out.model_copy(): diff --git a/openbb_platform/core/openbb_core/app/command_runner.py b/openbb_platform/core/openbb_core/app/command_runner.py index c8d7f43e377..33fb4def99f 100644 --- a/openbb_platform/core/openbb_core/app/command_runner.py +++ b/openbb_platform/core/openbb_core/app/command_runner.py @@ -6,7 +6,7 @@ from datetime import datetime from inspect import Parameter, signature from sys import exc_info from time import perf_counter_ns -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple, Type from warnings import catch_warnings, showwarning, warn from fastapi.params import Query @@ -181,7 +181,7 @@ class ParametersBuilder: def _warn_kwargs( provider_choices: Dict[str, Any], extra_params: Dict[str, Any], - model: BaseModel, + model: Type[BaseModel], ) -> None: """Warn if kwargs received and ignored by the validation model.""" # We only check the extra_params annotation because ignored fields @@ -247,7 +247,7 @@ class ParametersBuilder: @classmethod def build( cls, - args: Tuple[Any], + args: Tuple[Any, ...], execution_context: ExecutionContext, func: Callable, route: str, @@ -317,7 +317,7 @@ class StaticCommandRunner: async def _execute_func( cls, route: str, - args: Tuple[Any], + args: Tuple[Any, ...], execution_context: ExecutionContext, func: Callable, kwargs: Dict[str, Any], diff --git a/openbb_platform/core/openbb_core/app/deprecation.py b/openbb_platform/core/openbb_core/app/deprecation.py index 482338bf32d..5dd38c1ae54 100644 --- a/openbb_platform/core/openbb_core/app/deprecation.py +++ b/openbb_platform/core/openbb_core/app/deprecation.py @@ -12,10 +12,10 @@ from openbb_core.app.version import VERSION, get_major_minor class DeprecationSummary(str): """A string subclass that can be used to store deprecation metadata.""" - def __new__(cls, value, metadata): + def __new__(cls, value: str, metadata: DeprecationWarning): """Create a new instance of the class.""" obj = str.__new__(cls, value) - obj.metadata = metadata + setattr(obj, "metadata", metadata) return obj diff --git a/openbb_platform/core/openbb_core/app/model/abstract/singleton.py b/openbb_platform/core/openbb_core/app/model/abstract/singleton.py index 7d6bad71a1b..3575186ee22 100644 --- a/openbb_platform/core/openbb_core/app/model/abstract/singleton.py +++ b/openbb_platform/core/openbb_core/app/model/abstract/singleton.py @@ -7,7 +7,7 @@ class SingletonMeta(type, Generic[T]): # TODO : check if we want to update this to be thread safe _instances: Dict[T, T] = {} - def __call__(cls, *args, **kwargs): + def __call__(cls: "SingletonMeta", *args, **kwargs): if cls not in cls._instances: instance = super().__call__(*args, **kwargs) cls._instances[cls] = instance diff --git a/openbb_platform/core/openbb_core/app/model/example.py b/openbb_platform/core/openbb_core/app/model/example.py index 27f0e77084a..25772107824 100644 --- a/openbb_platform/core/openbb_core/app/model/example.py +++ b/openbb_platform/core/openbb_core/app/model/example.py @@ -2,7 +2,7 @@ from abc import abstractmethod from datetime import date, datetime, timedelta -from typing import Any, Dict, List, Literal, Optional, Union, _GenericAlias +from typing import Any, Dict, List, Literal, Optional, Union, _GenericAlias # type: ignore from pydantic import ( BaseModel, @@ -58,7 +58,7 @@ class APIEx(Example): @staticmethod def _unpack_type(type_: type) -> set: - """Unpack types from types, example Union[List[str], int] -> {str, int}.""" + """Unpack types from types, example Union[List[str], int] -> {typing._GenericAlias, int}.""" if ( hasattr(type_, "__args__") and type(type_) # pylint: disable=unidiomatic-typecheck diff --git a/openbb_platform/core/openbb_core/app/model/metadata.py b/openbb_platform/core/openbb_core/app/model/metadata.py index e5ea6a0f5bf..cf421fca7e0 100644 --- a/openbb_platform/core/openbb_core/app/model/metadata.py +++ b/openbb_platform/core/openbb_core/app/model/metadata.py @@ -1,6 +1,6 @@ from datetime import datetime from inspect import isclass -from typing import Any, Dict +from typing import Any, Dict, Optional, Sequence, Union import numpy as np import pandas as pd @@ -36,7 +36,7 @@ class Metadata(BaseModel): value is kept or trimmed to 80 characters. """ for arg, arg_val in v.items(): - new_arg_val = None + new_arg_val: Optional[Union[str, dict[str, Sequence[Any]]]] = None # Data if isclass(type(arg_val)) and issubclass(type(arg_val), Data): @@ -47,30 +47,32 @@ class Metadata(BaseModel): # List[Data] if isinstance(arg_val, list) and issubclass(type(arg_val[0]), Data): - columns = [list(d.model_dump().keys()) for d in arg_val] - columns = (item for sublist in columns for item in sublist) # flatten + _columns = [list(d.model_dump().keys()) for d in arg_val] + ld_columns = ( + item for sublist in _columns for item in sublist + ) # flatten new_arg_val = { "type": f"List[{type(arg_val[0]).__name__}]", - "columns": list(set(columns)), + "columns": list(set(ld_columns)), } # DataFrame elif isinstance(arg_val, pd.DataFrame): - columns = ( + df_columns = ( list(arg_val.index.names) + arg_val.columns.tolist() if any(index is not None for index in list(arg_val.index.names)) else arg_val.columns.tolist() ) new_arg_val = { "type": f"{type(arg_val).__name__}", - "columns": columns, + "columns": df_columns, } # List[DataFrame] elif isinstance(arg_val, list) and issubclass( type(arg_val[0]), pd.DataFrame ): - columns = [ + ldf_columns = [ ( list(df.index.names) + df.columns.tolist() if any(index is not None for index in list(df.index.names)) @@ -80,7 +82,7 @@ class Metadata(BaseModel): ] new_arg_val = { "type": f"List[{type(arg_val[0]).__name__}]", - "columns": columns, + "columns": ldf_columns, } # Series @@ -92,7 +94,7 @@ class Metadata(BaseModel): # List[Series] elif isinstance(arg_val, list) and isinstance(arg_val[0], pd.Series): - columns = [ + ls_columns = [ ( list(series.index.names) + [series.name] if any(index is not None for index in list(series.index.names)) @@ -102,7 +104,7 @@ class Metadata(BaseModel): ] new_arg_val = { "type": f"List[{type(arg_val[0]).__name__}]", - "columns": columns, + "columns": ls_columns, } # ndarray diff --git a/openbb_platform/core/openbb_core/app/model/obbject.py b/openbb_platform/core/openbb_core/app/model/obbject.py index 9f2a2da8746..89232702405 100644 --- a/openbb_platform/core/openbb_core/app/model/obbject.py +++ b/openbb_platform/core/openbb_core/app/model/obbject.py @@ -8,6 +8,7 @@ from typing import ( ClassVar, Dict, Generic, + Hashable, List, Literal, Optional, @@ -82,30 +83,32 @@ class OBBject(Tagged, Generic[T]): @classmethod def results_type_repr(cls, params: Optional[Any] = None) -> str: - """Return the results type name.""" + """Return the results type representation.""" results_field = cls.model_fields.get("results") - type_ = params[0] if params else results_field.annotation - name = type_.__name__ if hasattr(type_, "__name__") else str(type_) + type_repr = "Any" + if results_field: + type_ = params[0] if params else results_field.annotation + type_repr = getattr(type_, "__name__", str(type_)) - if (json_schema_extra := results_field.json_schema_extra) is not None: - model = json_schema_extra.get("model") + if json_schema_extra := getattr(results_field, "json_schema_extra", {}): + model = json_schema_extra.get("model", "Any") - if json_schema_extra.get("is_union"): - return f"Union[List[{model}], {model}]" - if json_schema_extra.get("has_list"): - return f"List[{model}]" + if json_schema_extra.get("is_union"): + return f"Union[List[{model}], {model}]" + if json_schema_extra.get("has_list"): + return f"List[{model}]" - return model + return model - if "typing." in str(type_): - unpack_optional = sub(r"Optional\[(.*)\]", r"\1", str(type_)) - name = sub( - r"(\w+\.)*(\w+)?(\, NoneType)?", - r"\2", - unpack_optional, - ) + if "typing." in str(type_): + unpack_optional = sub(r"Optional\[(.*)\]", r"\1", str(type_)) + type_repr = sub( + r"(\w+\.)*(\w+)?(\, NoneType)?", + r"\2", + unpack_optional, + ) - return name + return type_repr @classmethod def model_parametrized_name(cls, params: Any) -> str: @@ -189,9 +192,9 @@ class OBBject(Tagged, Generic[T]): # List[List | str | int | float] | Dict[str, Dict | List | BaseModel] else: try: - df = pd.DataFrame(res) + df = pd.DataFrame(res) # type: ignore[call-overload] # Set index, if any - if index is not None and index in df.columns: + if df is not None and index is not None and index in df.columns: df.set_index(index, inplace=True) except ValueError: @@ -245,7 +248,7 @@ class OBBject(Tagged, Generic[T]): orient: Literal[ "dict", "list", "series", "split", "tight", "records", "index" ] = "list", - ) -> Dict[str, List]: + ) -> Union[Dict[Hashable, Any], List[Dict[Hashable, Any]]]: """Convert results field to a dictionary using any of pandas to_dict options. Parameters @@ -256,25 +259,21 @@ class OBBject(Tagged, Generic[T]): Returns ------- - Dict[str, List] - Dictionary of lists. + Union[Dict[Hashable, Any], List[Dict[Hashable, Any]]] + Dictionary of lists or list of dictionaries if orient is "records". """ - df = self.to_dataframe(index=None) # type: ignore - transpose = False - if orient == "list": - transpose = True - if not isinstance(self.results, dict): - transpose = False - else: # Only enter the loop if self.results is a dictionary - self.results: Dict[str, Any] = self.results # type: ign |