mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-08 18:32:21 +00:00
* Fixes * Update stocks_helper.py * update git-actions set-output to new format * Update stocks_helper.py * Update terminal_helper.py * removed LineAnnotateDrawer from qa_view * lint * few changes * updates * sdk auto gen modules done * Update stocks_helper.py * updates to changed imports, and remove first sdk_modules * Update generate_sdk.py * Update generate_sdk.py * pylint * revert stocks_helper * Update generate_sdk.py * Update sdk.py * Update generate_sdk.py * full auto generation, added sdk.py/controllers creation * missed enable forecasting * added running black in subprocess after sdk files generation completes * removed deleted sdk_arg_logger * comment out tests * property doc fix * clean up * Update generate_sdk.py * make trailmap classes useable for doc generation * Update generate_sdk.py * added lineon to trailmap class for linking to func in markdown * changed lineon to dict * added full_path to trailmap for linking in docs * updated portfolio * feat: initial files * feat: added meta head * feat: added funcdef * added func_def to trailmap attributes for markdown in docs, added missing type hints to covid functions * feat: added view and merged with jaun * Update generate_sdk.py * Update generate_sdk.py * Update generate_sdk.py * Update generate_sdk.py * init * fix returns * fix: random stuff * fix: random * fixed encoding issue on windows * fix: generate tabs * update * Update generate_sdk_markdown.py * Create .pydocstyle.ini * added type hint classes for views * fixes * alt, ba * alt-economy * Update finviz_compare_model.py * fixs * Update substack_model.py * Update generate_sdk.py * last of my section * porfolio * po * Update optimizer_model.py * fixing more things * few more * keys done * update * fixes * Update generate_sdk_markdown.py * Update generate_sdk_markdown.py * mypy forecast fix * Update generate_sdk_markdown.py * Update generate_sdk_markdown.py * Update generate_sdk_markdown.py * fixes * forecast fixes * one more fix * Update coinbase_model.py * Update generate_sdk_markdown.py Co-authored-by: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Co-authored-by: James Maslek <jmaslek11@gmail.com> Co-authored-by: jose-donato <zmcdonato@gmail.com> Co-authored-by: andrewkenreich <andrew.kenreich@gmail.com>
287 lines
9.8 KiB
Python
287 lines
9.8 KiB
Python
import csv
|
|
import importlib
|
|
import inspect
|
|
import os
|
|
|
|
from types import FunctionType
|
|
from typing import Dict, List, Literal, Optional
|
|
|
|
from docstring_parser import parse
|
|
|
|
from openbb_terminal.rich_config import console
|
|
from openbb_terminal.core.library.trail_map import FORECASTING, MISCELLANEOUS_DIRECTORY
|
|
|
|
MAP_PATH = MISCELLANEOUS_DIRECTORY / "library" / "trail_map.csv"
|
|
MAP_FORECASTING_PATH = MISCELLANEOUS_DIRECTORY / "library" / "trail_map_forecasting.csv"
|
|
|
|
|
|
def clean_attr_desc(attr: Optional[FunctionType] = None) -> Optional[str]:
|
|
"""Clean the attribute description."""
|
|
if attr.__doc__ is None:
|
|
return None
|
|
return (
|
|
attr.__doc__.splitlines()[1].lstrip()
|
|
if not attr.__doc__.splitlines()[0]
|
|
else attr.__doc__.splitlines()[0].lstrip()
|
|
if attr.__doc__
|
|
else ""
|
|
)
|
|
|
|
|
|
class Trailmap:
|
|
def __init__(self, trailmap: str, model: str, view: Optional[str] = None):
|
|
tmap = trailmap.split(".")
|
|
if len(tmap) == 1:
|
|
tmap = ["", tmap[0]]
|
|
self.class_attr: str = tmap.pop(-1)
|
|
self.category = tmap[0]
|
|
self.location_path = tmap
|
|
self.model = model
|
|
self.view = view if view else None
|
|
self.model_func: Optional[str] = model if model else None
|
|
self.view_func: Optional[str] = view if view else None
|
|
self.short_doc: Dict[str, Optional[str]] = {}
|
|
self.long_doc: Dict[str, str] = {}
|
|
self.lineon: Dict[str, int] = {}
|
|
self.full_path: Dict[str, str] = {}
|
|
self.func_def: Dict[str, str] = {}
|
|
self.func_attr: Dict[str, FunctionType] = {}
|
|
self.params: Dict[str, Dict[str, inspect.Parameter]] = {}
|
|
self.get_docstrings()
|
|
|
|
def get_docstrings(self) -> None:
|
|
"""Gets the function docstrings. We get the short and long docstrings."""
|
|
|
|
for key, func in zip(["model", "view"], [self.model, self.view]):
|
|
if func:
|
|
module_path, function_name = func.rsplit(".", 1)
|
|
module = importlib.import_module(module_path)
|
|
self.func_attr[key] = getattr(module, function_name)
|
|
|
|
add_juan = 0
|
|
if "__wrapped__" in dir(self.func_attr[key]):
|
|
self.func_attr[key] = self.func_attr[key].__wrapped__
|
|
if "__wrapped__" in dir(self.func_attr[key]):
|
|
self.func_attr[key] = self.func_attr[key].__wrapped__
|
|
add_juan = 1
|
|
self.lineon[key] = (
|
|
inspect.getsourcelines(self.func_attr[key])[1] + add_juan
|
|
)
|
|
|
|
self.func_def[key] = self.get_definition(key)
|
|
self.long_doc[key] = self.func_attr[key].__doc__
|
|
self.short_doc[key] = clean_attr_desc(self.func_attr[key])
|
|
full_path = (
|
|
inspect.getfile(self.func_attr[key])
|
|
.replace("\\", "/")
|
|
.split("openbb_terminal/")[1]
|
|
)
|
|
self.full_path[key] = f"openbb_terminal/{full_path}"
|
|
|
|
def get_definition(self, key: str) -> str:
|
|
"""Creates the function definition to be used in SDK docs."""
|
|
funcspec = inspect.getfullargspec(self.func_attr[key])
|
|
|
|
definition = ""
|
|
added_comma = False
|
|
for arg in funcspec.args:
|
|
annotation = (
|
|
funcspec.annotations[arg] if arg in funcspec.annotations else "Any"
|
|
)
|
|
if arg in funcspec.annotations:
|
|
annotation = (
|
|
str(annotation)
|
|
.replace("<class '", "")
|
|
.replace("'>", "")
|
|
.replace("typing.", "")
|
|
.replace("pandas.core.frame.", "pd.")
|
|
.replace("pandas.core.series.", "pd.")
|
|
.replace("openbb_terminal.portfolio.", "")
|
|
)
|
|
definition += f"{arg}: {annotation}, "
|
|
added_comma = True
|
|
|
|
if added_comma:
|
|
definition = definition[:-2]
|
|
|
|
return_def = (
|
|
funcspec.annotations["return"].__name__
|
|
if "return" in funcspec.annotations
|
|
and hasattr(funcspec.annotations["return"], "__name__")
|
|
and funcspec.annotations["return"] is not None
|
|
else "None"
|
|
)
|
|
definition = f"def {getattr(self, f'{key}_func').split('.')[-1]}({definition }) -> {return_def}"
|
|
return definition
|
|
|
|
|
|
def get_trailmaps() -> List[Trailmap]:
|
|
trailmaps = []
|
|
for tmap_csv in [MAP_PATH, MAP_FORECASTING_PATH]:
|
|
if tmap_csv == MAP_FORECASTING_PATH and not FORECASTING:
|
|
console.print(
|
|
"[bold red]Forecasting is disabled. Forecasting will not be included in the Generation of Docs[/bold red]"
|
|
)
|
|
break
|
|
with open(tmap_csv) as csvfile:
|
|
reader = csv.reader(csvfile, delimiter=",")
|
|
next(reader)
|
|
for row in reader:
|
|
trailmaps.append(Trailmap(*row))
|
|
|
|
return trailmaps
|
|
|
|
|
|
def get_function_meta(trailmap: Trailmap, trail_type: Literal["model", "view"]):
|
|
doc_parsed = parse(trailmap.long_doc[trail_type])
|
|
line = trailmap.lineon[trail_type]
|
|
path = trailmap.full_path[trail_type]
|
|
func_def = trailmap.func_def[trail_type]
|
|
source_code_url = (
|
|
"https://github.com/OpenBB-finance/OpenBBTerminal/tree/main/"
|
|
+ path
|
|
+ "#L"
|
|
+ str(line)
|
|
)
|
|
function_name = trailmap.view if trail_type == "view" else trailmap.model
|
|
params = []
|
|
for param in doc_parsed.params:
|
|
params.append(
|
|
{
|
|
"name": param.arg_name,
|
|
"doc": param.description,
|
|
"type": param.type_name,
|
|
"default": param.default,
|
|
"optional": param.is_optional,
|
|
}
|
|
)
|
|
if doc_parsed.returns:
|
|
returns = {
|
|
"doc": doc_parsed.returns.description,
|
|
"type": doc_parsed.returns.type_name,
|
|
}
|
|
else:
|
|
returns = None
|
|
|
|
examples = []
|
|
|
|
for example in doc_parsed.examples:
|
|
if example.description:
|
|
examples.append(
|
|
{"snippet": example.snippet, "description": example.description}
|
|
)
|
|
|
|
return {
|
|
"name": trailmap.class_attr,
|
|
"path": path,
|
|
"function_name": function_name,
|
|
"func_def": func_def,
|
|
"source_code_url": source_code_url,
|
|
"description": doc_parsed.short_description,
|
|
"params": params,
|
|
"returns": returns,
|
|
"examples": examples,
|
|
}
|
|
|
|
|
|
def generate_markdown(meta_model, meta_view):
|
|
main_model = meta_model
|
|
if not meta_model:
|
|
if not meta_view:
|
|
raise ValueError("No model or view")
|
|
main_model = meta_view
|
|
markdown = f"""---
|
|
title: {main_model["name"]}
|
|
description: OpenBB SDK Function
|
|
---\n\n"""
|
|
if meta_view and meta_model:
|
|
markdown += """import Tabs from '@theme/Tabs';
|
|
import TabItem from '@theme/TabItem';\n\n"""
|
|
|
|
markdown += f"# {main_model['name']}\n\n"
|
|
|
|
if meta_view and meta_model:
|
|
markdown += f"""<Tabs>
|
|
<TabItem value="model" label="Model" default>\n
|
|
{generate_markdown_section(meta_model)}\n
|
|
</TabItem>
|
|
<TabItem value="view" label="View">\n
|
|
{generate_markdown_section(meta_view)}\n
|
|
</TabItem>
|
|
</Tabs>"""
|
|
else:
|
|
markdown += generate_markdown_section(main_model)
|
|
return markdown
|
|
|
|
|
|
def generate_markdown_section(meta):
|
|
# head meta https://docusaurus.io/docs/markdown-features/head-metadata
|
|
# use real description but need to parse it
|
|
markdown = f"## {meta['function_name']}\n\n"
|
|
markdown += f"```python title='{meta['path']}'\n{meta['func_def']}\n```\n"
|
|
markdown += f"[Source Code]({meta['source_code_url']})\n\n"
|
|
markdown += f"Description: {meta['description']}\n\n"
|
|
|
|
markdown += "## Parameters\n\n"
|
|
if meta["params"]:
|
|
markdown += "| Name | Type | Description | Default | Optional |\n"
|
|
markdown += "| ---- | ---- | ----------- | ------- | -------- |\n"
|
|
for param in meta["params"]:
|
|
description = param["doc"].replace("\n", "<br/>")
|
|
markdown += f"| {param['name']} | {param['type']} | {description} | {param['default']} | {param['optional']} |\n" # noqa: E501
|
|
markdown += "\n"
|
|
else:
|
|
markdown += "This function does not take any parameters.\n\n"
|
|
|
|
markdown += "## Returns\n\n"
|
|
if meta["returns"]:
|
|
markdown += "| Type | Description |\n"
|
|
markdown += "| ---- | ----------- |\n"
|
|
return_desc = (
|
|
meta["returns"]["doc"].replace("\n", "<br/>")
|
|
if meta["returns"]["doc"]
|
|
else ""
|
|
)
|
|
markdown += f"| {meta['returns']['type']} | {return_desc} |\n\n"
|
|
else:
|
|
markdown += "This function does not return anything\n\n"
|
|
|
|
markdown += "## Examples\n\n"
|
|
for example in meta["examples"]:
|
|
markdown += f"{example['description']}\n"
|
|
|
|
if isinstance(example["snippet"], str):
|
|
snippet = example["snippet"].replace(">>> ", "")
|
|
markdown += f"```python\n{snippet}\n```\n\n"
|
|
|
|
return markdown
|
|
|
|
|
|
def main():
|
|
print("Loading trailmaps...")
|
|
trailmaps = get_trailmaps()
|
|
print("Generating markdown files...")
|
|
for trailmap in trailmaps:
|
|
model_meta = get_function_meta(trailmap, "model") if trailmap.model else None
|
|
view_meta = get_function_meta(trailmap, "view") if trailmap.view else None
|
|
markdown = generate_markdown(model_meta, view_meta)
|
|
|
|
if trailmap.class_attr == "index":
|
|
trailmap.class_attr = "index_cmd"
|
|
|
|
filepath = (
|
|
"functions/"
|
|
+ "/".join(trailmap.location_path)
|
|
+ "/"
|
|
+ trailmap.class_attr
|
|
+ ".md"
|
|
)
|
|
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
with open(filepath, "w", encoding="utf-8") as f:
|
|
f.write(markdown)
|
|
print("Markdown files generated, check the functions folder")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|