mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
fix merge widgets
This commit is contained in:
@@ -47,67 +47,38 @@ async def get_additional_widgets(app: FastAPI) -> dict:
|
||||
|
||||
|
||||
def fix_router_widgets(path, widgets):
|
||||
"""Append the API prefix and path to the function, if necessary."""
|
||||
"""Restore the full API route path on every router-attached widget."""
|
||||
updated_widgets: dict = {}
|
||||
|
||||
def _restore(value: str) -> str:
|
||||
if not value or "://" in value or value.startswith(path):
|
||||
return value
|
||||
return path + value.lstrip("/")
|
||||
|
||||
for widget_id, widget in widgets.items():
|
||||
if not isinstance(widget, dict) or widget_id.endswith("/widgets.json"):
|
||||
continue
|
||||
|
||||
new_widget: dict = widget.copy()
|
||||
params = widget.get("params", [])
|
||||
|
||||
if (endpoint := widget.get("endpoint", "")) and not endpoint.startswith(path):
|
||||
new_widget["endpoint"] = (
|
||||
path + endpoint[1:] if endpoint.startswith("/") else endpoint
|
||||
)
|
||||
|
||||
if (
|
||||
(ws_endpoint := widget.get("wsEndpoint", ""))
|
||||
and "://" not in ws_endpoint
|
||||
and not ws_endpoint.startswith(path)
|
||||
):
|
||||
new_widget["wsEndpoint"] = (
|
||||
path + ws_endpoint[1:] if ws_endpoint.startswith("/") else ws_endpoint
|
||||
)
|
||||
|
||||
if (
|
||||
(img_url := widget.get("imgUrl", ""))
|
||||
and "://" not in img_url
|
||||
and not img_url.startswith(path)
|
||||
):
|
||||
new_widget["imgUrl"] = (
|
||||
path + img_url[1:] if img_url.startswith("/") else img_url
|
||||
)
|
||||
if endpoint := widget.get("endpoint", ""):
|
||||
new_widget["endpoint"] = _restore(endpoint)
|
||||
if ws_endpoint := widget.get("wsEndpoint", ""):
|
||||
new_widget["wsEndpoint"] = _restore(ws_endpoint)
|
||||
if img_url := widget.get("imgUrl", ""):
|
||||
new_widget["imgUrl"] = _restore(img_url)
|
||||
|
||||
new_params: list = []
|
||||
|
||||
for param in params:
|
||||
for param in widget.get("params", []):
|
||||
new_param: dict = param.copy()
|
||||
|
||||
if (
|
||||
(endpoint := param.get("endpoint", ""))
|
||||
and "://" not in endpoint
|
||||
and not endpoint.startswith(path)
|
||||
):
|
||||
new_param["endpoint"] = (
|
||||
path + endpoint[1:] if endpoint.startswith("/") else endpoint
|
||||
)
|
||||
|
||||
if (
|
||||
(opt_endpoint := param.get("optionsEndpoint", ""))
|
||||
and "://" not in opt_endpoint
|
||||
and not opt_endpoint.startswith(path)
|
||||
):
|
||||
new_param["optionsEndpoint"] = (
|
||||
path + opt_endpoint[1:]
|
||||
if opt_endpoint.startswith("/")
|
||||
else opt_endpoint
|
||||
)
|
||||
|
||||
if endpoint := param.get("endpoint", ""):
|
||||
new_param["endpoint"] = _restore(endpoint)
|
||||
if opt_endpoint := param.get("optionsEndpoint", ""):
|
||||
new_param["optionsEndpoint"] = _restore(opt_endpoint)
|
||||
new_params.append(new_param)
|
||||
|
||||
new_widget["params"] = new_params
|
||||
updated_widgets[new_widget.get("widgetId", new_widget["endpoint"])] = new_widget
|
||||
updated_widgets[new_widget.get("widgetId", widget_id)] = new_widget
|
||||
|
||||
return updated_widgets
|
||||
|
||||
|
||||
@@ -186,6 +186,80 @@ def test_fix_router_widgets_updates_nested_paths_without_mutating_source():
|
||||
assert widget["params"][1]["endpoint"] == "http://external/api"
|
||||
|
||||
|
||||
def test_fix_router_widgets_prefixes_bare_relative_endpoints():
|
||||
"""Bare (non-slash) endpoints from a standalone router get the full path.
|
||||
|
||||
Per-district routers serve their widgets with bare endpoints so the router
|
||||
can be added to the Workspace as a standalone backend; merging them into the
|
||||
root widgets.json must restore the full route path for those too, not only
|
||||
``/``-prefixed endpoints.
|
||||
"""
|
||||
widgets = {
|
||||
"kc_natural_rate": {
|
||||
"widgetId": "kc_natural_rate",
|
||||
"endpoint": "natural_rate",
|
||||
"params": [{"name": "report", "optionsEndpoint": "natural_rate_choices"}],
|
||||
}
|
||||
}
|
||||
updated = fix_router_widgets("/federal_reserve/kc/", widgets)
|
||||
assert list(updated.keys()) == ["kc_natural_rate"]
|
||||
widget = updated["kc_natural_rate"]
|
||||
assert widget["endpoint"] == "/federal_reserve/kc/natural_rate"
|
||||
assert (
|
||||
widget["params"][0]["optionsEndpoint"]
|
||||
== "/federal_reserve/kc/natural_rate_choices"
|
||||
)
|
||||
|
||||
|
||||
def test_fix_router_widgets_falls_back_to_source_key_without_widget_id():
|
||||
"""A widget without a widgetId keeps its source key rather than the endpoint."""
|
||||
widgets = {"custom_report": {"endpoint": "report"}}
|
||||
updated = fix_router_widgets("/federal_reserve/sf/", widgets)
|
||||
assert list(updated.keys()) == ["custom_report"]
|
||||
assert updated["custom_report"]["endpoint"] == "/federal_reserve/sf/report"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_and_fix_widget_paths_carries_configured_api_prefix():
|
||||
"""A router mounted under an API prefix yields prefixed merged endpoints.
|
||||
|
||||
The merge restores the *full* route path discovered from the app, so whatever
|
||||
prefix the platform is mounted under (e.g. ``/api/v1`` or whatever
|
||||
``api_settings.prefix`` dictates) flows into every merged endpoint and options
|
||||
endpoint. The router's own widgets.json stays relative (bare) so it still
|
||||
works as a standalone backend.
|
||||
"""
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/widgets.json")
|
||||
async def root_widgets():
|
||||
return {"root": {"widgetId": "root", "endpoint": "/root"}}
|
||||
|
||||
sub = APIRouter()
|
||||
|
||||
@sub.get("/widgets.json")
|
||||
async def sub_widgets():
|
||||
return {
|
||||
"kc_natural_rate": {
|
||||
"widgetId": "kc_natural_rate",
|
||||
"endpoint": "natural_rate",
|
||||
"params": [
|
||||
{"paramName": "x", "optionsEndpoint": "natural_rate_choices"}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
app.include_router(sub, prefix="/api/v1/federal_reserve/kc")
|
||||
result = await get_and_fix_widget_paths(app)
|
||||
|
||||
widget = result["/api/v1/federal_reserve/kc/"]["kc_natural_rate"]
|
||||
assert widget["endpoint"] == "/api/v1/federal_reserve/kc/natural_rate"
|
||||
assert (
|
||||
widget["params"][0]["optionsEndpoint"]
|
||||
== "/api/v1/federal_reserve/kc/natural_rate_choices"
|
||||
)
|
||||
|
||||
|
||||
def test_fix_router_widgets_leaves_prefixed_and_missing_fields_untouched():
|
||||
"""Already-prefixed or absent endpoint/wsEndpoint/imgUrl are left as-is."""
|
||||
widgets = {
|
||||
|
||||
Reference in New Issue
Block a user