Merge branch 'feature/openbb-sdk-v4' into feature/auto-test

This commit is contained in:
hjoaquim
2023-09-18 09:41:02 +01:00
8 changed files with 275 additions and 52 deletions

2
.coveragerc vendored
View File

@@ -2,4 +2,6 @@
omit =
env/*
tests/*
**/tests/**
**/package/**
source = .

View File

@@ -1,8 +1,11 @@
import nox
test_locations = ["openbb_sdk/sdk/core/tests", "openbb_sdk/providers/tests"]
@nox.session(python=["3.8", "3.9", "3.10", "3.11"])
def tests(session):
session.install("./openbb_sdk")
session.install("./openbb_sdk[all]")
session.install("pytest")
session.run("pytest", "openbb_sdk/sdk/core/tests")
session.install("pytest-cov")
session.run("pytest", *test_locations, "--cov=openbb_sdk/")

99
openbb_sdk/poetry.lock generated vendored
View File

@@ -215,16 +215,6 @@ files = [
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]]
name = "atomicwrites"
version = "1.4.1"
description = "Atomic file writes."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
]
[[package]]
name = "attrs"
version = "23.1.0"
@@ -308,13 +298,33 @@ lxml = ["lxml"]
[[package]]
name = "black"
version = "23.9.0"
version = "23.9.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.9.0-py3-none-any.whl", hash = "sha256:9366c1f898981f09eb8da076716c02fd021f5a0e63581c66501d68a2e4eab844"},
{file = "black-23.9.0.tar.gz", hash = "sha256:3511c8a7e22ce653f89ae90dfddaf94f3bb7e2587a245246572d3b9c92adf066"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"},
{file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"},
{file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"},
{file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"},
{file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"},
{file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"},
{file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"},
{file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"},
{file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"},
{file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"},
{file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"},
]
[package.dependencies]
@@ -1991,17 +2001,6 @@ files = [
{file = "property_cached-1.6.4-py2.py3-none-any.whl", hash = "sha256:135fc059ec969c1646424a0db15e7fbe1b5f8c36c0006d0b3c91ba568c11e7d8"},
]
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
[[package]]
name = "pyasn1"
version = "0.5.0"
@@ -2087,27 +2086,40 @@ tests = ["pytest", "pytest-xdist"]
[[package]]
name = "pytest"
version = "6.2.5"
version = "7.4.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
files = [
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
{file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"},
{file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"},
]
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
toml = "*"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-subtests"
version = "0.11.0"
description = "unittest subTest() support and subtests fixture"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-subtests-0.11.0.tar.gz", hash = "sha256:51865c88457545f51fb72011942f0a3c6901ee9e24cbfb6d1b9dc1348bafbe37"},
{file = "pytest_subtests-0.11.0-py3-none-any.whl", hash = "sha256:453389984952eec85ab0ce0c4f026337153df79587048271c7fd0f49119c07e4"},
]
[package.dependencies]
attrs = ">=19.2.0"
pytest = ">=7.0"
[[package]]
name = "python-dateutil"
@@ -2651,19 +2663,19 @@ test = ["pytest"]
[[package]]
name = "setuptools"
version = "68.2.0"
version = "68.2.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = true
python-versions = ">=3.8"
files = [
{file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"},
{file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"},
{file = "setuptools-68.2.1-py3-none-any.whl", hash = "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae"},
{file = "setuptools-68.2.1.tar.gz", hash = "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "setuptools-scm"
@@ -2844,17 +2856,6 @@ webencodings = ">=0.4"
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "pytest"]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
[[package]]
name = "tomli"
version = "2.0.1"
@@ -3275,4 +3276,4 @@ yfinance = ["openbb-yfinance"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.8,<3.12"
content-hash = "468558e372f483a87864db0d5220e56937b8b2b1c8ad67a614d959d62ccb19e4"
content-hash = "b88ccaa7809833588bdae056576238ede6670f0c8ce43126d81c4757bbd3f30c"

View File

0
openbb_sdk/providers/tests/.gitkeep vendored Normal file
View File

View File

View File

@@ -0,0 +1,216 @@
import glob
import importlib
import inspect
import os
import unittest
from typing import Dict, List, Type
from openbb_provider import standard_models
from openbb_provider.abstract.data import Data
from openbb_provider.abstract.query_params import QueryParams
from openbb_provider.registry import RegistryLoader
def get_module(file_path: str, package_name: str):
"""
Given a file path and its package, loads the module.
Parameters
----------
file_path (str): The path to the file.
package (str): The package where the module is located.
Returns
-------
module: The loaded module.
"""
# Get the module name by removing the file extension and getting the basename
module_name = os.path.splitext(os.path.basename(file_path))[0]
# Import the module using the package name and the module name
return importlib.import_module(f"{package_name}.{module_name}")
def get_subclasses_w_keys(module: object, cls: Type) -> Dict[Type, List[str]]:
"""Given a module and a class, return the subclasses of the class and their fields.
Parameters
----------
module (object): The module containing the classes.
cls (Type): The base class.
Returns
-------
Dict[Type, List[str]]: A dictionary mapping each subclass to a list of its field names.
"""
subclasses = {}
module_members = inspect.getmembers(module)
for _, obj in module_members:
if inspect.isclass(obj) and issubclass(obj, cls) and obj != cls:
subclasses[obj] = list(obj.__fields__.keys())
return subclasses
def get_subclasses(
python_files: List[str], package_name: str, cls: Type
) -> Dict[Type, List[str]]:
"""
Given a list of python files, and a class, return a dictionary of
subclasses of that class that are defined in those files.
Parameters
----------
python_files (List[str]): A list of file paths to Python files.
package_name (str): The name of the package.
cls (Type): The base class.
Returns
-------
Dict[str, Type]: A dictionary where the keys are the subclass names
and the values are the subclasses themselves.
"""
subclasses = {}
for file_path in python_files:
module = get_module(file_path, package_name)
subclasses.update(get_subclasses_w_keys(module, cls))
return subclasses
def child_parent_map(map_: Dict, parents: Dict, module: object) -> None:
"""
Generate a mapping of child classes to their parent classes and provider fields.
Parameters
----------
map_ (dict): The dict to append to.
parents (dict): A dictionary of parent classes and their standard fields.
module (module): The module containing the classes.
"""
for cls, std_fields in parents.items():
# Check if class name is not already in the map
if cls.__name__ not in map_:
map_[cls.__name__] = []
# Get the first subclass and its provider fields
sub_w_keys = get_subclasses_w_keys(module, cls)
if sub_w_keys:
subclass = list(sub_w_keys.keys())[0]
provider_fields = list(sub_w_keys.values())[0]
# Remove standard fields from provider fields
provider_fields = [
field for field in provider_fields if field not in std_fields
]
# If there are provider fields, add them to the map
if provider_fields:
map_[cls.__name__].append({subclass.__name__: provider_fields})
def get_path_components(path: str):
"""Given a path, return a list of path components"""
path_components = []
head, tail = os.path.split(path)
while tail:
path_components.append(tail)
head, tail = os.path.split(head)
return path_components
def match_provider_and_fields(
providers_w_fields: List[Dict[str, List[str]]], duplicated_fields: List[str]
) -> List[str]:
"""
Given a list of providers with fields and duplicated fields,
return a list of matching "Provider:'dup_field'".
"""
matching_provider_fields = []
for item in providers_w_fields:
for model, fields in item.items():
for f in duplicated_fields:
if f in fields:
matching_provider_fields.append(f"{model}:'{f}'")
return matching_provider_fields
def get_provider_modules():
"""Get provider modules."""
registry = RegistryLoader.from_extensions()
modules = []
for _, provider in registry.providers.items():
for _, fetcher in provider.fetcher_dict.items():
modules.append(fetcher.__module__)
return modules
class ProviderFieldDupesTest(unittest.TestCase):
"""Test for common fields in the provider models that should be standard."""
def test_provider_field_dupes(self):
"""
This function checks for duplicate fields in the provider models
and identifies the fields that should be standardized.
"""
standard_models_directory = os.path.dirname(standard_models.__file__)
standard_models_files = glob.glob(
os.path.join(standard_models_directory, "*.py")
)
standard_query_classes = get_subclasses(
standard_models_files, standard_models.__name__, QueryParams
)
standard_data_classes = get_subclasses(
standard_models_files, standard_models.__name__, Data
)
provider_modules = get_provider_modules()
child_parent_dict = {}
for module in provider_modules:
provider_module = importlib.import_module(module)
# query classes
child_parent_map(child_parent_dict, standard_query_classes, provider_module)
# data classes
child_parent_map(child_parent_dict, standard_data_classes, provider_module)
# remove keys with no values
child_parent_dict = {k: v for k, v in child_parent_dict.items() if v}
for std_cls in child_parent_dict:
with self.subTest(i=std_cls):
providers_w_fields = child_parent_dict[std_cls]
fields = []
provider_models = []
for provider_cls in providers_w_fields:
provider_models.extend(list(provider_cls.keys()))
fields.extend(list(provider_cls.values())[0])
seen = set()
dupes = [x for x in fields if x in seen or seen.add(x)]
dupes_str = ", ".join([f"'{x}'" for x in set(dupes)])
provider_str = (
", ".join(match_provider_and_fields(providers_w_fields, set(dupes)))
if dupes
else ""
)
assert not dupes, (
f"The following fields are common among models and should be standardized: {dupes_str}.\n"
f"Standard model: {std_cls}, Provider models: {provider_str}\n"
)

View File

@@ -59,7 +59,8 @@ all = [
]
[tool.poetry.group.dev.dependencies]
pytest = "^6.2.2"
pytest = "^7.0.0"
pytest-subtests = "^0.11.0"
[build-system]
requires = ["poetry-core"]