mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
[Feature] openbb-platform-api Better POST Params Discovery (#7204)
* better post params discovery * missing sentence in readme * nasdaq expected widgets * codespell * openapi_extra is expected to be not None by packagebuilder * lint * lint
This commit is contained in:
@@ -870,7 +870,7 @@ async def direction_of_trade(
|
||||
"h": 27,
|
||||
},
|
||||
"refetchInterval": False,
|
||||
"endpoint": "/api/v1/economy/fomc_documents/download",
|
||||
"endpoint": f"{api_prefix}/economy/fomc_documents/download",
|
||||
"params": [
|
||||
{
|
||||
"type": "endpoint",
|
||||
@@ -915,7 +915,7 @@ async def fomc_documents(
|
||||
"""
|
||||
results = await OBBject.from_query(Query(**locals()))
|
||||
|
||||
return results.results.content
|
||||
return results.results.content # type: ignore
|
||||
|
||||
|
||||
# This endpoint is used to download FOMC documents in Workspace.
|
||||
@@ -926,11 +926,7 @@ async def fomc_documents(
|
||||
@router._api_router.post(
|
||||
"/fomc_documents/download",
|
||||
include_in_schema=False,
|
||||
openapi_extra={
|
||||
"widget_config": {
|
||||
"exclude": True,
|
||||
}
|
||||
},
|
||||
openapi_extra={},
|
||||
)
|
||||
async def fomc_documents_download(params: Annotated[dict, Body()]) -> list:
|
||||
"""
|
||||
@@ -947,7 +943,7 @@ async def fomc_documents_download(params: Annotated[dict, Body()]) -> list:
|
||||
|
||||
urls = params.get("url", [])
|
||||
|
||||
results = []
|
||||
results: list = []
|
||||
for url in urls:
|
||||
try:
|
||||
response = make_request(url)
|
||||
|
||||
@@ -433,10 +433,12 @@ For example, the response to submitting a form can be a Markdown widget with a c
|
||||
|
||||
The entry in `widgets.json` will be automatically created if the conditions below are met:
|
||||
|
||||
- GET and POST methods must share the same API route.
|
||||
- GET request defines in top-level `widget_config`:
|
||||
- `{"form_endpoint": /path_to/form_post_endpoint}`
|
||||
- POST method takes 1 positional argument, a sub-class of Pydantic BaseModel.
|
||||
- Create a model, like annotated table fields, defining all inputs to the form.
|
||||
|
||||
|
||||
#### Example
|
||||
|
||||
The code below creates a widget with a form as the input, and an output table of all submitted forms, as processed through the `IntakeForm` model.
|
||||
@@ -447,15 +449,14 @@ from datetime import date as dateType
|
||||
from typing import Literal, Union
|
||||
|
||||
# from fastapi import FastAPI
|
||||
from openbb_platform_api.query_models import FormData
|
||||
from openbb_platform_api.response_models import Data
|
||||
from pydantic import ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
# app = FastAPI()
|
||||
|
||||
AccountTypes = Literal["General Fund", "Separately Managed", "Private Equity", "Family Office"]
|
||||
|
||||
class GeneralIntake(FormData):
|
||||
class GeneralIntake(BaseModel):
|
||||
"""Submit a form via POST request."""
|
||||
|
||||
date_created: dateType = Field(
|
||||
@@ -476,7 +477,11 @@ class GeneralIntake(FormData):
|
||||
submit: bool = Field(
|
||||
default=True,
|
||||
title="Submit",
|
||||
type="button", # This creates a button, when pressed the parameter is sent as True
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"type": "button",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -510,7 +515,7 @@ class IntakeForm(Data):
|
||||
INTAKE_FORMS: list[IntakeForm] = []
|
||||
|
||||
|
||||
@app.post("/general_intake")
|
||||
@app.post("/general_intake_submit")
|
||||
async def general_intake_post(data: GeneralIntake) -> bool:
|
||||
global INTAKE_FORMS
|
||||
try:
|
||||
@@ -520,7 +525,14 @@ async def general_intake_post(data: GeneralIntake) -> bool:
|
||||
raise e from e
|
||||
|
||||
|
||||
@app.get("/general_intake")
|
||||
@app.get(
|
||||
"/general_intake",
|
||||
openapi_extra= {
|
||||
"widget_config": {
|
||||
"form_endpoint": "/general_intake_submit",
|
||||
},
|
||||
},
|
||||
)
|
||||
async def general_intake() -> list[IntakeForm]:
|
||||
return INTAKE_FORMS
|
||||
```
|
||||
@@ -529,6 +541,12 @@ async def general_intake() -> list[IntakeForm]:
|
||||
|
||||
### Omni Widget Example
|
||||
|
||||
An Omni Widget is a POST request where all parameters are sent to the request body, along with the text input box (keyed as "prompt").
|
||||
|
||||
The returned type can be a list of records (table), a Plotly Figure, or formatted Markdwon.
|
||||
The model will attempt to assign the correct return type dynamically.
|
||||
|
||||
Set the response model as `OmniWidgetResponseModel`, then return `{"content": your_content}` from the endpoint.
|
||||
|
||||
```python
|
||||
from typing import Literal, Optional
|
||||
@@ -565,7 +583,7 @@ async def create_omni_widget(item: TestOmniWidgetQueryModel):
|
||||
if item.parse_as == "chart":
|
||||
some_test_data = {
|
||||
"data": [{"type": "bar", "x": ["A", "B", "C"], "y": [1, 2, 3]}],
|
||||
"layout": {"template": "...", "title": {"text": "Hello Chart!"}}
|
||||
"layout": {"template": "plotly_dark", "title": {"text": "Hello Chart!"}}
|
||||
}
|
||||
elif item.parse_as == "text":
|
||||
some_test_data = f"""
|
||||
|
||||
@@ -7,16 +7,6 @@ from pydantic import AliasGenerator, ConfigDict, Field, field_validator
|
||||
from pydantic.alias_generators import to_snake
|
||||
|
||||
|
||||
class FormData(Data):
|
||||
"""Submit a form via POST request."""
|
||||
|
||||
model_config = ConfigDict(
|
||||
extra="allow",
|
||||
alias_generator=AliasGenerator(to_snake),
|
||||
title="Submit Form",
|
||||
)
|
||||
|
||||
|
||||
class OmniWidgetInput(Data):
|
||||
"""Input for OmniWidget."""
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"""OpenAPI parsing Utils."""
|
||||
|
||||
# pylint: disable=C0302,R0912
|
||||
# flake8: noqa: PLR0912
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from openbb_core.provider.utils.helpers import to_snake_case
|
||||
@@ -937,39 +940,93 @@ def post_query_schema_for_widget(
|
||||
):
|
||||
# Get the reference to the schema for the request body.
|
||||
|
||||
param_ref = (
|
||||
schema["items"].get("$ref")
|
||||
if "items" in schema
|
||||
else schema.get("$ref") or schema
|
||||
)
|
||||
title = schema.get("title")
|
||||
providers: list[str] = []
|
||||
|
||||
if isinstance(param_ref, dict) and "type" in param_ref:
|
||||
param_ref = param_ref["type"]
|
||||
if title and title in schema:
|
||||
providers = [title]
|
||||
elif title and "," in title:
|
||||
providers = title.split(",")
|
||||
else:
|
||||
providers = ["Custom"]
|
||||
|
||||
if param_ref and isinstance(param_ref, str):
|
||||
# Extract the schema name from the reference
|
||||
schema_name = param_ref.split("/")[-1]
|
||||
schema = openapi_json["components"]["schemas"].get(schema_name, schema_name)
|
||||
props = {} if isinstance(schema, str) else schema.get("properties", {})
|
||||
|
||||
for k, v in props.items():
|
||||
if target_schema and target_schema != k:
|
||||
continue
|
||||
if nested_schema := v.get("$ref"):
|
||||
nested_schema_name = nested_schema.split("/")[-1]
|
||||
nested_schema = openapi_json["components"]["schemas"].get(
|
||||
nested_schema_name, {}
|
||||
)
|
||||
for nested_k, nested_v in nested_schema.get(
|
||||
"properties", {}
|
||||
).items():
|
||||
set_param(nested_k, nested_v)
|
||||
|
||||
else:
|
||||
if params := _route.get("parameters"):
|
||||
if isinstance(params, list):
|
||||
for _param in params:
|
||||
set_param(_param["name"], _param["schema"])
|
||||
elif isinstance(params, dict):
|
||||
for k, v in params.items():
|
||||
set_param(k, v)
|
||||
|
||||
route_params: list[dict] = []
|
||||
providers = ["custom"]
|
||||
if "items" in schema or "$ref" in schema:
|
||||
param_ref = (
|
||||
schema["items"].get("$ref")
|
||||
if "items" in schema
|
||||
else schema.get("$ref") or schema
|
||||
)
|
||||
|
||||
if isinstance(param_ref, dict) and "type" in param_ref:
|
||||
param_ref = param_ref["type"]
|
||||
|
||||
if param_ref and isinstance(param_ref, str):
|
||||
# Extract the schema name from the reference
|
||||
schema_name = param_ref.split("/")[-1]
|
||||
schema = openapi_json["components"]["schemas"].get(
|
||||
schema_name, schema_name
|
||||
)
|
||||
props = {} if isinstance(schema, str) else schema.get("properties", {})
|
||||
|
||||
for k, v in props.items():
|
||||
if target_schema and target_schema != k:
|
||||
continue
|
||||
if nested_schema := v.get("$ref"):
|
||||
nested_schema_name = nested_schema.split("/")[-1]
|
||||
nested_schema = openapi_json["components"]["schemas"].get(
|
||||
nested_schema_name, {}
|
||||
)
|
||||
for nested_k, nested_v in nested_schema.get(
|
||||
"properties", {}
|
||||
).items():
|
||||
set_param(nested_k, nested_v)
|
||||
|
||||
else:
|
||||
set_param(k, v)
|
||||
|
||||
route_params: list[dict] = []
|
||||
|
||||
for new_param_values in new_params.values():
|
||||
_new_values = new_param_values.copy()
|
||||
p = process_parameter(_new_values, providers)
|
||||
if not p.get("exclude") and not p.get("x-widget_config", {}).get(
|
||||
"exclude"
|
||||
):
|
||||
route_params.append(p)
|
||||
|
||||
return route_params
|
||||
if "anyOf" in _route or "anyOf" in schema:
|
||||
any_of_schema = (
|
||||
schema.get("anyOf", [])
|
||||
if "anyOf" in schema
|
||||
else _route.get("anyOf", [])
|
||||
)
|
||||
for item in any_of_schema:
|
||||
# If item is a $ref, resolve it
|
||||
if "$ref" in item:
|
||||
ref_name = item["$ref"].split("/")[-1]
|
||||
ref_schema = openapi_json["components"]["schemas"].get(ref_name, {})
|
||||
if "properties" in ref_schema:
|
||||
for k, v in ref_schema["properties"].items():
|
||||
if target_schema and target_schema != k:
|
||||
continue
|
||||
set_param(k, v)
|
||||
# If item has properties directly
|
||||
elif "properties" in item:
|
||||
for k, v in item["properties"].items():
|
||||
if target_schema and target_schema != k:
|
||||
continue
|
||||
set_param(k, v)
|
||||
|
||||
route_params = []
|
||||
|
||||
for new_param_values in new_params.values():
|
||||
_new_values = new_param_values.copy()
|
||||
|
||||
@@ -239,6 +239,16 @@ def modify_query_schema(query_schema: list[dict], provider_value: str):
|
||||
return modified_query_schema
|
||||
|
||||
|
||||
def get_form_input_paths(openapi: dict) -> dict:
|
||||
"""Get a mapping of form input paths, defined by 'widget_config.form_endpoint'."""
|
||||
return {
|
||||
path: config.get("form_endpoint")
|
||||
for path, path_config in openapi["paths"].items()
|
||||
if (config := path_config.get("get", {}).get("widget_config"))
|
||||
and config.get("form_endpoint")
|
||||
}
|
||||
|
||||
|
||||
def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-locals, too-many-statements
|
||||
openapi: dict, widget_exclude_filter: list
|
||||
):
|
||||
@@ -261,6 +271,8 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
starred_list.append(item)
|
||||
widget_exclude_filter.remove(item)
|
||||
|
||||
# Collect all routes that are designated as form endpoints to exclude them from direct widget generation
|
||||
form_endpoint_paths = get_form_input_paths(openapi)
|
||||
widgets_json: dict = {}
|
||||
routes = [
|
||||
p
|
||||
@@ -269,13 +281,20 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
and ("get" in openapi["paths"][p] or "post" in openapi["paths"][p])
|
||||
]
|
||||
for route in routes:
|
||||
has_form_endpoint = False
|
||||
form_route: dict = {}
|
||||
if "post" in openapi["paths"][route] and "get" in openapi["paths"][route]:
|
||||
has_form_endpoint = True
|
||||
form_route = openapi["paths"][route]["post"]
|
||||
elif "post" in openapi["paths"][route]:
|
||||
form_route = openapi["paths"][route]["post"]
|
||||
# Skip routes that are only used as form endpoints for other routes
|
||||
if route in form_endpoint_paths.values():
|
||||
continue
|
||||
|
||||
route_api = openapi["paths"][route]
|
||||
|
||||
has_form_endpoint = route in list(form_endpoint_paths)
|
||||
form_endpoint_path = form_endpoint_paths.get(route) if has_form_endpoint else ""
|
||||
form_route: dict = (
|
||||
openapi["paths"][form_endpoint_path]["post"] if has_form_endpoint else {}
|
||||
)
|
||||
# Determine the primary method for the widget
|
||||
# If a GET exists, it's the primary. Otherwise, it's a POST.
|
||||
route_method = "get" if "get" in route_api else "post"
|
||||
|
||||
skip = False
|
||||
for starred in starred_list:
|
||||
@@ -292,10 +311,6 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
if skip is True:
|
||||
continue
|
||||
|
||||
route_api = openapi["paths"][route]
|
||||
route_method = (
|
||||
"post" if "post" in route_api and not has_form_endpoint else "get"
|
||||
)
|
||||
widget_id = (
|
||||
(
|
||||
route[1:].replace("/", "_")
|
||||
@@ -318,7 +333,7 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
# Prepare the query schema of the widget
|
||||
if route_method == "get":
|
||||
query_schema, has_chart = get_query_schema_for_widget(openapi, route)
|
||||
else:
|
||||
else: # post
|
||||
query_schema = (
|
||||
post_query_schema_for_widget(
|
||||
openapi, route_api.get("post", {}).get("operationId", ""), route
|
||||
@@ -349,7 +364,7 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
columns_defs = (
|
||||
data_schema_to_columns_defs(openapi, widget_id, provider, route)
|
||||
if widget_config_dict.get("type")
|
||||
not in ["multi_file_viewer", "pdf", "metric", "omni"]
|
||||
not in ["multi_file_viewer", "pdf", "metric"]
|
||||
else []
|
||||
)
|
||||
_cats = [
|
||||
@@ -383,121 +398,182 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
|
||||
param_names: list = []
|
||||
var_schema: dict = {}
|
||||
if (
|
||||
_schema := route_api.get("post", {})
|
||||
.get("requestBody", {})
|
||||
.get("content", {})
|
||||
.get("application/json", {})
|
||||
.get("schema", {})
|
||||
):
|
||||
schema_name = _schema.get("$ref", "").split("/")[-1]
|
||||
var_schema = openapi["components"]["schemas"].get(schema_name, {})
|
||||
|
||||
if var_schema:
|
||||
var_props = var_schema.get("properties", {})
|
||||
for k, v in var_props.items():
|
||||
if "$ref" in v:
|
||||
param_names.append(k)
|
||||
# Determine the source of the POST body schema
|
||||
post_body_source = None
|
||||
if has_form_endpoint:
|
||||
post_body_source = form_route
|
||||
elif route_method == "post":
|
||||
post_body_source = route_api.get("post")
|
||||
|
||||
if param_names:
|
||||
for _param in param_names:
|
||||
if post_body_source:
|
||||
if (
|
||||
_schema := post_body_source.get("requestBody", {})
|
||||
.get("content", {})
|
||||
.get("application/json", {})
|
||||
.get("schema", {})
|
||||
):
|
||||
schema_name = _schema.get("$ref", "").split("/")[-1]
|
||||
var_schema = openapi["components"]["schemas"].get(schema_name, {})
|
||||
|
||||
if var_schema:
|
||||
var_props = var_schema.get("properties", {})
|
||||
for k, v in var_props.items():
|
||||
if "$ref" in v:
|
||||
param_names.append(k)
|
||||
|
||||
if param_names:
|
||||
for _param in param_names:
|
||||
post_params = post_query_schema_for_widget(
|
||||
openapi, post_body_source.get("operationId"), route, _param
|
||||
)
|
||||
modified_post_params = modify_query_schema(
|
||||
post_params, provider # type: ignore
|
||||
)
|
||||
|
||||
if has_form_endpoint:
|
||||
has_submit = False
|
||||
for item in modified_post_params:
|
||||
if item.get("type") == "button":
|
||||
has_submit = True
|
||||
break
|
||||
|
||||
if not has_submit:
|
||||
modified_post_params.append(
|
||||
{
|
||||
"paramName": "submit",
|
||||
"label": "Submit",
|
||||
"type": "button",
|
||||
"value": True,
|
||||
"description": "Submit the form.",
|
||||
}
|
||||
)
|
||||
|
||||
form_params = {
|
||||
"type": "form",
|
||||
"paramName": _param,
|
||||
"label": "Form",
|
||||
"description": "Form Data",
|
||||
"endpoint": form_endpoint_path,
|
||||
"inputParams": modified_post_params,
|
||||
}
|
||||
|
||||
if post_config := var_schema.get("x-widget_config", {}):
|
||||
form_params = deep_merge_configs(
|
||||
form_params,
|
||||
post_config,
|
||||
)
|
||||
|
||||
modified_query_schema.append(form_params)
|
||||
else:
|
||||
# For non-form endpoints, extend with the modified params directly
|
||||
modified_query_schema.extend(modified_post_params)
|
||||
else: # This handles POST requests with no parameters in the body
|
||||
post_params = post_query_schema_for_widget(
|
||||
openapi, form_route.get("operationId"), route, _param
|
||||
openapi,
|
||||
post_body_source.get("operationId"),
|
||||
form_endpoint_path if has_form_endpoint else route,
|
||||
)
|
||||
modified_post_params = modify_query_schema(
|
||||
post_params, provider # type: ignore
|
||||
)
|
||||
|
||||
if has_form_endpoint:
|
||||
|
||||
has_submit = False
|
||||
for item in modified_post_params:
|
||||
if item.get("type") == "button":
|
||||
has_submit = True
|
||||
break
|
||||
|
||||
if not has_submit:
|
||||
modified_post_params.append(
|
||||
{
|
||||
"paramName": "submit",
|
||||
"label": "Submit",
|
||||
"value": True,
|
||||
"type": "button",
|
||||
"description": "Submit the form.",
|
||||
}
|
||||
)
|
||||
|
||||
form_params = {
|
||||
"type": "form",
|
||||
"paramName": _param,
|
||||
"label": "Form",
|
||||
"description": "Form Data",
|
||||
"endpoint": (
|
||||
route.replace("/api", "api")
|
||||
if "/api" in route
|
||||
else route[1:] if route[0] == "/" else route
|
||||
),
|
||||
"paramName": "form",
|
||||
"label": var_schema.get("title", "Form"),
|
||||
"description": var_schema.get("description", ""),
|
||||
"endpoint": form_endpoint_path,
|
||||
"inputParams": modified_post_params,
|
||||
}
|
||||
|
||||
var_key: dict = {}
|
||||
# Widget Config at the model level goes first.
|
||||
if post_config := var_schema.get("x-widget_config", {}):
|
||||
for key, value in post_config.copy().items():
|
||||
if key.startswith("$."):
|
||||
var_key[key] = value
|
||||
else:
|
||||
form_params[key] = value
|
||||
|
||||
form_params = deep_merge_configs(
|
||||
form_params,
|
||||
post_config,
|
||||
)
|
||||
|
||||
# Then the widget config at the POST endpoint level takes priority.
|
||||
if post_config := form_route.get("widget_config", {}):
|
||||
for key, value in post_config.copy().items():
|
||||
if key.startswith("$."):
|
||||
var_key[key] = value
|
||||
|
||||
form_params = deep_merge_configs(
|
||||
form_params,
|
||||
{
|
||||
k: v
|
||||
for k, v in post_config.items()
|
||||
if not k.startswith("$.")
|
||||
},
|
||||
)
|
||||
|
||||
modified_query_schema.append(form_params)
|
||||
else:
|
||||
# For non-form endpoints, extend with the modified params directly
|
||||
modified_query_schema.extend(modified_post_params)
|
||||
else:
|
||||
post_params = post_query_schema_for_widget(
|
||||
openapi, form_route.get("operationId"), route
|
||||
)
|
||||
modified_post_params = modify_query_schema(post_params, provider) # type: ignore
|
||||
|
||||
if has_form_endpoint:
|
||||
form_params = {
|
||||
"type": "form",
|
||||
"paramName": "",
|
||||
"label": var_schema.get("title", "Form"),
|
||||
"description": var_schema.get("description", ""),
|
||||
"endpoint": (
|
||||
route.replace("/api", "api")
|
||||
if "/api" in route
|
||||
else route[1:] if route[0] == "/" else route
|
||||
),
|
||||
"inputParams": modified_post_params,
|
||||
}
|
||||
if var_key:
|
||||
for key, value in var_key.items():
|
||||
if (
|
||||
key.replace("$.", "") in widget_config_dict
|
||||
and "params" not in key
|
||||
and "inputParams" not in key
|
||||
):
|
||||
widget_config_dict.update(
|
||||
{key.replace("$.", ""): value}
|
||||
)
|
||||
else:
|
||||
widget_config_dict[key.replace("$.", "")] = value
|
||||
# For POST-only routes, extend with the modified params directly
|
||||
# elif route_method == "post":
|
||||
# modified_query_schema.extend(modified_post_params)
|
||||
elif route_method == "post":
|
||||
|
||||
var_key: dict = {}
|
||||
# Widget Config at the model level goes first.
|
||||
if post_config := var_schema.get("x-widget_config", {}):
|
||||
for key, value in post_config.copy().items():
|
||||
if key.startswith("$."):
|
||||
var_key[key] = value
|
||||
else:
|
||||
form_params[key] = value
|
||||
var_key = {}
|
||||
# Widget Config at the model level goes first.
|
||||
if post_config := var_schema.get("x-widget_config", {}):
|
||||
for key, value in post_config.copy().items():
|
||||
if key.startswith("$."):
|
||||
var_key[key] = value
|
||||
|
||||
form_params = deep_merge_configs(
|
||||
form_params,
|
||||
post_config,
|
||||
)
|
||||
if var_key:
|
||||
for key, value in var_key.items():
|
||||
if (
|
||||
key.replace("$.", "") in widget_config_dict
|
||||
and "params" not in key
|
||||
and "inputParams" not in key
|
||||
):
|
||||
widget_config_dict.update(
|
||||
{key.replace("$.", ""): value}
|
||||
)
|
||||
else:
|
||||
widget_config_dict[key.replace("$.", "")] = value
|
||||
|
||||
# Then the widget config at the POST endpoint level takes priority.
|
||||
if post_config := form_route.get("widget_config", {}):
|
||||
for key, value in post_config.copy().items():
|
||||
if key.startswith("$."):
|
||||
var_key[key] = value
|
||||
|
||||
form_params = deep_merge_configs(
|
||||
form_params,
|
||||
{
|
||||
k: v
|
||||
for k, v in post_config.items()
|
||||
if not k.startswith("$.")
|
||||
},
|
||||
)
|
||||
|
||||
modified_query_schema.append(form_params)
|
||||
|
||||
if var_key:
|
||||
for key, value in var_key.items():
|
||||
if (
|
||||
key.replace("$.", "") in widget_config_dict
|
||||
and "params" not in key
|
||||
and "inputParams" not in key
|
||||
):
|
||||
widget_config_dict.update(
|
||||
{key.replace("$.", ""): value}
|
||||
)
|
||||
else:
|
||||
widget_config_dict[key.replace("$.", "")] = value
|
||||
# For non-form endpoints, extend with the modified params directly
|
||||
# else:
|
||||
# modified_query_schema.extend(modified_post_params)
|
||||
provider_map = {
|
||||
"tmx": "TMX",
|
||||
"ecb": "ECB",
|
||||
@@ -528,14 +604,10 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
else ""
|
||||
)
|
||||
widget_type = (
|
||||
(
|
||||
"markdown"
|
||||
if isinstance(response_schema, dict)
|
||||
and response_schema.get("type") == "string"
|
||||
else "table"
|
||||
)
|
||||
if route_method == "get"
|
||||
else "omni"
|
||||
"markdown"
|
||||
if isinstance(response_schema, dict)
|
||||
and response_schema.get("type") == "string"
|
||||
else "table"
|
||||
)
|
||||
widget_config = {
|
||||
"name": f"{name}" if name else route_api[route_method].get("summary"),
|
||||
@@ -545,11 +617,7 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
"searchCategory": category.replace("_", " ").title(),
|
||||
"widgetId": f"{widget_id}_{provider}_obb",
|
||||
"params": modified_query_schema,
|
||||
"endpoint": (
|
||||
route.replace("/api", "api")
|
||||
if "/api" in route
|
||||
else route[1:] if route[0] == "/" else route
|
||||
),
|
||||
"endpoint": route,
|
||||
"runButton": False,
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
"data": {
|
||||
@@ -609,9 +677,9 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
widget_config,
|
||||
widget_config_dict,
|
||||
)
|
||||
# TODO: uncomment when charting is implemented for advanced tables.
|
||||
# if widget_config.get("type") == "table":
|
||||
# widget_config["data"]["table"]["enableAdvanced"] = True
|
||||
|
||||
if widget_config.get("type") == "table":
|
||||
widget_config["data"]["table"]["enableAdvanced"] = True
|
||||
|
||||
if widget_config.get("type") == "metric":
|
||||
widget_config["gridData"]["w"] = (
|
||||
@@ -640,6 +708,16 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-
|
||||
else widget_config["gridData"].get("h")
|
||||
)
|
||||
|
||||
if source := widget_config_dict.get("source", []):
|
||||
widget_config["source"] = source
|
||||
|
||||
if route_method == "post" and widget_config.get("type", "") not in [
|
||||
"ssrm_table",
|
||||
"omni",
|
||||
"multi_file_viewer",
|
||||
]:
|
||||
widget_exclude_filter.append(widget_config["widgetId"])
|
||||
|
||||
# Add the widget configuration to the widgets.json
|
||||
if widget_config["widgetId"] not in widget_exclude_filter:
|
||||
widgets_json[widget_config["widgetId"]] = widget_config
|
||||
|
||||
@@ -3538,10 +3538,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/form_widget": {
|
||||
"get": {
|
||||
"summary": "All Forms",
|
||||
"operationId": "all_forms_form_submit_get",
|
||||
"widget_config": {
|
||||
"form_endpoint": "/form_submit"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "some_param",
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/sloos",
|
||||
"endpoint": "/api/v1/economy/survey/sloos",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -167,7 +167,8 @@
|
||||
"headerTooltip": "Survey title.",
|
||||
"cellDataType": "text"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -292,7 +293,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/university_of_michigan",
|
||||
"endpoint": "/api/v1/economy/survey/university_of_michigan",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -326,7 +327,8 @@
|
||||
"cellDataType": "number",
|
||||
"renderFn": "greenRed"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -451,7 +453,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/economic_conditions_chicago",
|
||||
"endpoint": "/api/v1/economy/survey/economic_conditions_chicago",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -533,7 +535,8 @@
|
||||
"headerTooltip": "Non-Labor Costs Index.",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -726,7 +729,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/manufacturing_outlook_texas",
|
||||
"endpoint": "/api/v1/economy/survey/manufacturing_outlook_texas",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -783,7 +786,8 @@
|
||||
"cellDataType": "number",
|
||||
"renderFn": "greenRed"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -898,7 +902,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/nonfarm_payrolls",
|
||||
"endpoint": "/api/v1/economy/survey/nonfarm_payrolls",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -972,7 +976,8 @@
|
||||
"headerTooltip": "The indentation level of the element.",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -1275,7 +1280,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/cpi",
|
||||
"endpoint": "/api/v1/economy/cpi",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -1308,7 +1313,8 @@
|
||||
"headerTooltip": "CPI index value or period change.",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -1551,7 +1557,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/balance_of_payments",
|
||||
"endpoint": "/api/v1/economy/balance_of_payments",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -1714,7 +1720,8 @@
|
||||
"headerTooltip": "Current Account Debits Total Secondary Income (USD)",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -1842,7 +1849,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_search",
|
||||
"endpoint": "/api/v1/economy/fred_search",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -1995,7 +2002,8 @@
|
||||
"headerTooltip": "The series group ID of the series. This value is used to query for regional data.",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -2191,7 +2199,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_series",
|
||||
"endpoint": "/api/v1/economy/fred_series",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -2210,7 +2218,8 @@
|
||||
"headerTooltip": "The date of the data.",
|
||||
"cellDataType": "date"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -2415,7 +2424,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_series",
|
||||
"endpoint": "/api/v1/economy/fred_series",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -2434,7 +2443,8 @@
|
||||
"headerTooltip": "The date of the data.",
|
||||
"cellDataType": "date"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -2489,7 +2499,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_release_table",
|
||||
"endpoint": "/api/v1/economy/fred_release_table",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -2577,7 +2587,8 @@
|
||||
"headerTooltip": "The reported value of the series.",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -2852,7 +2863,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_regional",
|
||||
"endpoint": "/api/v1/economy/fred_regional",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -2900,7 +2911,8 @@
|
||||
"headerTooltip": "The individual series ID for the region.",
|
||||
"cellDataType": "text"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3055,7 +3067,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/retail_prices",
|
||||
"endpoint": "/api/v1/economy/retail_prices",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3104,7 +3116,8 @@
|
||||
"headerTooltip": "Price, or change in price, per unit.",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3183,7 +3196,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/pce",
|
||||
"endpoint": "/api/v1/economy/pce",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3264,7 +3277,8 @@
|
||||
"headerTooltip": "The line number of the series in the table.",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3304,7 +3318,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/cik_map",
|
||||
"endpoint": "/api/v1/regulators/sec/cik_map",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3323,7 +3337,8 @@
|
||||
"cellDataType": "text",
|
||||
"renderFn": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3363,7 +3378,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/institutions_search",
|
||||
"endpoint": "/api/v1/regulators/sec/institutions_search",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3388,7 +3403,8 @@
|
||||
"headerTooltip": "Central Index Key (CIK)",
|
||||
"cellDataType": "number"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3437,7 +3453,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/schema_files",
|
||||
"endpoint": "/api/v1/regulators/sec/schema_files",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3455,7 +3471,8 @@
|
||||
"headerTooltip": "Dictionary of URLs to SEC Schema Files",
|
||||
"cellDataType": "text"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3495,7 +3512,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/symbol_map",
|
||||
"endpoint": "/api/v1/regulators/sec/symbol_map",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3515,7 +3532,8 @@
|
||||
"cellDataType": "text",
|
||||
"renderFn": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3537,7 +3555,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/rss_litigation",
|
||||
"endpoint": "/api/v1/regulators/sec/rss_litigation",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3584,7 +3602,8 @@
|
||||
"headerTooltip": "URL to the release.",
|
||||
"cellDataType": "text"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3624,7 +3643,7 @@
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/sic_search",
|
||||
"endpoint": "/api/v1/regulators/sec/sic_search",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3656,7 +3675,8 @@
|
||||
"headerTooltip": "Reporting office within the Corporate Finance Office",
|
||||
"cellDataType": "text"
|
||||
}
|
||||
]
|
||||
],
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
@@ -3664,13 +3684,13 @@
|
||||
],
|
||||
"subCategory": "SEC"
|
||||
},
|
||||
"form_submit_custom_obb": {
|
||||
"name": "Form Submit",
|
||||
"form_widget_custom_obb": {
|
||||
"name": "Form Widget",
|
||||
"description": "",
|
||||
"category": "Form Submit",
|
||||
"category": "Form Widget",
|
||||
"type": "table",
|
||||
"searchCategory": "Form Submit",
|
||||
"widgetId": "form_submit_custom_obb",
|
||||
"searchCategory": "Form Widget",
|
||||
"widgetId": "form_widget_custom_obb",
|
||||
"params": [
|
||||
{
|
||||
"label": "Some Param",
|
||||
@@ -3683,10 +3703,10 @@
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"paramName": "",
|
||||
"paramName": "form",
|
||||
"label": "Submit Form",
|
||||
"description": "Submit a form via POST request.",
|
||||
"endpoint": "form_submit",
|
||||
"endpoint": "/form_submit",
|
||||
"inputParams": [
|
||||
{
|
||||
"label": "Date",
|
||||
@@ -3762,7 +3782,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"endpoint": "form_submit",
|
||||
"endpoint": "/form_widget",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -3771,11 +3791,13 @@
|
||||
"data": {
|
||||
"dataKey": "",
|
||||
"table": {
|
||||
"showAll": true
|
||||
"showAll": true,
|
||||
"enableAdvanced": true
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"Custom"
|
||||
]
|
||||
],
|
||||
"form_endpoint": "/form_submit"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
"""Test OpenAPI Utils."""
|
||||
|
||||
# pylint: disable=redefined-outer-name,line-too-long
|
||||
# flake8: noqa: E501
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
@@ -9,8 +14,6 @@ from openbb_platform_api.utils.openapi import (
|
||||
post_query_schema_for_widget,
|
||||
)
|
||||
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
# Load the mock OpenAPI JSON
|
||||
@pytest.fixture(scope="module")
|
||||
@@ -100,7 +103,6 @@ def mock_openapi_json():
|
||||
"x-widget_config": {},
|
||||
"available_providers": ["fred"],
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "start_date",
|
||||
@@ -114,7 +116,6 @@ def mock_openapi_json():
|
||||
"x-widget_config": {},
|
||||
"available_providers": ["fred"],
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "end_date",
|
||||
@@ -128,7 +129,6 @@ def mock_openapi_json():
|
||||
"x-widget_config": {},
|
||||
"available_providers": ["fred"],
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
],
|
||||
False,
|
||||
@@ -170,7 +170,6 @@ def mock_openapi_json():
|
||||
"options": {"fred": []},
|
||||
"x-widget_config": {},
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "end_date",
|
||||
@@ -183,7 +182,6 @@ def mock_openapi_json():
|
||||
"options": {"fred": []},
|
||||
"x-widget_config": {},
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "limit",
|
||||
@@ -196,7 +194,6 @@ def mock_openapi_json():
|
||||
"options": {"fred": []},
|
||||
"x-widget_config": {},
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "frequency",
|
||||
@@ -227,12 +224,13 @@ def mock_openapi_json():
|
||||
"x-widget_config": {},
|
||||
"available_providers": ["fred"],
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "aggregation_method",
|
||||
"label": "Aggregation Method",
|
||||
"description": "A key that indicates the aggregation method used for frequency aggregation.\n This parameter has no affect if the frequency parameter is not set.\n \n avg = Average\n \n sum = Sum\n \n eop = End of Period",
|
||||
"description": "A key that indicates the aggregation method used for frequency aggregation.\n"
|
||||
+ " This parameter has no affect if the frequency parameter is not set.\n"
|
||||
+ " \n avg = Average\n \n sum = Sum\n \n eop = End of Period",
|
||||
"optional": True,
|
||||
"type": "text",
|
||||
"value": "eop",
|
||||
@@ -247,7 +245,6 @@ def mock_openapi_json():
|
||||
"x-widget_config": {},
|
||||
"available_providers": ["fred"],
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "transform",
|
||||
@@ -272,7 +269,6 @@ def mock_openapi_json():
|
||||
"x-widget_config": {},
|
||||
"available_providers": ["fred"],
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
],
|
||||
True,
|
||||
@@ -302,7 +298,6 @@ def mock_openapi_json():
|
||||
"options": {"sec": []},
|
||||
"x-widget_config": {},
|
||||
"show": True,
|
||||
"x-widget_config": {},
|
||||
},
|
||||
{
|
||||
"parameter_name": "use_cache",
|
||||
@@ -327,7 +322,6 @@ def mock_openapi_json():
|
||||
"options": {"sec": []},
|
||||
"x-widget_config": {},
|
||||
"available_providers": ["sec"],
|
||||
"x-widget_config": {},
|
||||
"show": True,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -498,9 +498,9 @@ def test_modify_query_schema(query_schema, provider_value, expected_result):
|
||||
|
||||
def test_build_json(mock_openapi_json, mock_widgets_json):
|
||||
result = build_json(openapi=mock_openapi_json, widget_exclude_filter=[])
|
||||
assert (
|
||||
len(result) == len(mock_openapi_json["paths"]) + 1
|
||||
) # +1 for the duplicate path with a chart.
|
||||
assert len(result) == len(
|
||||
mock_openapi_json["paths"]
|
||||
) # +1 for the duplicate path with a chart. -1 for the form widget over 2 paths.
|
||||
assert result == mock_widgets_json
|
||||
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"endpoint": "open_document",
|
||||
"endpoint": "/open_document",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -264,7 +264,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"endpoint": "earnings_calendar",
|
||||
"endpoint": "/earnings_calendar",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -414,7 +414,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"endpoint": "dividend_calendar",
|
||||
"endpoint": "/dividend_calendar",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -538,7 +538,7 @@
|
||||
"paramName": "end_date"
|
||||
}
|
||||
],
|
||||
"endpoint": "historical_dividends",
|
||||
"endpoint": "/historical_dividends",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -548,6 +548,7 @@
|
||||
"dataKey": "",
|
||||
"table": {
|
||||
"showAll": true,
|
||||
"enableAdvanced": true,
|
||||
"columnsDefs": [
|
||||
{
|
||||
"field": "ex_dividend_date",
|
||||
@@ -679,7 +680,7 @@
|
||||
"paramName": "is_spo"
|
||||
}
|
||||
],
|
||||
"endpoint": "ipo_calendar",
|
||||
"endpoint": "/ipo_calendar",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
@@ -888,7 +889,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"endpoint": "economic_calendar",
|
||||
"endpoint": "/economic_calendar",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 40,
|
||||
|
||||
Reference in New Issue
Block a user