mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
[BugFix] - Bring back mypy (#6242)
* first batch
* another
* another
* another batch unyped defs
* final untyped defs
* update yml
* pylint
* remove unecessary __init__.py files
* Revert "remove unecessary __init__.py files"
This reverts commit 5977eccfee.
* fix company_news typing
* rename func
* fix tests
* tests
This commit is contained in:
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: ignore
|
||||
for _, value in self.results.items():
|
||||
if not isinstance(value, dict):
|
||||
transpose = False
|
||||
break
|
||||
if transpose:
|
||||
df = self.to_dataframe(index=None)
|
||||
if (
|
||||
orient == "list"
|
||||
and isinstance(self.results, dict)
|
||||
and all(
|
||||
isinstance(value, dict)
|
||||
for value in self.results.values() # pylint: disable=no-member
|
||||
)
|
||||
):
|
||||
df = df.T
|
||||
results = df.to_dict(orient=orient)
|
||||
if orient == "list" and "index" in results:
|
||||
if isinstance(results, dict) and orient == "list" and "index" in results:
|
||||
del results["index"]
|
||||
return results
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from openbb_core.provider.query_executor import QueryExecutor
|
||||
from openbb_core.provider.registry_map import MapType, RegistryMap
|
||||
from openbb_core.provider.utils.helpers import to_snake_case
|
||||
|
||||
TupleFieldType = Tuple[str, type, Any]
|
||||
TupleFieldType = Tuple[str, Optional[Type], Optional[Any]]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -26,8 +26,8 @@ class DataclassField:
|
||||
"""Dataclass field."""
|
||||
|
||||
name: str
|
||||
type_: type
|
||||
default: Any
|
||||
annotation: Optional[Type]
|
||||
default: Optional[Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -154,20 +154,19 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
|
||||
def create_executor(self) -> QueryExecutor:
|
||||
"""Get query executor."""
|
||||
return self._query_executor(self._registry_map.registry) # type: ignore
|
||||
return self._query_executor(self._registry_map.registry) # type: ignore[operator]
|
||||
|
||||
@staticmethod
|
||||
def _merge_fields(
|
||||
current: DataclassField, incoming: DataclassField, query: bool = False
|
||||
) -> DataclassField:
|
||||
current_name = current.name
|
||||
current_type = current.type_
|
||||
current_desc = getattr(current.default, "description", "")
|
||||
"""Merge 2 dataclass fields."""
|
||||
curr_name = current.name
|
||||
curr_type: Optional[Type] = current.annotation
|
||||
curr_desc = getattr(current.default, "description", "")
|
||||
|
||||
incoming_type = incoming.type_
|
||||
incoming_desc = getattr(incoming.default, "description", "")
|
||||
|
||||
F: Union[Callable, object] = Query if query else FieldInfo
|
||||
inc_type: Optional[Type] = incoming.annotation
|
||||
inc_desc = getattr(incoming.default, "description", "")
|
||||
|
||||
def split_desc(desc: str) -> str:
|
||||
"""Split field description."""
|
||||
@@ -175,34 +174,33 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
detail = item[0] if item else ""
|
||||
return detail
|
||||
|
||||
curr_detail = split_desc(current_desc)
|
||||
inc_detail = split_desc(incoming_desc)
|
||||
curr_detail = split_desc(curr_desc)
|
||||
inc_detail = split_desc(inc_desc)
|
||||
|
||||
providers = f"{current.default.title},{incoming.default.title}"
|
||||
curr_title = getattr(current.default, "title", "")
|
||||
inc_title = getattr(incoming.default, "title", "")
|
||||
providers = ",".join([curr_title, inc_title])
|
||||
formatted_prov = providers.replace(",", ", ")
|
||||
|
||||
if SequenceMatcher(None, curr_detail, inc_detail).ratio() > 0.8:
|
||||
new_desc = f"{curr_detail} (provider: {formatted_prov})"
|
||||
else:
|
||||
new_desc = f"{current_desc};\n {incoming_desc}"
|
||||
new_desc = f"{curr_desc};\n {inc_desc}"
|
||||
|
||||
merged_default = F( # type: ignore
|
||||
default=current.default.default,
|
||||
QF: Callable = Query if query else FieldInfo # type: ignore[assignment]
|
||||
merged_default = QF(
|
||||
default=getattr(current.default, "default", None),
|
||||
title=providers,
|
||||
description=new_desc,
|
||||
)
|
||||
|
||||
merged_type = (
|
||||
Union[current_type, incoming_type]
|
||||
if current_type != incoming_type
|
||||
else current_type
|
||||
merged_type: Optional[Type] = (
|
||||
Union[curr_type, inc_type] # type: ignore[assignment]
|
||||
if curr_type != inc_type
|
||||
else curr_type
|
||||
)
|
||||
|
||||
return DataclassField(
|
||||
name=current_name,
|
||||
type_=merged_type, # type: ignore
|
||||
default=merged_default,
|
||||
)
|
||||
return DataclassField(curr_name, merged_type, merged_default)
|
||||
|
||||
@staticmethod
|
||||
def _create_field(
|
||||
@@ -213,9 +211,7 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
force_optional: bool = False,
|
||||
) -> DataclassField:
|
||||
new_name = name.replace(".", "_")
|
||||
# field.type_ don't work for nested types
|
||||
# field.outer_type_ don't work for Optional nested types
|
||||
type_ = field.annotation
|
||||
annotation = field.annotation
|
||||
|
||||
additional_description = ""
|
||||
if (extra := field.json_schema_extra) and (
|
||||
@@ -239,7 +235,7 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
|
||||
if field.is_required():
|
||||
if force_optional:
|
||||
type_ = Optional[type_] # type: ignore
|
||||
annotation = Optional[annotation] # type: ignore
|
||||
default = None
|
||||
else:
|
||||
default = ...
|
||||
@@ -247,24 +243,32 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
default = field.default
|
||||
|
||||
if query:
|
||||
# We need to use query if we want the field description to show up in the
|
||||
# swagger, it's a fastapi limitation
|
||||
default = Query(
|
||||
default=default,
|
||||
title=provider_name,
|
||||
description=description,
|
||||
alias=field.alias or None,
|
||||
json_schema_extra=field.json_schema_extra,
|
||||
# We need to use query if we want the field description to show
|
||||
# up in the swagger, it's a fastapi limitation
|
||||
return DataclassField(
|
||||
new_name,
|
||||
annotation,
|
||||
Query(
|
||||
default=default,
|
||||
title=provider_name,
|
||||
description=description,
|
||||
alias=field.alias or None,
|
||||
json_schema_extra=getattr(field, "json_schema_extra", None),
|
||||
),
|
||||
)
|
||||
elif provider_name:
|
||||
default: FieldInfo = Field(
|
||||
default=default or None,
|
||||
title=provider_name,
|
||||
description=description,
|
||||
json_schema_extra=field.json_schema_extra,
|
||||
if provider_name:
|
||||
return DataclassField(
|
||||
new_name,
|
||||
annotation,
|
||||
Field(
|
||||
default=default or None,
|
||||
title=provider_name,
|
||||
description=description,
|
||||
json_schema_extra=field.json_schema_extra,
|
||||
),
|
||||
)
|
||||
|
||||
return DataclassField(new_name, type_, default)
|
||||
return DataclassField(new_name, annotation, default)
|
||||
|
||||
@classmethod
|
||||
def _extract_params(
|
||||
@@ -282,7 +286,7 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
|
||||
standard[incoming.name] = (
|
||||
incoming.name,
|
||||
incoming.type_,
|
||||
incoming.annotation,
|
||||
incoming.default,
|
||||
)
|
||||
else:
|
||||
@@ -305,7 +309,7 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
|
||||
extra[updated.name] = (
|
||||
updated.name,
|
||||
updated.type_,
|
||||
updated.annotation,
|
||||
updated.default,
|
||||
)
|
||||
|
||||
@@ -331,7 +335,7 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
|
||||
standard[incoming.name] = (
|
||||
incoming.name,
|
||||
incoming.type_,
|
||||
incoming.annotation,
|
||||
incoming.default,
|
||||
)
|
||||
else:
|
||||
@@ -357,7 +361,7 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
|
||||
extra[updated.name] = (
|
||||
updated.name,
|
||||
updated.type_,
|
||||
updated.annotation,
|
||||
updated.default,
|
||||
)
|
||||
|
||||
@@ -393,14 +397,14 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
standard, extra = self._extract_params(providers)
|
||||
|
||||
result[model_name] = {
|
||||
"standard": make_dataclass( # type: ignore
|
||||
"standard": make_dataclass(
|
||||
cls_name=model_name,
|
||||
fields=list(standard.values()),
|
||||
fields=list(standard.values()), # type: ignore[arg-type]
|
||||
bases=(StandardParams,),
|
||||
),
|
||||
"extra": make_dataclass( # type: ignore
|
||||
"extra": make_dataclass(
|
||||
cls_name=model_name,
|
||||
fields=list(extra.values()),
|
||||
fields=list(extra.values()), # type: ignore[arg-type]
|
||||
bases=(ExtraParams,),
|
||||
),
|
||||
}
|
||||
@@ -464,14 +468,14 @@ class ProviderInterface(metaclass=SingletonMeta):
|
||||
extra: dict
|
||||
standard, extra = self._extract_data(providers)
|
||||
result[model_name] = {
|
||||
"standard": make_dataclass( # type: ignore
|
||||
"standard": make_dataclass(
|
||||
cls_name=model_name,
|
||||
fields=list(standard.values()),
|
||||
fields=list(standard.values()), # type: ignore[arg-type]
|
||||
bases=(StandardData,),
|
||||
),
|
||||
"extra": make_dataclass( # type: ignore
|
||||
"extra": make_dataclass(
|
||||
cls_name=model_name,
|
||||
fields=list(extra.values()),
|
||||
fields=list(extra.values()), # type: ignore[arg-type]
|
||||
bases=(ExtraData,),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ class SignatureInspector:
|
||||
|
||||
func = cls.inject_return_type(
|
||||
func=func,
|
||||
return_map=provider_interface.return_map.get(model),
|
||||
return_map=provider_interface.return_map.get(model, {}),
|
||||
model=model,
|
||||
)
|
||||
|
||||
@@ -374,11 +374,7 @@ class SignatureInspector:
|
||||
return_map: Dict[str, dict],
|
||||
model: str,
|
||||
) -> Callable[P, OBBject]:
|
||||
"""
|
||||
Inject full return model into the function.
|
||||
Also updates __name__ and __doc__ for API schemas.
|
||||
"""
|
||||
|
||||
"""Inject full return model into the function. Also updates __name__ and __doc__ for API schemas."""
|
||||
results: Dict[str, Any] = {"list_type": [], "dict_type": []}
|
||||
|
||||
for provider, return_data in return_map.items():
|
||||
@@ -397,7 +393,7 @@ class SignatureInspector:
|
||||
if not v:
|
||||
continue
|
||||
|
||||
inner_type = SerializeAsAny[
|
||||
inner_type: Any = SerializeAsAny[ # type: ignore[misc,valid-type]
|
||||
Annotated[
|
||||
Union[tuple(v)], # type: ignore
|
||||
Field(discriminator="provider"),
|
||||
|
||||
@@ -42,10 +42,11 @@ class Account:
|
||||
def _log_account_command(func): # pylint: disable=E0213
|
||||
"""Log account command."""
|
||||
|
||||
@wraps(func)
|
||||
@wraps(func) # type: ignore[arg-type]
|
||||
def wrapped(self, *args, **kwargs):
|
||||
try:
|
||||
result = func(self, *args, **kwargs) # pylint: disable=E1102
|
||||
# pylint: disable=E1102
|
||||
result = func(self, *args, **kwargs) # type: ignore[operator]
|
||||
except Exception as e:
|
||||
raise OpenBBError(e) from e
|
||||
finally:
|
||||
@@ -57,8 +58,9 @@ class Account:
|
||||
ls.log(
|
||||
user_settings=user_settings,
|
||||
system_settings=system_settings,
|
||||
route=f"/account/{func.__name__}", # pylint: disable=E1101
|
||||
func=func,
|
||||
# pylint: disable=E1101
|
||||
route=f"/account/{func.__name__}", # type: ignore[attr-defined]
|
||||
func=func, # type: ignore[arg-type]
|
||||
kwargs={}, # don't want any credentials being logged by accident
|
||||
exec_info=exc_info(),
|
||||
)
|
||||
|
||||
@@ -54,8 +54,9 @@ def exception_handler(func: Callable[P, R]) -> Callable[P, R]:
|
||||
|
||||
# Get the last traceback object from the exception
|
||||
tb = e.__traceback__
|
||||
while tb.tb_next is not None:
|
||||
tb = tb.tb_next
|
||||
if tb:
|
||||
while tb.tb_next is not None:
|
||||
tb = tb.tb_next
|
||||
|
||||
if isinstance(e, ValidationError):
|
||||
error_list = []
|
||||
@@ -70,7 +71,10 @@ def exception_handler(func: Callable[P, R]) -> Callable[P, R]:
|
||||
f"input_type={type(error['input']).__name__}, "
|
||||
f"input_value={error['input']}]\n"
|
||||
)
|
||||
error_info = f" For further information visit {error['url']}\n"
|
||||
url = error.get("url")
|
||||
error_info = (
|
||||
f" For further information visit {url}\n" if url else ""
|
||||
)
|
||||
error_list.append(arg_error + error_details + error_info)
|
||||
|
||||
error_list.insert(0, validation_error)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import ast
|
||||
import json
|
||||
from datetime import time
|
||||
from typing import Dict, Iterable, List, Optional, Union
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -17,7 +17,7 @@ from openbb_core.provider.abstract.data import Data
|
||||
|
||||
def basemodel_to_df(
|
||||
data: Union[List[Data], Data],
|
||||
index: Optional[Union[None, str, Iterable]] = None,
|
||||
index: Optional[str] = None,
|
||||
) -> pd.DataFrame:
|
||||
"""Convert list of BaseModel to a Pandas DataFrame."""
|
||||
if isinstance(data, list):
|
||||
@@ -44,9 +44,7 @@ def basemodel_to_df(
|
||||
df.set_index("date", inplace=True)
|
||||
df.sort_index(axis=0, inplace=True)
|
||||
else:
|
||||
df = (
|
||||
df.set_index(index) if index is not None and index in df.columns else df
|
||||
)
|
||||
df = df.set_index(index) if index and index in df.columns else df
|
||||
|
||||
return df
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class Fetcher(Generic[Q, R]):
|
||||
super().__init_subclass__(*args, **kwargs)
|
||||
|
||||
if cls.aextract_data != Fetcher.aextract_data:
|
||||
cls.extract_data = cls.aextract_data
|
||||
cls.extract_data = cls.aextract_data # type: ignore[method-assign]
|
||||
elif cls.extract_data == Fetcher.extract_data:
|
||||
raise NotImplementedError(
|
||||
"Fetcher subclass must implement either extract_data or aextract_data"
|
||||
|
||||
@@ -146,7 +146,7 @@ class RegistryMap:
|
||||
def extract_data_model(fetcher: Fetcher, provider_str: str) -> BaseModel:
|
||||
"""Extract info (fields and docstring) from fetcher query params or data."""
|
||||
model: BaseModel = RegistryMap._get_model(fetcher, "data")
|
||||
|
||||
model_name = getattr(model, "__name__", "")
|
||||
fields = {}
|
||||
for field_name, field in model.model_fields.items():
|
||||
field.serialization_alias = field_name
|
||||
@@ -161,8 +161,8 @@ class RegistryMap:
|
||||
),
|
||||
)
|
||||
|
||||
provider_model = create_model(
|
||||
model.__name__.replace("Data", ""),
|
||||
provider_model = create_model( # type: ignore[call-overload]
|
||||
model_name.replace("Data", ""),
|
||||
__base__=model,
|
||||
__doc__=model.__doc__,
|
||||
__module__=model.__module__,
|
||||
@@ -171,7 +171,11 @@ class RegistryMap:
|
||||
|
||||
# Replace the provider models in the modules with the new models we created
|
||||
# To make sure provider field is defined to be the provider string
|
||||
setattr(sys.modules[model.__module__], model.__name__, provider_model)
|
||||
# This is hacky, but we need to have `provider: Literal['provider_name']`
|
||||
# in the model to serve as union discriminator for the API validation
|
||||
# the alternative would be to specify it manually in all the models
|
||||
if model_name:
|
||||
setattr(sys.modules[model.__module__], model_name, provider_model)
|
||||
|
||||
return provider_model
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class ConsumerPriceIndexQueryParams(QueryParams):
|
||||
|
||||
country: str = Field(
|
||||
description=QUERY_DESCRIPTIONS.get("country"),
|
||||
choices=CPI_COUNTRIES, # type: ignore
|
||||
json_schema_extra={"choices": CPI_COUNTRIES}, # type: ignore[dict-item]
|
||||
)
|
||||
units: CPI_UNITS = Field(
|
||||
default="growth_same",
|
||||
|
||||
@@ -32,7 +32,7 @@ class SpotRateQueryParams(QueryParams):
|
||||
category: str = Field(
|
||||
default="spot_rate",
|
||||
description="Rate category. Options: spot_rate, par_yield.",
|
||||
choices=["par_yield", "spot_rate"],
|
||||
json_schema_extra={"choices": ["par_yield", "spot_rate"]},
|
||||
)
|
||||
|
||||
@field_validator("category", mode="before", check_fields=False)
|
||||
|
||||
@@ -2,11 +2,20 @@
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from datetime import datetime
|
||||
from datetime import date, datetime
|
||||
from difflib import SequenceMatcher
|
||||
from functools import partial
|
||||
from inspect import iscoroutinefunction
|
||||
from typing import Awaitable, Callable, List, Literal, Optional, TypeVar, Union, cast
|
||||
from typing import (
|
||||
Awaitable,
|
||||
Callable,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
import requests
|
||||
from anyio import start_blocking_portal
|
||||
@@ -21,6 +30,7 @@ from openbb_core.provider.utils.client import (
|
||||
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
D = TypeVar("D", bound="Data")
|
||||
|
||||
|
||||
def check_item(item: str, allowed: List[str], threshold: float = 0.75) -> None:
|
||||
@@ -177,13 +187,13 @@ async def amake_requests(
|
||||
is_exception = isinstance(result, Exception)
|
||||
|
||||
if is_exception and kwargs.get("raise_for_status", False):
|
||||
raise result
|
||||
raise result # type: ignore[misc]
|
||||
|
||||
if is_exception or not result:
|
||||
continue
|
||||
|
||||
results.extend( # type: ignore
|
||||
result if isinstance(result, list) else [result]
|
||||
results.extend(
|
||||
result if isinstance(result, list) else [result] # type: ignore[list-item]
|
||||
)
|
||||
|
||||
return results
|
||||
@@ -283,17 +293,23 @@ def run_async(
|
||||
|
||||
|
||||
def filter_by_dates(
|
||||
data: List[Data],
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
) -> List[Data]:
|
||||
data: List[D], start_date: Optional[date] = None, end_date: Optional[date] = None
|
||||
) -> List[D]:
|
||||
"""Filter data by dates."""
|
||||
if not any([start_date, end_date]):
|
||||
if start_date is None and end_date is None:
|
||||
return data
|
||||
|
||||
return list(
|
||||
filter(
|
||||
lambda d: start_date <= d.date.date() <= end_date,
|
||||
data,
|
||||
)
|
||||
)
|
||||
def _filter(d: Data) -> bool:
|
||||
_date = getattr(d, "date", None)
|
||||
dt = _date.date() if _date and isinstance(_date, datetime) else _date
|
||||
if dt:
|
||||
if start_date and end_date:
|
||||
return start_date <= dt <= end_date
|
||||
if start_date:
|
||||
return dt >= start_date
|
||||
if end_date:
|
||||
return dt <= end_date
|
||||
return True
|
||||
return False
|
||||
|
||||
return list(filter(_filter, data))
|
||||
|
||||
@@ -59,6 +59,6 @@ def test_get_user_settings_(mock_user_service):
|
||||
mock_user_settings = MagicMock(spec=UserSettings, profile=MagicMock(active=True))
|
||||
mock_user_service.default_user_settings = mock_user_settings
|
||||
mock_user_service.return_value = mock_user_service
|
||||
result = asyncio.run(get_user_settings(MagicMock(), mock_user_service))
|
||||
result = asyncio.run(get_user_settings(MagicMock(), mock_user_service)) # type: ignore[arg-type]
|
||||
|
||||
assert result == mock_user_settings
|
||||
|
||||
@@ -9,6 +9,6 @@ from openbb_core.api.dependency.coverage import get_command_map
|
||||
def test_get_system_settings():
|
||||
"""Test get_system_settings."""
|
||||
|
||||
response = asyncio.run(get_command_map(MagicMock()))
|
||||
response = asyncio.run(get_command_map(MagicMock())) # type: ignore[arg-type]
|
||||
|
||||
assert response
|
||||
|
||||
@@ -14,6 +14,6 @@ def test_get_system_settings(mock_system_service):
|
||||
"""Test get_system_settings."""
|
||||
mock_system_service.return_value.system_settings = SystemSettings()
|
||||
|
||||
response = asyncio.run(get_system_settings(MagicMock(), mock_system_service))
|
||||
response = asyncio.run(get_system_settings(MagicMock(), mock_system_service)) # type: ignore[arg-type]
|
||||
|
||||
assert response
|
||||
|
||||
@@ -272,7 +272,7 @@ def test_filter_log_line(input_text, expected_output, formatter):
|
||||
|
||||
def test_formatException_invalid():
|
||||
with pytest.raises(Exception):
|
||||
formatter.formatException(Exception("Big bad error"))
|
||||
formatter.formatException(Exception("Big bad error")) # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_format(formatter):
|
||||
|
||||
@@ -85,4 +85,4 @@ def test_update_handlers():
|
||||
for hdlr in handlers:
|
||||
if isinstance(hdlr, (MockPosthogHandler, MockPathTrackingFileHandler)):
|
||||
assert hdlr.settings == changed_settings
|
||||
assert hdlr.formatter.settings == changed_settings
|
||||
assert hdlr.formatter.settings == changed_settings # type: ignore[union-attr]
|
||||
|
||||
@@ -8,6 +8,7 @@ from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from pydantic import BaseModel
|
||||
|
||||
# ruff: noqa: S106
|
||||
# pylint: disable=redefined-outer-name, protected-access
|
||||
|
||||
|
||||
class MockLoggingSettings:
|
||||
@@ -24,9 +25,7 @@ class MockOBBject(BaseModel):
|
||||
@pytest.fixture(scope="function")
|
||||
def logging_service():
|
||||
mock_system_settings = Mock()
|
||||
mock_system_settings = "mock_system_settings"
|
||||
mock_user_settings = Mock()
|
||||
mock_user_settings = "mock_user_settings"
|
||||
mock_setup_handlers = Mock()
|
||||
mock_log_startup = Mock()
|
||||
|
||||
@@ -40,19 +39,37 @@ def logging_service():
|
||||
"openbb_core.app.logs.logging_service.LoggingService._log_startup",
|
||||
mock_log_startup,
|
||||
):
|
||||
logging_service = LoggingService(
|
||||
_logging_service = LoggingService(
|
||||
system_settings=mock_system_settings,
|
||||
user_settings=mock_user_settings,
|
||||
)
|
||||
|
||||
assert mock_setup_handlers.assert_called_once
|
||||
assert mock_log_startup.assert_called_once
|
||||
|
||||
return logging_service
|
||||
return _logging_service
|
||||
|
||||
|
||||
def test_correctly_initialized(logging_service):
|
||||
assert logging_service
|
||||
def test_correctly_initialized():
|
||||
mock_system_settings = Mock()
|
||||
mock_user_settings = Mock()
|
||||
mock_setup_handlers = Mock()
|
||||
mock_log_startup = Mock()
|
||||
|
||||
with patch(
|
||||
"openbb_core.app.logs.logging_service.LoggingSettings",
|
||||
MockLoggingSettings,
|
||||
), patch(
|
||||
"openbb_core.app.logs.logging_service.LoggingService._setup_handlers",
|
||||
mock_setup_handlers,
|
||||
), patch(
|
||||
"openbb_core.app.logs.logging_service.LoggingService._log_startup",
|
||||
mock_log_startup,
|
||||
):
|
||||
LoggingService(
|
||||
system_settings=mock_system_settings,
|
||||
user_settings=mock_user_settings,
|
||||
)
|
||||
|
||||
mock_setup_handlers.assert_called_once()
|
||||
mock_log_startup.assert_called_once()
|
||||
|
||||
|
||||
def test_logging_settings_setter(logging_service):
|
||||
@@ -68,8 +85,8 @@ def test_logging_settings_setter(logging_service):
|
||||
custom_user_settings,
|
||||
)
|
||||
|
||||
assert logging_service.logging_settings.system_settings == "custom_system_settings"
|
||||
assert logging_service.logging_settings.user_settings == "custom_user_settings"
|
||||
assert logging_service.logging_settings.system_settings == "custom_system_settings" # type: ignore[attr-defined]
|
||||
assert logging_service.logging_settings.user_settings == "custom_user_settings" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_log_startup(logging_service):
|
||||
@@ -93,7 +110,10 @@ def test_log_startup(logging_service):
|
||||
expected_log_data = {
|
||||
"route": "test_route",
|
||||
"PREFERENCES": "your_preferences",
|
||||
"KEYS": {"username": "defined", "password": "defined"},
|
||||
"KEYS": {
|
||||
"username": "defined",
|
||||
"password": "defined", # pragma: allowlist secret
|
||||
},
|
||||
"SYSTEM": "your_system_settings",
|
||||
"custom_headers": {"X-OpenBB-Test": "test"},
|
||||
}
|
||||
@@ -101,7 +121,7 @@ def test_log_startup(logging_service):
|
||||
"STARTUP: %s ",
|
||||
json.dumps(expected_log_data),
|
||||
)
|
||||
mock_get_logger.assert_called_once
|
||||
mock_get_logger.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -163,7 +183,7 @@ def test_log(
|
||||
exec_info=exec_info,
|
||||
custom_headers=custom_headers,
|
||||
)
|
||||
assert mock_log_startup.assert_called_once
|
||||
mock_log_startup.assert_called_once()
|
||||
|
||||
else:
|
||||
mock_info = mock_get_logger.return_value.info
|
||||
|
||||
@@ -2,6 +2,7 @@ import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
from typing import List
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -92,7 +93,7 @@ def mock_path():
|
||||
def test_remove_file_list_no_files(mock_path):
|
||||
# Arrange
|
||||
# Let's assume the file list is empty, meaning there are no files to remove
|
||||
file_list = []
|
||||
file_list: List[Path] = []
|
||||
|
||||
# Act
|
||||
remove_file_list(file_list)
|
||||
|
||||
@@ -8,7 +8,7 @@ def test_tagged_model():
|
||||
|
||||
|
||||
def test_fields():
|
||||
fields = Tagged.__fields__
|
||||
fields = Tagged.model_fields
|
||||
fields_keys = fields.keys()
|
||||
|
||||
assert "id" in fields_keys
|
||||
|
||||
@@ -19,7 +19,7 @@ def test_warn_model(category, message):
|
||||
|
||||
|
||||
def test_fields():
|
||||
fields = Warning_.__fields__
|
||||
fields = Warning_.model_fields
|
||||
fields_keys = fields.keys()
|
||||
|
||||
assert "category" in fields_keys
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_charting_config_validation():
|
||||
chart = Chart(content=content, format=chart_format)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
chart.content = "Invalid Content"
|
||||
chart.content = "Invalid Content" # type: ignore[assignment]
|
||||
|
||||
assert chart.content == content
|
||||
assert chart.format == chart_format
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from openbb_core.app.model.hub.hub_session import HubSession
|
||||
from pydantic import SecretStr
|
||||
|
||||
# ruff: noqa: S105 S106
|
||||
|
||||
|
||||
def test_hub_session():
|
||||
session = HubSession(
|
||||
access_token="mock_access_token",
|
||||
access_token=SecretStr("mock_access_token"),
|
||||
token_type="mock_token_type",
|
||||
email="mock_email",
|
||||
user_uuid="mock_user_uuid",
|
||||
@@ -21,7 +22,7 @@ def test_hub_session():
|
||||
|
||||
|
||||
def test_fields():
|
||||
fields = HubSession.__fields__
|
||||
fields = HubSession.model_fields
|
||||
fields_keys = fields.keys()
|
||||
|
||||
assert "access_token" in fields_keys
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_command_context():
|
||||
|
||||
|
||||
def test_fields():
|
||||
fields = CommandContext.__fields__
|
||||
fields = CommandContext.model_fields
|
||||
fields_keys = fields.keys()
|
||||
|
||||
assert "user_settings" in fields_keys
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from openbb_core.app.model.defaults import Defaults
|
||||
|
||||
|
||||
def test_defaultst():
|
||||
def test_defaults():
|
||||
cc = Defaults(routes={"test": {"test": "test"}})
|
||||
assert isinstance(cc, Defaults)
|
||||
assert cc.routes == {"test": {"test": "test"}}
|
||||
|
||||
|
||||
def test_fields():
|
||||
fields = Defaults.__fields__
|
||||
fields = Defaults.model_fields
|
||||
fields_keys = fields.keys()
|
||||
|
||||
assert "routes" in fields_keys
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_Metadata():
|
||||
|
||||
def test_fields():
|
||||
"Smoke test"
|
||||
fields = Metadata.__fields__.keys()
|
||||
fields = Metadata.model_fields.keys()
|
||||
assert "arguments" in fields
|
||||
assert "duration" in fields
|
||||
assert "route" in fields
|
||||
|
||||
@@ -12,7 +12,7 @@ from pandas.testing import assert_frame_equal
|
||||
|
||||
def test_OBBject():
|
||||
"""Smoke test."""
|
||||
co = OBBject()
|
||||
co: OBBject = OBBject()
|
||||
assert isinstance(co, OBBject)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ def test_fields():
|
||||
|
||||
def test_to_dataframe_no_results():
|
||||
"""Test helper."""
|
||||
co = OBBject()
|
||||
co: OBBject = OBBject()
|
||||
with pytest.raises(Exception):
|
||||
co.to_dataframe()
|
||||
|
||||
@@ -214,7 +214,7 @@ class MockDataFrame(Data):
|
||||
def test_to_dataframe(results, expected_df):
|
||||
"""Test helper."""
|
||||
# Arrange
|
||||
co = OBBject(results=results)
|
||||
co: OBBject = OBBject(results=results)
|
||||
|
||||
# Act and Assert
|
||||
if isinstance(expected_df, pd.DataFrame):
|
||||
@@ -253,7 +253,7 @@ def test_to_dataframe(results, expected_df):
|
||||
def test_to_dataframe_w_args(results, index, sort_by):
|
||||
"""Test helper."""
|
||||
# Arrange
|
||||
co = OBBject(results=results)
|
||||
co: OBBject = OBBject(results=results)
|
||||
|
||||
# Act and Assert
|
||||
result = co.to_dataframe(index=index, sort_by=sort_by)
|
||||
@@ -281,7 +281,7 @@ def test_to_dataframe_w_args(results, index, sort_by):
|
||||
def test_to_df_daylight_savings(results):
|
||||
"""Test helper."""
|
||||
# Arrange
|
||||
co = OBBject(results=results)
|
||||
co: OBBject = OBBject(results=results)
|
||||
|
||||
# Act and Assert
|
||||
expected_df = basemodel_to_df(results, index="date")
|
||||
@@ -342,7 +342,7 @@ def test_to_df_daylight_savings(results):
|
||||
def test_to_dict(results, expected_dict):
|
||||
"""Test helper."""
|
||||
# Arrange
|
||||
co = OBBject(results=results)
|
||||
co: OBBject = OBBject(results=results)
|
||||
|
||||
# Act and Assert
|
||||
if isinstance(expected_dict, (list, dict)):
|
||||
@@ -357,7 +357,7 @@ def test_to_dict(results, expected_dict):
|
||||
|
||||
def test_show_chart_exists():
|
||||
"""Test helper."""
|
||||
mock_instance = OBBject()
|
||||
mock_instance: OBBject = OBBject()
|
||||
# Arrange
|
||||
mock_instance.chart = MagicMock(spec=Chart)
|
||||
mock_instance.chart.fig = MagicMock()
|
||||
@@ -372,7 +372,7 @@ def test_show_chart_exists():
|
||||
|
||||
def test_show_chart_no_chart():
|
||||
"""Test helper."""
|
||||
mock_instance = OBBject()
|
||||
mock_instance: OBBject = OBBject()
|
||||
|
||||
# Act and Assert
|
||||
with pytest.raises(OpenBBError, match="Chart not found."):
|
||||
@@ -381,7 +381,7 @@ def test_show_chart_no_chart():
|
||||
|
||||
def test_show_chart_no_fig():
|
||||
"""Test helper."""
|
||||
mock_instance = OBBject()
|
||||
mock_instance: OBBject = OBBject()
|
||||
# Arrange
|
||||
mock_instance.chart = Chart()
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ def test_create_openbb_directory_directory_and_files_not_exist(tmpdir):
|
||||
)
|
||||
|
||||
# Act
|
||||
SystemSettings.create_openbb_directory(values)
|
||||
SystemSettings.create_openbb_directory(values) # type: ignore[operator]
|
||||
|
||||
# Assert
|
||||
assert os.path.exists(values.openbb_directory)
|
||||
assert os.path.exists(values.user_settings_path)
|
||||
assert os.path.exists(values.system_settings_path)
|
||||
assert os.path.exists(values.openbb_directory) # type: ignore[attr-defined]
|
||||
assert os.path.exists(values.user_settings_path) # type: ignore[attr-defined]
|
||||
assert os.path.exists(values.system_settings_path) # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_create_openbb_directory_directory_exists_user_settings_missing(tmpdir):
|
||||
@@ -47,15 +47,15 @@ def test_create_openbb_directory_directory_exists_user_settings_missing(tmpdir):
|
||||
)
|
||||
|
||||
# Create the openbb directory
|
||||
Path(values.openbb_directory).mkdir(parents=True, exist_ok=True)
|
||||
Path(values.openbb_directory).mkdir(parents=True, exist_ok=True) # type: ignore[attr-defined]
|
||||
|
||||
# Act
|
||||
SystemSettings.create_openbb_directory(values)
|
||||
SystemSettings.create_openbb_directory(values) # type: ignore[operator]
|
||||
|
||||
# Assert
|
||||
assert os.path.exists(values.openbb_directory)
|
||||
assert os.path.exists(values.user_settings_path)
|
||||
assert os.path.exists(values.system_settings_path)
|
||||
assert os.path.exists(values.openbb_directory) # type: ignore[attr-defined]
|
||||
assert os.path.exists(values.user_settings_path) # type: ignore[attr-defined]
|
||||
assert os.path.exists(values.system_settings_path) # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_create_openbb_directory_directory_exists_system_settings_missing(tmpdir):
|
||||
@@ -69,19 +69,19 @@ def test_create_openbb_directory_directory_exists_system_settings_missing(tmpdir
|
||||
)
|
||||
|
||||
# Create the openbb directory
|
||||
Path(values.openbb_directory).mkdir(parents=True, exist_ok=True)
|
||||
Path(values.openbb_directory).mkdir(parents=True, exist_ok=True) # type: ignore[attr-defined]
|
||||
|
||||
# Create the user_settings.json file
|
||||
with open(values.user_settings_path, "w") as f:
|
||||
with open(values.user_settings_path, "w") as f: # type: ignore[attr-defined]
|
||||
f.write("{}")
|
||||
|
||||
# Act
|
||||
SystemSettings.create_openbb_directory(values)
|
||||
SystemSettings.create_openbb_directory(values) # type: ignore[operator]
|
||||
|
||||
# Assert
|
||||
assert os.path.exists(values.openbb_directory)
|
||||
assert os.path.exists(values.user_settings_path)
|
||||
assert os.path.exists(values.system_settings_path)
|
||||
assert os.path.exists(values.openbb_directory) # type: ignore[attr-defined]
|
||||
assert os.path.exists(values.user_settings_path) # type: ignore[attr-defined]
|
||||
assert os.path.exists(values.system_settings_path) # type: ignore[attr-defined]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -138,7 +138,7 @@ def test_create_openbb_directory_directory_exists_system_settings_missing(tmpdir
|
||||
def test_validate_posthog_handler(values, expected_handlers):
|
||||
values = MockSystemSettings(**values)
|
||||
# Act
|
||||
result = SystemSettings.validate_posthog_handler(values)
|
||||
result = SystemSettings.validate_posthog_handler(values) # type: ignore[operator]
|
||||
|
||||
# Assert
|
||||
assert result.logging_handlers == expected_handlers
|
||||
@@ -160,7 +160,7 @@ def test_validate_posthog_handler(values, expected_handlers):
|
||||
def test_validate_logging_handlers(handlers, valid):
|
||||
# Act and Assert
|
||||
if valid:
|
||||
assert SystemSettings.validate_logging_handlers(handlers) == handlers
|
||||
assert SystemSettings.validate_logging_handlers(handlers) == handlers # type: ignore[call-arg]
|
||||
else:
|
||||
with pytest.raises(ValueError, match="Invalid logging handler"):
|
||||
SystemSettings.validate_logging_handlers(handlers)
|
||||
SystemSettings.validate_logging_handlers(handlers) # type: ignore[call-arg]
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from openbb_core.app.model.defaults import Defaults
|
||||
from openbb_core.app.service.user_service import (
|
||||
UserService,
|
||||
UserSettings,
|
||||
@@ -26,9 +27,9 @@ def test_write_default_user_settings():
|
||||
|
||||
# Create a UserSettings object with some test data
|
||||
user_settings = UserSettings()
|
||||
user_settings.credentials = {"username": "test"}
|
||||
user_settings.preferences = {"theme": "dark"}
|
||||
user_settings.defaults = {"language": "en"}
|
||||
user_settings.credentials = {"username": "test"} # type: ignore[assignment]
|
||||
user_settings.preferences = {"theme": "dark"} # type: ignore[assignment]
|
||||
user_settings.defaults = {"language": "en"} # type: ignore[assignment]
|
||||
|
||||
# Write the user settings to the temporary file
|
||||
UserService.write_default_user_settings(user_settings, temp_path)
|
||||
@@ -50,13 +51,13 @@ def test_update_default():
|
||||
"""Test update default user settings."""
|
||||
|
||||
# Some settings
|
||||
defaults_test = {"routes": {"test": {"test": "test"}}}
|
||||
defaults_test = Defaults(routes={"test": {"test": "test"}})
|
||||
other_settings = UserSettings(defaults=defaults_test)
|
||||
|
||||
# Update the default settings
|
||||
updated_settings = UserService.update_default(other_settings)
|
||||
|
||||
assert updated_settings.defaults.model_dump() == defaults_test
|
||||
assert updated_settings.defaults.model_dump() == defaults_test.model_dump()
|
||||
|
||||
|
||||
def test_merge_dicts():
|
||||
|
||||
@@ -10,7 +10,7 @@ from openbb_core.provider.abstract.data import Data
|
||||
def test_filter_inputs_not_df():
|
||||
"""Test filter_inputs."""
|
||||
kwargs = {"num": 1}
|
||||
kwargs = filter_inputs(**kwargs)
|
||||
kwargs = filter_inputs(False, None, **kwargs)
|
||||
|
||||
assert kwargs["num"] == 1
|
||||
|
||||
@@ -19,7 +19,7 @@ def test_filter_inputs_df():
|
||||
"""Test filter_inputs."""
|
||||
df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
||||
kwargs = {"data": df}
|
||||
kwargs = filter_inputs(data_processing=True, **kwargs)
|
||||
kwargs = filter_inputs(True, None, **kwargs)
|
||||
|
||||
assert isinstance(kwargs["data"], list)
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ def test_parameters_builder_get_polished_func(input_func, expected_annotations):
|
||||
polished_func = ParametersBuilder.get_polished_func(input_func)
|
||||
|
||||
assert polished_func.__annotations__ == expected_annotations
|
||||
assert polished_func.__signature__ == input_func.__signature__
|
||||
assert polished_func.__signature__ == input_func.__signature__ # type: ignore[attr-defined]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -206,10 +206,8 @@ def test_parameters_builder_update_command_context(
|
||||
def test_parameters_builder_update_provider_choices(
|
||||
command_coverage, route, kwargs, route_default, expected_result
|
||||
):
|
||||
with patch(
|
||||
"openbb_core.app.command_runner.ProviderInterface",
|
||||
**{"return_value.available_providers": ["provider1", "provider2"]},
|
||||
):
|
||||
with patch("openbb_core.app.command_runner.ProviderInterface") as mock_pi:
|
||||
mock_pi.available_providers = ["provider1", "provider2"]
|
||||
result = ParametersBuilder.update_provider_choices(
|
||||
mock_func, command_coverage, route, kwargs, route_default
|
||||
)
|
||||
@@ -293,12 +291,11 @@ def test_parameters_builder_build(mock_func, execution_context):
|
||||
|
||||
# TODO: add more test cases with @pytest.mark.parametrize
|
||||
|
||||
with patch(
|
||||
"openbb_core.app.command_runner.ProviderInterface",
|
||||
**{"return_value.available_providers": ["provider1", "provider2"]},
|
||||
):
|
||||
with patch("openbb_core.app.command_runner.ProviderInterface") as mock_pi:
|
||||
mock_pi.available_providers = ["provider1", "provider2"]
|
||||
|
||||
result = ParametersBuilder.build(
|
||||
args=[1, 2],
|
||||
args=(1, 2),
|
||||
kwargs={
|
||||
"c": 3,
|
||||
"d": "4",
|
||||
|
||||
@@ -16,8 +16,10 @@ class DeprecatedCommandsTest(unittest.TestCase):
|
||||
with self.subTest(i=path):
|
||||
if getattr(route, "deprecated", False):
|
||||
deprecation_message = getattr(route, "summary", "")
|
||||
obb_deprecation_warning = deprecation_message.metadata
|
||||
if hasattr(deprecation_message, "metadata"):
|
||||
obb_deprecation_warning = deprecation_message.metadata
|
||||
|
||||
assert (
|
||||
obb_deprecation_warning.expected_removal != current_major_minor
|
||||
), f"The expected removal version of `{path}` matches the current version, please remove it."
|
||||
assert (
|
||||
obb_deprecation_warning.expected_removal
|
||||
!= current_major_minor
|
||||
), f"The expected removal version of `{path}` matches the current version, please remove it."
|
||||
|
||||
@@ -79,13 +79,15 @@ def test_get_entry_point():
|
||||
el = ExtensionLoader()
|
||||
# pylint: disable=protected-access
|
||||
result = el._get_entry_point(el.provider_entry_points, "fmp")
|
||||
assert result.group == OpenBBGroups.provider.value
|
||||
assert result.name == "fmp"
|
||||
if result:
|
||||
assert result.group == OpenBBGroups.provider.value
|
||||
assert result.name == "fmp"
|
||||
|
||||
# pylint: disable=protected-access
|
||||
result = el._get_entry_point(el.core_entry_points, "equity")
|
||||
assert result.group == OpenBBGroups.core.value
|
||||
assert result.name == "equity"
|
||||
if result:
|
||||
assert result.group == OpenBBGroups.core.value
|
||||
assert result.name == "equity"
|
||||
|
||||
|
||||
def test_get_entry_point_not_found():
|
||||
@@ -106,8 +108,9 @@ def test_get_obbject_entry_point(mock_get_entry_point):
|
||||
|
||||
el = ExtensionLoader()
|
||||
result = el.get_obbject_entry_point("mock_extension")
|
||||
assert result.group == OpenBBGroups.obbject.value
|
||||
assert result.name == "mock_extension"
|
||||
if result:
|
||||
assert result.group == OpenBBGroups.obbject.value
|
||||
assert result.name == "mock_extension"
|
||||
|
||||
|
||||
@patch("openbb_core.app.extension_loader.ExtensionLoader._get_entry_point")
|
||||
@@ -120,8 +123,9 @@ def test_get_entry_point_core(mock_get_entry_point):
|
||||
|
||||
el = ExtensionLoader()
|
||||
result = el.get_core_entry_point("mock_extension")
|
||||
assert result.group == OpenBBGroups.obbject.value
|
||||
assert result.name == "mock_extension"
|
||||
if result:
|
||||
assert result.group == OpenBBGroups.obbject.value
|
||||
assert result.name == "mock_extension"
|
||||
|
||||
|
||||
@patch("openbb_core.app.extension_loader.ExtensionLoader._get_entry_point")
|
||||
@@ -134,8 +138,9 @@ def test_get_entry_point_provider(mock_get_entry_point):
|
||||
|
||||
el = ExtensionLoader()
|
||||
result = el.get_provider_entry_point("mock_extension")
|
||||
assert result.group == OpenBBGroups.obbject.value
|
||||
assert result.name == "mock_extension"
|
||||
if result:
|
||||
assert result.group == OpenBBGroups.obbject.value
|
||||
assert result.name == "mock_extension"
|
||||
|
||||
|
||||
def test_obbject_objects():
|
||||
|
||||
@@ -187,7 +187,7 @@ def test_signature_inspector_init(signature_inspector):
|
||||
def test_complete_signature(signature_inspector):
|
||||
"""Test complete_signature."""
|
||||
|
||||
async def sample_function(
|
||||
async def sample_function( # type: ignore[empty-body]
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
|
||||
@@ -25,11 +25,11 @@ df = pd.DataFrame(
|
||||
df_multiindex = df.set_index(["x", "y"])
|
||||
|
||||
simple_base_model = [
|
||||
Data(x=i, y=j, z=k) for i in range(2) for j in range(6, 8) for k in range(10, 12)
|
||||
Data(x=i, y=j, z=k) for i in range(2) for j in range(6, 8) for k in range(10, 12) # type: ignore[call-arg]
|
||||
]
|
||||
|
||||
multi_index_base_model = [
|
||||
Data(x=i, y=j, z=k, is_multiindex=True, multiindex_names="['x','y']")
|
||||
Data(x=i, y=j, z=k, is_multiindex=True, multiindex_names="['x','y']") # type: ignore[call-arg]
|
||||
for i in range(2)
|
||||
for j in range(6, 8)
|
||||
for k in range(10, 12)
|
||||
@@ -39,7 +39,7 @@ multi_index_base_model = [
|
||||
def test_df_to_basemodel():
|
||||
base_model = df_to_basemodel(df)
|
||||
assert isinstance(base_model, list)
|
||||
assert base_model[0].x == 1
|
||||
assert base_model[0].x == 1 # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_df_to_basemodel_multiindex():
|
||||
@@ -93,11 +93,11 @@ def test_list_to_basemodel(data_list, expected):
|
||||
"data_dict, expected",
|
||||
[
|
||||
# Simple dictionary
|
||||
({"a": 10}, Data(a=10)),
|
||||
({"a": 10}, Data(a=10)), # type: ignore[call-arg]
|
||||
# Nested dictionary (assuming Data can handle nested dicts)
|
||||
({"b": {"c": 20}}, Data(b={"c": 20})),
|
||||
({"b": {"c": 20}}, Data(b={"c": 20})), # type: ignore[call-arg]
|
||||
# Dictionary with list (assuming Data can handle lists)
|
||||
({"d": [30, 40]}, Data(d=[30, 40])),
|
||||
({"d": [30, 40]}, Data(d=[30, 40])), # type: ignore[call-arg]
|
||||
],
|
||||
)
|
||||
def test_dict_to_basemodel(data_dict, expected):
|
||||
|
||||
@@ -14,14 +14,14 @@ def test_check_int_valid():
|
||||
def test_check_int_invalid():
|
||||
"""Test if the check_int function raises an error when the value is not an int."""
|
||||
with pytest.raises(TypeError):
|
||||
check_int("not_an_integer")
|
||||
check_int("not_an_integer") # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_data_model():
|
||||
"""Test the Data model."""
|
||||
some_data = Data(test="test")
|
||||
some_data = Data(test="test") # type: ignore[call-arg]
|
||||
|
||||
assert some_data.test == "test"
|
||||
assert some_data.test == "test" # type: ignore[attr-defined]
|
||||
assert not some_data.__alias_dict__
|
||||
assert some_data.__repr__() == "Data(test=test)"
|
||||
assert some_data.model_dump() == {"test": "test"}
|
||||
@@ -35,9 +35,9 @@ def test_data_model_alias():
|
||||
|
||||
__alias_dict__ = {"test_alias": "test"}
|
||||
|
||||
some_data = SomeData(test="Hello")
|
||||
some_data = SomeData(test="Hello") # type: ignore[call-arg]
|
||||
|
||||
assert some_data.__alias_dict__ == {"test_alias": "test"}
|
||||
assert some_data.__repr__() == "SomeData(test_alias=Hello)"
|
||||
assert some_data.model_dump() == {"test_alias": "Hello"}
|
||||
assert some_data.test_alias == "Hello"
|
||||
assert some_data.test_alias == "Hello" # type: ignore[attr-defined]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Test the Provider."""
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.provider import Provider
|
||||
|
||||
|
||||
@@ -21,14 +22,14 @@ def test_provider_with_optional_parameters():
|
||||
description="A simple test provider.",
|
||||
website="https://testprovider.example.com",
|
||||
credentials=["api_key"],
|
||||
fetcher_dict={"fetcher1": None},
|
||||
fetcher_dict={"fetcher1": Fetcher},
|
||||
)
|
||||
|
||||
assert provider.name == "TestProvider"
|
||||
assert provider.description == "A simple test provider."
|
||||
assert provider.website == "https://testprovider.example.com"
|
||||
assert provider.credentials == ["testprovider_api_key"]
|
||||
assert provider.fetcher_dict == {"fetcher1": None}
|
||||
assert provider.fetcher_dict == {"fetcher1": Fetcher}
|
||||
|
||||
|
||||
def test_provider_credentials_formatting():
|
||||
|
||||
@@ -5,14 +5,14 @@ from openbb_core.provider.abstract.query_params import QueryParams
|
||||
|
||||
def test_query_params_repr():
|
||||
"""Test the __repr__ method of QueryParams."""
|
||||
params = QueryParams(param1="value1", param2="value2")
|
||||
params = QueryParams(param1="value1", param2="value2") # type: ignore[call-arg]
|
||||
assert "param1='value1'" in str(params)
|
||||
assert "param2='value2'" in str(params)
|
||||
|
||||
|
||||
def test_query_params_no_alias():
|
||||
"""Test model_dump without aliases."""
|
||||
params = QueryParams(param1="value1", param2="value2")
|
||||
params = QueryParams(param1="value1", param2="value2") # type: ignore[call-arg]
|
||||
dumped_params = params.model_dump()
|
||||
|
||||
assert dumped_params == {"param1": "value1", "param2": "value2"}
|
||||
@@ -24,7 +24,7 @@ def test_query_params_with_alias():
|
||||
class AliasedQueryParams(QueryParams):
|
||||
__alias_dict__ = {"param1": "alias1"}
|
||||
|
||||
params = AliasedQueryParams(param1="value1", param2="value2")
|
||||
params = AliasedQueryParams(param1="value1", param2="value2") # type: ignore[call-arg]
|
||||
dumped_params = params.model_dump()
|
||||
|
||||
assert dumped_params == {"alias1": "value1", "param2": "value2"}
|
||||
|
||||
@@ -54,14 +54,14 @@ def test_standard_models(standard_model):
|
||||
), f"Field {name} should be a ModelField instance"
|
||||
if "QueryParams" in standard_model.__name__:
|
||||
if name in QUERY_DESCRIPTIONS:
|
||||
assert QUERY_DESCRIPTIONS[name] in field.description, (
|
||||
assert QUERY_DESCRIPTIONS[name] in getattr(field, "description"), (
|
||||
f"Description for {name} is incorrect for the {standard_model.__name__}.\n"
|
||||
f"Please modify the description or change the field name to a non-reserved name."
|
||||
f"To get a full list of reserved descriptions, navigate to openbb_core.provider.utils.descriptions.py"
|
||||
f"You can also add extra information to the existing reserved field description in your model."
|
||||
)
|
||||
elif name in DATA_DESCRIPTIONS:
|
||||
assert DATA_DESCRIPTIONS[name] in field.description, (
|
||||
assert DATA_DESCRIPTIONS[name] in getattr(field, "description"), (
|
||||
f"Description for {name} is incorrect for the {standard_model.__name__}.\n"
|
||||
f"Please modify the description or change the field name to a non-reserved name."
|
||||
f"To get a full list of reserved descriptions, navigate to openbb_core.provider.utils.descriptions.py"
|
||||
|
||||
@@ -11,14 +11,16 @@ from yarl import URL
|
||||
|
||||
def test_obfuscate():
|
||||
"""Test the obfuscate helper."""
|
||||
params = {
|
||||
"api_key": "1234",
|
||||
"token": "1234",
|
||||
"auth": "1234",
|
||||
"auth_token": "1234",
|
||||
"c": "1234",
|
||||
"api_key2": "1234",
|
||||
}
|
||||
params = CIMultiDict(
|
||||
{
|
||||
"api_key": "1234",
|
||||
"token": "1234",
|
||||
"auth": "1234",
|
||||
"auth_token": "1234",
|
||||
"c": "1234",
|
||||
"api_key2": "1234",
|
||||
}
|
||||
)
|
||||
|
||||
assert client.obfuscate(params) == {
|
||||
"api_key": "********",
|
||||
|
||||
@@ -108,7 +108,7 @@ async def test_amake_request(monkeypatch):
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await amake_request("http://mock.url", method="PUT")
|
||||
await amake_request("http://mock.url", method="PUT") # type: ignore[arg-type]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -90,4 +90,4 @@ class FMPCompanyNewsFetcher(
|
||||
) -> List[FMPCompanyNewsData]:
|
||||
"""Return the transformed data."""
|
||||
modeled_data = [FMPCompanyNewsData.model_validate(d) for d in data]
|
||||
return filter_by_dates(modeled_data, query.start_date, query.end_date) # type: ignore
|
||||
return filter_by_dates(modeled_data, query.start_date, query.end_date)
|
||||
|
||||
@@ -79,11 +79,13 @@ class IntrinioCompanyNewsFetcher(
|
||||
symbol = response.url.parts[-2]
|
||||
data = await response.json()
|
||||
|
||||
return [{**d, "symbol": symbol} for d in data.get("news", [])]
|
||||
if isinstance(data, dict):
|
||||
return [{**d, "symbol": symbol} for d in data.get("news", [])]
|
||||
return []
|
||||
|
||||
urls = [
|
||||
f"{base_url}/{symbol}/news?{query_str}&api_key={api_key}"
|
||||
for symbol in [s.strip() for s in query.symbol.split(",")]
|
||||
for symbol in [s.strip() for s in getattr(query, "symbol", "").split(",")]
|
||||
]
|
||||
|
||||
return await amake_requests(urls, callback, **kwargs)
|
||||
@@ -95,4 +97,4 @@ class IntrinioCompanyNewsFetcher(
|
||||
) -> List[IntrinioCompanyNewsData]:
|
||||
"""Return the transformed data."""
|
||||
modeled_data = [IntrinioCompanyNewsData.model_validate(d) for d in data]
|
||||
return filter_by_dates(modeled_data, query.start_date, query.end_date) # type: ignore
|
||||
return filter_by_dates(modeled_data, query.start_date, query.end_date)
|
||||
|
||||
Reference in New Issue
Block a user