Files
OpenBB/openbb_sdk/providers/tests/utils/unit_tests_generator.py
teh_coderer 6a9308c3e7 Feature/sdk v4 pydantic v2 (#5458)
* init

* Update provider_interface.py

* docstring

* pydantic v2

* Update __init__.py

* build package v2 support

* `alises` to `aliases`

Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com>

* `alises` to `aliases`

Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com>

* `alises` to `aliases`

Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com>

* updates

* Update registry.py

* validate_arguments -> validate_call

* update terminal classes

* response_model_by_alias = False

* query_params by alias

* Update major_indices_constituents.py

* add alias to Query in `provider_interface`

* yfinance

* build package, add param defaults and correct `OBBject` results type

* fix cpi validation errors

* Update risk_premium.py

* fix `forex_pairs` active query param annotation, set default when only 1 provider

* fix and refactor intrinio `options_chains`

* Update poetry.lock

* fix benzinga news, build package, add intrinio fred_historical fetcher

* Update extension_map.json

* ruff

* Update unit_test_generator.py

* updates

* add `default=None` on Optional fields

* `parse_obj` -> `model_validate`

* add alias to dup fields on providers with `__alias_dict__`, fix `test_generate_model_docstring`

* Update package_builder.py

* fix validation errors, fix tests

* tests http

* `fred_historical` record

* Update test_intrinio_fetchers.py

* Update test_intrinio_fetchers.py

* Update test_intrinio_fetchers.py

* Update available_indices.py

* Update stock_historical.py

* build package

* build package without optional

* change `SystemSettings` -> `MockSystemSettings` in `test_system_settings.py`

* `openbb_figure.py`/`backend.py` terminal changes  , update deps/lock files

* `linearmodels>4.26`  auto installs `setuptools-scm` which cause error messages

* clean up

* `dev_install.py` for local development

* ruff

* dup `# noqa`

* build

* Update bitquery_model.py

* records

* rec

* f

* worky?

* update `integration_test_generator.py` and `unit_test_generator.py`

* renamed test to tests in file

* fix import due to change in file name

---------

Co-authored-by: Pratyush Shukla <ps4534@nyu.edu>
Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com>
2023-09-27 09:40:07 +01:00

197 lines
7.4 KiB
Python

"""The unit test generator for the fetchers."""
from datetime import date
from pathlib import Path
from typing import Any, Dict, Tuple
from openbb_provider.abstract.fetcher import Fetcher
from openbb_provider.registry import RegistryLoader
from openbb_provider.utils.helpers import to_snake_case
from pydantic.fields import FieldInfo
from pydantic_core import PydanticUndefined
from sdk.core.openbb_core.app.provider_interface import ProviderInterface
from providers.tests.utils.credentials_schema import test_credentials
PROVIDERS_PATH = Path(__file__).parent.parent.parent.resolve()
def get_provider_fetchers() -> Dict[str, Dict[str, Fetcher]]:
"""Get the fetchers from the provider registry."""
registry = RegistryLoader.from_extensions()
provider_fetcher_map: Dict[str, Dict[str, Fetcher]] = {}
for provider_name, provider_cls in registry.providers.items():
provider_fetcher_map[provider_name] = {}
for fetcher_name, fetcher_cls in provider_cls.fetcher_dict.items():
provider_fetcher_map[provider_name][fetcher_name] = fetcher_cls
return provider_fetcher_map
def generate_fetcher_unit_tests(path: Path) -> None:
"""Generate the fetcher unit tests in the provider test folders."""
if not path.exists():
with open(path, "w", encoding="utf-8", newline="\n") as f:
f.write(
"from datetime import datetime\n"
"import pytest\nfrom openbb_core.app.service.user_service import UserService\n"
)
def get_test_params(param_fields: Dict[str, FieldInfo]) -> Dict[str, Any]:
"""Get the test params for the fetcher based on the requires standard params."""
test_params: Dict[str, Any] = {}
for field_name, field in param_fields.items():
if field.is_required() and field.default is not PydanticUndefined:
test_params[field_name] = field.default
elif field.is_required() or field.default is PydanticUndefined:
example_dict = {
"symbol": "AAPL",
"symbols": "AAPL,MSFT",
"start_date": date(2023, 1, 1),
"end_date": date(2023, 6, 6),
"country": "Portugal",
"date": date(2023, 1, 1),
"countries": ["portugal", "spain"],
}
if field_name in example_dict:
test_params[field_name] = example_dict[field_name]
elif field.annotation == str:
test_params[field_name] = "test"
elif field.annotation == int:
test_params[field_name] = 1
elif field.annotation == float:
test_params[field_name] = 1.0
elif field.annotation == bool:
test_params[field_name] = True
# This makes sure that the unit test are reproducible by fixing the date
elif not field.is_required() and field_name in [
"start_date",
"end_date",
"date",
]:
if field_name == "start_date":
test_params[field_name] = date(2023, 1, 1)
elif field_name == "end_date":
test_params[field_name] = date(2023, 6, 6)
elif field_name == "date":
test_params[field_name] = date(2023, 1, 1)
return test_params
def write_test_credentials(path: str, provider: str) -> None:
"""Write the mocked credentials to the provider test folders."""
credentials: Tuple[str, str] = test_credentials.get(
provider, ("token", "MOCK_TOKEN")
)
template = """
test_credentials = UserService().default_user_settings.credentials.dict()
@pytest.fixture(scope="module")
def vcr_config():
return {{
"filter_headers": [("User-Agent", None)],
"filter_query_parameters": [
{credentials_str},
],
}}
"""
with open(path, "a", encoding="utf-8", newline="\n") as f:
f.write(template.format(credentials_str=str(credentials)))
def check_pattern_in_file(file_path: str, pattern: str) -> bool:
"""Check if a pattern is in a file."""
with open(file_path, encoding="utf-8", newline="\n") as f:
lines = f.readlines()
for line in lines:
if pattern in line:
return True
return False
def write_fetcher_unit_tests() -> None:
"""Write the fetcher unit tests to the provider test folders."""
provider_interface = ProviderInterface()
provider_interface_map = provider_interface.map
fetchers = get_provider_fetchers()
provider_fetchers: Dict[str, Dict[str, str]] = {}
for provider, fetcher_dict in fetchers.items():
path = PROVIDERS_PATH / provider / "tests" / f"test_{provider}_fetchers.py"
generate_fetcher_unit_tests(path)
for model_name, fetcher in fetcher_dict.items():
fetcher_loaded = fetcher()
fetcher_path = fetcher_loaded.__module__
fetcher_name = fetcher_loaded.__class__.__name__
if model_name not in provider_fetchers:
provider_fetchers[model_name] = {}
provider_fetchers[model_name][fetcher_name] = path
pattern = f"from {fetcher_path}"
if not check_pattern_in_file(path, pattern):
with open(path, "a", encoding="utf-8", newline="\n") as f:
f.write(f"{pattern} import {fetcher_name}\n")
pattern = "vcr_config"
if not check_pattern_in_file(path, pattern):
write_test_credentials(path, provider)
test_template = """
@pytest.mark.record_http
def test_{fetcher_name_snake}(credentials=test_credentials):
params = {params}
fetcher = {fetcher_name}()
result = fetcher.test(params, credentials)
assert result is None
"""
for model_name, fetcher_dict in provider_fetchers.items():
for fetcher_name, path in fetcher_dict.items():
pattern = f"{fetcher_name}()"
if check_pattern_in_file(path, pattern):
continue
# Add logic here to grab the necessary standardized params and credentials
test_params = get_test_params(
param_fields=provider_interface_map[model_name]["openbb"][
"QueryParams"
]["fields"]
)
if "forex" in fetcher_name.lower() and "symbol" in test_params:
test_params["symbol"] = "EUR/USD"
if "crypto" in fetcher_name.lower() and "symbol" in test_params:
test_params["symbol"] = "BTC/USD"
if "indices" in fetcher_name.lower() and "symbol" in test_params:
test_params["symbol"] = "DJI"
if "future" in fetcher_name.lower() and "symbol" in test_params:
test_params["symbol"] = "ES"
if (
"european" in fetcher_name.lower()
and "symbol" in test_params
and "index" in fetcher_name.lower()
):
test_params["symbol"] = "BUKBUS"
if "european" in fetcher_name.lower() and "symbol" in test_params:
test_params["symbol"] = "BUKBUS"
with open(path, "a", encoding="utf-8", newline="\n") as f:
test_code = test_template.format(
fetcher_name_snake=to_snake_case(fetcher_name),
fetcher_name=fetcher_name,
params=test_params,
credentials={},
)
f.write(test_code)
f.write("\n\n")
if __name__ == "__main__":
write_fetcher_unit_tests()