mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
[Feature] Allow Passing Custom FastAPI Instance To openbb-api From File (#7016)
* allow passing custom fastapi instance to openbb-api command line launcher * lint * more lint * more lint * fix reading list from command line args for --exclude * fix category and subCategory not populating by default * unused local * toml and lock cleanup * mock_widgets.json * add --templates-path arg * some path handling for templates * add the middleware if it is just a plain FastAPI app being passed * mock_widgets.json * fix source as 'custom' when it is a provider * clean that up * only use results for properties if it exists * else instead of elif * lint * update * add a few default templates * move order of events around or templates * update mock_widgets.json for runButton * remove extra slash at beginning of route * the real default templates.. * undo black formatting to remove changes to router * fix retail_prices choices * fred cpi * put cpi beside pce * update readme * generate columnsDefs for non-platform apps and routers if output is annotated * update readme * typo * readme unused imports * add a sentence to readme * update mock_widgets.json * fix indentation * make runButton False and fix a missing definition in yfinance management * duplicate comment * read each Field in Query and Data for x-widget_config in json_schema_extra * ruff * parameter placeholder label as Title * fix test
This commit is contained in:
160
openbb_platform/extensions/platform_api/README.md
vendored
160
openbb_platform/extensions/platform_api/README.md
vendored
@@ -1,10 +1,13 @@
|
||||
# OpenBB Platform API Meta Package
|
||||
|
||||
This is a meta package for installing a command line script to launch the OpenBB Platform API, and the OpenBB Terminal Pro data connector widgets build script.
|
||||
This package is responsible for launching and configuring an OpenBB Platform environment, or FastAPI instance, to use as an OpenBB Workspace custom backend.
|
||||
|
||||
## Installation
|
||||
|
||||
Install this package within an existing [OpenBB Platform Python environment](https://docs.openbb.co/platform/installation).
|
||||
This package is included when you run [`pip install openbb`](https://docs.openbb.co/platform/installation); however, it also works as a standalone package
|
||||
for creating new backends that are not part of the OpenBB GitHub [repository](https://github.com/OpenBB-finance/OpenBB/).
|
||||
|
||||
To install as a standalone, use a Python environment between versions 3.9 and 3.12, inclusively.
|
||||
|
||||
```sh
|
||||
pip install openbb-platform-api
|
||||
@@ -12,25 +15,142 @@ pip install openbb-platform-api
|
||||
|
||||
## Usage
|
||||
|
||||
The script is run from the command line, with the environment active, by entering:
|
||||
See the [keyword arguments](#keyword-arguments) section for parameters and descriptions.
|
||||
|
||||
```sh
|
||||
### Launch OpenBB Platform
|
||||
|
||||
To start the OpenBB Platform API, open a terminal, activate the environment where it is installed, and then enter:
|
||||
|
||||
```
|
||||
openbb-api
|
||||
```
|
||||
|
||||
This will launch a Fast API instance, via `uvicorn`, at `http://127.0.0.1:6900`
|
||||
|
||||
### Keyword Arguments
|
||||
Uvicorn can be configured by adding keyword arguments, see the section [below](#keyword-arguments)
|
||||
|
||||
### Launch Custom App
|
||||
|
||||
To run your application as an OpenBB Workspace custom backend, add the path to the Python file with the FastAPI instance to the launch command.
|
||||
|
||||
```sh
|
||||
openbb-api --app /Users/some_user/path/to/main.py
|
||||
```
|
||||
|
||||
#### Example Application
|
||||
|
||||
This script will create a "markdown" widget with the returned text.
|
||||
|
||||
```python
|
||||
from openbb_platform_api.main import app
|
||||
|
||||
@app.get("/hello")
|
||||
async def hello() -> str:
|
||||
"""Widget Description Generated By Docstring"""
|
||||
return "Hello, from OpenBB!"
|
||||
```
|
||||
|
||||
Function arguments will populate as widget parameters.
|
||||
|
||||
```python
|
||||
from openbb_platform_api.main import app
|
||||
from typing import Literal, Optional
|
||||
|
||||
@app.get("/hello")
|
||||
async def hello(param1: Optional[str] = None, param2: Literal["Choice 1", "Choice 2"] = None, param3: bool = False) -> str:
|
||||
"""Widget Description Generated By Docstring"""
|
||||
if not param1 and not param2 and not param3:
|
||||
return "Enter a parameter or make a choice!"
|
||||
if param3:
|
||||
return f"Param3 enabled!"
|
||||
if param2:
|
||||
return f"You selected: {param2}"
|
||||
if param1:
|
||||
return f"You entered: {param1}"
|
||||
|
||||
return "Nothing to return!"
|
||||
```
|
||||
|
||||
Create a table widget by returning data shaped as a list of dictionaries (records)
|
||||
|
||||
```python
|
||||
from openbb_platform_api.main import app
|
||||
|
||||
@app.get("/hello")
|
||||
async def hello() -> list:
|
||||
"""Widget Description Generated By Docstring"""
|
||||
return [{"Column 1": "Hello", "Column 2": "from OpenBB!"}]
|
||||
```
|
||||
|
||||
#### Widget Config
|
||||
|
||||
Any value from the [`widgets.json`](https://docs.openbb.co/terminal/custom-backend/widgets.json) structure can be passed into the `@app` decorator by including an `openapi_extra` dictionary with the key, `"widget_config"`.
|
||||
|
||||
```python
|
||||
from openbb_platform_api.main import app
|
||||
|
||||
|
||||
@app.get("/hello",
|
||||
openapi_extra={
|
||||
"widget_config": {
|
||||
"data": {
|
||||
"table": {
|
||||
"columnsDefs": [
|
||||
{"field": "Column 1", "headerName": "My Column", "headerTooltip": "Tooltip added!", "type": "text"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
async def hello() -> list:
|
||||
"""Widget Description Generated By Docstring"""
|
||||
return [{"Column 1": "Hello", "Column 2": "from OpenBB!"}]
|
||||
```
|
||||
|
||||
Configurations for `widgets.json` supplied here will override any of the automatically generated content.
|
||||
If the key does not exist, it will be created.
|
||||
|
||||
#### Auto-Generate ColumnsDefs
|
||||
|
||||
To generate ColumnsDefs for each field in the response data, return a list of Pydantic models with each column being assigned to Field.
|
||||
|
||||
Inherit the BaseModel from the `openbb-core` package by using the imports below.
|
||||
|
||||
Columns, by default, are presented in the order of insertion.
|
||||
|
||||
```python
|
||||
from openbb_platform_api.main import app
|
||||
from openbb_core.provider.abstract.data import Data # Inherit this instead of pydantic.BaseModel to represent 1 row of data.
|
||||
from pydantic import Field
|
||||
|
||||
class MyData(Data):
|
||||
"""MyData Response Model."""
|
||||
column_1: str = Field(title="Column 1", description="Description for Column 1") # 'description' will add the column header tooltip to your widget.
|
||||
column_2: str = Field(title="Column 2", description="Description for Column 2")
|
||||
|
||||
|
||||
@app.get("/hello")
|
||||
async def hello() -> list[MyData]:
|
||||
"""Widget Description Generated By Docstring"""
|
||||
return [MyData.model_validate({"column_1": "Hello", "column_2": "from OpenBB!"})]
|
||||
```
|
||||
|
||||
## Keyword Arguments
|
||||
|
||||
The behavior of the script can be configured with the use of arguments and keyword arguments.
|
||||
|
||||
Launcher specific arguments:
|
||||
|
||||
--build Build the widgets.json file.
|
||||
--no-build Do not build the widgets.json file.
|
||||
--app Absolute path to the Python file with the target FastAPI instance. Default is the installed OpenBB Platform API.
|
||||
--editable Flag to make widgets.json an editable file that can be modified during runtime. Default is 'false'.
|
||||
--build If the file already exists, changes prompt action to overwrite/append/ignore. Only valid when --editable true.
|
||||
--no-build Do not build the widgets.json file. Use this flag to load an existing widgets.json file without checking for updates.
|
||||
--login Login to the OpenBB Platform.
|
||||
--no-filter Do not filter the widgets.json file.
|
||||
|
||||
--exclude JSON encoded list of API paths to exclude from widgets.json. Disable entire routes with '*' - e.g. '["/api/v1/*"]'.
|
||||
--no-filter Do not filter out widgets in widget_settings.json file.
|
||||
--widgets-path Absolute path to the widgets.json file. Default is ~/envs/{env}/assets/widgets.json. Supplying this sets --editable true.
|
||||
--templates-path Absolute path to the workspace_templates.json file. Default is ~/OpenBBUserData/workspace_templates.json.
|
||||
|
||||
All other arguments will be passed to uvicorn. Here are the most common ones:
|
||||
|
||||
@@ -81,29 +201,41 @@ Contact the system administrator if you are using a work device and require addi
|
||||
|
||||
### Location of `widgets.json`
|
||||
|
||||
An OpenBB Terminal Pro data connector requires a file, `widgets.json`, to be served by the API. It contains the configuration for each widget available to be added to a dashboard. The file is stored, and served from, the environment's assets folder.
|
||||
When launched as `openbb-api --editiable`, a file will be stored to disk. By default, that location is:
|
||||
|
||||
```sh
|
||||
/Path/to/environments/envs/obb/assets/widgets.json
|
||||
```
|
||||
|
||||
It can be manually edited and served without the build process by passing `--no-build` to the API launch script.
|
||||
The file can be manually edited and served without the build process by passing `--editable --no-build` to the API launch script.
|
||||
|
||||
```sh
|
||||
openbb-api --no-build
|
||||
openbb-api --editable --no-build
|
||||
```
|
||||
|
||||
If you would like to construct this file manually for a custom backend configuration, save the file in the path above.
|
||||
If you would like to construct this file manually, create the file and define the path as an argument.
|
||||
|
||||
```sh
|
||||
openbb-api --widgets-path /Users/some_user/path/to/widgets.json
|
||||
```
|
||||
|
||||
|
||||
### Location of `templates.json`
|
||||
|
||||
By default, the location is:
|
||||
|
||||
> ~/OpenBBUserData/workspace_templates.json
|
||||
|
||||
This can be changed by adding the path as an argument.
|
||||
|
||||
```sh
|
||||
openbb-api --templates-path /Users/some_user/path/to/workspace_templates.json
|
||||
```
|
||||
|
||||
The OpenBB Workspace allows you to export the current dashboard layout - when it is a custom backend - as a template.
|
||||
|
||||
To export the layout, right-click on the dashboard and select, "Export Template".
|
||||
|
||||
A JSON dictionary will be exported. Insert the contents of the export into "~/OpenBBUserData/workspace_templates.json" by pasting between the JSON list markers, [ ].
|
||||
|
||||
If there is more than one, add a comma to the closing curly brace, }.
|
||||
If there are more than one, add a comma between each dictionary entry.
|
||||
|
||||
498
openbb_platform/extensions/platform_api/openbb_platform_api/assets/default_templates.json
vendored
Normal file
498
openbb_platform/extensions/platform_api/openbb_platform_api/assets/default_templates.json
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
[
|
||||
{
|
||||
"name": "Basic FRED Template",
|
||||
"img": "https://www.ajcottleappraisers.com/wp-content/uploads/2024/03/fred.png",
|
||||
"description": "FRED (Federal Reserve Economic Data) offers U.S. and global economic data, including GDP, inflation (CPI, PCE), unemployment, and consumer spending. It tracks interest rates, money supply (M1, M2), stock indices, bond yields, exchange rates, housing prices (Case-Shiller), mortgage rates, and trade balances. Industry data covers manufacturing, energy, and real estate.",
|
||||
"authentication": "Get your FRED API KEY at https://fred.stlouisfed.org/docs/api/api_key.html",
|
||||
"allowCustomization": true,
|
||||
"tabs": {
|
||||
"Search": {
|
||||
"id": "Search",
|
||||
"name": "Search",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_fred_search_fred_obb",
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"query": "pce",
|
||||
"tag_names": "inflation;pce"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"columnPinning": {
|
||||
"leftColIds": [
|
||||
"series_id",
|
||||
"title"
|
||||
],
|
||||
"rightColIds": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "economy_fred_series_fred_obb",
|
||||
"x": 0,
|
||||
"y": 17,
|
||||
"w": 40,
|
||||
"h": 10,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "RPI,PCE,PCENOW",
|
||||
"transform": "pc1",
|
||||
"start_date": "2010-01-01",
|
||||
"frequency": "q"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReleaseTables": {
|
||||
"id": "ReleaseTables",
|
||||
"name": "Release Tables",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_fred_release_table_fred_obb",
|
||||
"x": 0,
|
||||
"y": 10,
|
||||
"w": 40,
|
||||
"h": 12,
|
||||
"state": {
|
||||
"params": {
|
||||
"release_id": "52"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"columnPinning": {
|
||||
"leftColIds": [
|
||||
"name"
|
||||
],
|
||||
"rightColIds": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "economy_fred_search_fred_obb",
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 40,
|
||||
"h": 8,
|
||||
"state": {
|
||||
"params": {
|
||||
"search_type": "release"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"columnVisibility": {
|
||||
"hiddenColIds": [
|
||||
"observation_start",
|
||||
"observation_end",
|
||||
"frequency",
|
||||
"units",
|
||||
"seasonal_adjustment",
|
||||
"seasonal_adjustment_short",
|
||||
"realtime_start",
|
||||
"realtime_end",
|
||||
"notes"
|
||||
]
|
||||
},
|
||||
"focusedCell": {
|
||||
"colId": "series_id",
|
||||
"rowIndex": 4,
|
||||
"rowPinned": null
|
||||
},
|
||||
"scroll": {
|
||||
"top": 0,
|
||||
"left": 12938.6357421875
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "economy_fred_release_table_fred_obb",
|
||||
"x": 0,
|
||||
"y": 22,
|
||||
"w": 38,
|
||||
"h": 13,
|
||||
"state": {
|
||||
"params": {
|
||||
"release_id": "27",
|
||||
"element_id": "1240127"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"columnPinning": {
|
||||
"leftColIds": [
|
||||
"date",
|
||||
"symbol",
|
||||
"name",
|
||||
"value"
|
||||
],
|
||||
"rightColIds": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"BondIndices": {
|
||||
"id": "BondIndices",
|
||||
"name": "Bond Indices",
|
||||
"layout": [
|
||||
{
|
||||
"i": "fixedincome_bond_indices_fred_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"PCE": {
|
||||
"id": "PCE",
|
||||
"name": "PCE",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_pce_fred_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"cpi": {
|
||||
"id": "cpi",
|
||||
"name": "CPI",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_cpi_fred_obb",
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 28,
|
||||
"h": 18,
|
||||
"state": {
|
||||
"params": {
|
||||
"country": [
|
||||
"australia",
|
||||
"denmark"
|
||||
],
|
||||
"frequency": "quarter",
|
||||
"start_date": "2010-01-01"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"sort": {
|
||||
"sortModel": [
|
||||
{
|
||||
"colId": "date",
|
||||
"sort": "desc"
|
||||
}
|
||||
]
|
||||
},
|
||||
"columnPinning": {
|
||||
"leftColIds": [
|
||||
"date"
|
||||
],
|
||||
"rightColIds": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"NonFarmPayroll": {
|
||||
"id": "NonFarmPayroll",
|
||||
"name": "Nonfarm Payrolls",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_survey_nonfarm_payrolls_fred_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RetailPrices": {
|
||||
"id": "RetailPrices",
|
||||
"name": "Retail Prices",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_retail_prices_fred_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"EconomicConditions": {
|
||||
"id": "EconomicConditions",
|
||||
"name": "Chicago Economic Conditions",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_survey_economic_conditions_chicago_fred_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ManufacturingOutlook": {
|
||||
"id": "ManufacturingOutlook",
|
||||
"name": "Texas Manufacturing Outlook",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_survey_manufacturing_outlook_texas_fred_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SLOOS": {
|
||||
"id": "SLOOS",
|
||||
"name": "SLOOS",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_survey_sloos_fred_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"groups": []
|
||||
},
|
||||
{
|
||||
"name": "Basic BLS Template",
|
||||
"img": "https://tigadvisors.com/wp-content/uploads/2022/01/BLS.jpg",
|
||||
"description": "The Bureau of Labor Statistics (BLS) provides U.S. economic data on employment, unemployment, wages, and productivity. Key datasets include the Consumer Price Index (CPI) for inflation, Producer Price Index (PPI) for wholesale prices, and Employment Situation Report for job market trends. It tracks wages (CES, QCEW), job openings (JOLTS), productivity (BLS Productivity), and workplace injuries (SOII). Industry and regional data are also available.",
|
||||
"authentication": "Get your BLS API KEY at https://www.bls.gov/developers/home.htm",
|
||||
"allowCustomization": true,
|
||||
"tabs": {
|
||||
"Search": {
|
||||
"id": "Search",
|
||||
"name": "Search",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_survey_bls_search_bls_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 8,
|
||||
"state": {
|
||||
"params": {
|
||||
"category": "nfp",
|
||||
"include_extras": true
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"columnPinning": {
|
||||
"leftColIds": [
|
||||
"symbol",
|
||||
"title"
|
||||
],
|
||||
"rightColIds": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "economy_survey_bls_series_bls_obb",
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"w": 40,
|
||||
"h": 18,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "CES0000000001",
|
||||
"start_date": "2016-01-01"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"columnPinning": {
|
||||
"leftColIds": [
|
||||
"date",
|
||||
"symbol",
|
||||
"title"
|
||||
],
|
||||
"rightColIds": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"groups": []
|
||||
},
|
||||
{
|
||||
"name": "Basic IMF Template",
|
||||
"img": "https://www.imf.org/-/media/Images/IMF/About/Factsheets/2022/Hero/imf-logo.ashx",
|
||||
"description": "Global economic and financial data, including GDP, inflation, trade, and fiscal balances (WEO), financial stability risks (GFSR), and public debt (GFS, QPSD). It tracks exchange rates, interest rates, and money supply (IFS), balance of payments and trade flows (BOPS, DOTS), and banking sector health (FSIs).",
|
||||
"allowCustomization": true,
|
||||
"tabs": {
|
||||
"DirectionOfTrade": {
|
||||
"id": "DirectionOfTrade",
|
||||
"name": "Direction Of Trade",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_direction_of_trade_imf_obb",
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 40,
|
||||
"h": 25,
|
||||
"state": {
|
||||
"params": {
|
||||
"country": "us",
|
||||
"counterpart": "canada,mexico",
|
||||
"direction": "all",
|
||||
"start_date": "2010-01-01"
|
||||
},
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
},
|
||||
"columnState": {
|
||||
"default_": {
|
||||
"columnPinning": {
|
||||
"leftColIds": [
|
||||
"date",
|
||||
"symbol",
|
||||
"title"
|
||||
],
|
||||
"rightColIds": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Indicators": {
|
||||
"id": "Indicators",
|
||||
"name": "Indicators",
|
||||
"layout": [
|
||||
{
|
||||
"i": "economy_available_indicators_imf_obb",
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 40,
|
||||
"h": 10,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "economy_indicators_imf_obb",
|
||||
"x": 0,
|
||||
"y": 12,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"chartView": {
|
||||
"enabled": false,
|
||||
"chartType": "line"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"groups": []
|
||||
}
|
||||
]
|
||||
@@ -1,12 +1,13 @@
|
||||
"""OpenBB Platform API.
|
||||
|
||||
Launch script and widgets builder for the OpenBB Terminal Custom Backend.
|
||||
Launch script and widgets builder for the OpenBB Workspace Custom Backend.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import uvicorn
|
||||
from fastapi.responses import JSONResponse
|
||||
@@ -41,14 +42,32 @@ USER_SETTINGS_COPY = os.path.join(HOME, ".openbb_platform", "user_settings_backu
|
||||
|
||||
# Widget filtering is optional and can be used to exclude widgets from the widgets.json file
|
||||
# You can generate this filter on OpenBB Hub: https://my.openbb.co/app/platform/widgets
|
||||
# Alternatively, you can supply a JSON-encoded list of API paths to ignore.
|
||||
WIDGET_SETTINGS = os.path.join(HOME, ".openbb_platform", "widget_settings.json")
|
||||
|
||||
kwargs = parse_args()
|
||||
|
||||
_app = kwargs.pop("app", None)
|
||||
|
||||
if _app:
|
||||
app = _app
|
||||
|
||||
|
||||
EDITABLE = kwargs.pop("editable", None) is True
|
||||
WIDGETS_PATH = kwargs.pop("widgets_path", None)
|
||||
TEMPLATES_PATH = kwargs.pop("templates_path", None)
|
||||
DEFAULT_TEMPLATES_PATH = (
|
||||
Path(__file__)
|
||||
.absolute()
|
||||
.parent.joinpath("assets")
|
||||
.joinpath("default_templates.json")
|
||||
)
|
||||
COPILOTS = kwargs.pop("copilots", None)
|
||||
build = kwargs.pop("build", True)
|
||||
build = False if kwargs.pop("no-build", None) else build
|
||||
login = kwargs.pop("login", False)
|
||||
dont_filter = kwargs.pop("no-filter", False)
|
||||
widget_exclude_filter: list = []
|
||||
widget_exclude_filter: list = kwargs.pop("exclude", [])
|
||||
|
||||
uvicorn_settings = (
|
||||
SystemService().system_settings.python_settings.model_dump().get("uvicorn", {})
|
||||
@@ -59,9 +78,11 @@ for key, value in uvicorn_settings.items():
|
||||
kwargs[key] = value
|
||||
|
||||
if not dont_filter and os.path.exists(WIDGET_SETTINGS):
|
||||
with open(WIDGET_SETTINGS) as f:
|
||||
with open(WIDGET_SETTINGS) as widget_settings_file:
|
||||
try:
|
||||
widget_exclude_filter = json.load(f)["exclude"]
|
||||
widget_exclude_filter_json = json.load(widget_settings_file)["exclude"]
|
||||
if isinstance(widget_exclude_filter_json, list):
|
||||
widget_exclude_filter.extend(widget_exclude_filter_json)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.info("Error loading widget filter settings -> %s", e)
|
||||
|
||||
@@ -72,55 +93,112 @@ openapi = app.openapi()
|
||||
# but we need to call the function to update, login, and/or identify the settings file.
|
||||
current_settings = get_user_settings(login, CURRENT_USER_SETTINGS, USER_SETTINGS_COPY)
|
||||
|
||||
widgets_json = get_widgets_json(build, openapi, widget_exclude_filter)
|
||||
widgets_json = get_widgets_json(
|
||||
build, openapi, widget_exclude_filter, EDITABLE, WIDGETS_PATH
|
||||
)
|
||||
|
||||
# A template file will be served from the OpenBBUserDataDirectory, if it exists.
|
||||
# If it doesn't exist, an empty list will be returned, and an empty file will be created.
|
||||
TEMPLATES_PATH = (
|
||||
current_settings["preferences"].get("data_directory", HOME + "/OpenBBUserData")
|
||||
+ "/workspace_templates.json"
|
||||
TEMPLATES_PATH
|
||||
+ f"{'/' if TEMPLATES_PATH[-1] != '/' else ''}"
|
||||
+ "workspace_templates.json"
|
||||
if TEMPLATES_PATH
|
||||
else (
|
||||
current_settings["preferences"].get("data_directory", HOME + "/OpenBBUserData")
|
||||
+ "/workspace_templates.json"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# If a custom implementation, you might want to override.
|
||||
@app.get("/templates.json")
|
||||
def get_templates():
|
||||
"""Get the templates.json file."""
|
||||
if os.path.exists(TEMPLATES_PATH):
|
||||
with open(TEMPLATES_PATH) as templates_file:
|
||||
templates = json.load(templates_file)
|
||||
|
||||
if isinstance(templates, list):
|
||||
return JSONResponse(content=templates)
|
||||
if isinstance(templates, dict):
|
||||
return JSONResponse(content=[templates])
|
||||
|
||||
else:
|
||||
with open(TEMPLATES_PATH, "w", encoding="utf-8") as templates_file:
|
||||
json.dump([], templates_file)
|
||||
|
||||
return JSONResponse(content=[])
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def get_root():
|
||||
"""Root response and welcome message."""
|
||||
return JSONResponse(
|
||||
content="Welcome to the OpenBB Platform API."
|
||||
+ " Learn how to connect to Pro in https://docs.openbb.co/pro/custom-backend,"
|
||||
+ " or see the API documentation here: /docs"
|
||||
+ " and see the API documentation here: /docs, or, /redoc"
|
||||
)
|
||||
|
||||
|
||||
@app.get("/widgets.json")
|
||||
async def get_widgets():
|
||||
"""Widgets configuration file for the OpenBB Terminal Pro."""
|
||||
"""Widgets configuration file for the OpenBB Workspace."""
|
||||
# This allows us to serve an edited widgets.json file without reloading the server.
|
||||
global FIRST_RUN # noqa PLW0603 # pylint: disable=global-statement
|
||||
if FIRST_RUN is True:
|
||||
FIRST_RUN = False
|
||||
return JSONResponse(content=widgets_json)
|
||||
return JSONResponse(content=get_widgets_json(False, openapi, widget_exclude_filter))
|
||||
if EDITABLE:
|
||||
return JSONResponse(
|
||||
content=get_widgets_json(
|
||||
False, openapi, widget_exclude_filter, EDITABLE, WIDGETS_PATH
|
||||
)
|
||||
)
|
||||
return JSONResponse(content=widgets_json)
|
||||
|
||||
|
||||
# If a custom implementation, you might want to override.
|
||||
@app.get("/templates.json")
|
||||
async def get_templates():
|
||||
"""Get the templates.json file."""
|
||||
new_templates: list = []
|
||||
default_templates: list = []
|
||||
widgets = await get_widgets()
|
||||
|
||||
if not Path(TEMPLATES_PATH).parent.exists():
|
||||
Path(TEMPLATES_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(TEMPLATES_PATH, "w", encoding="utf-8") as templates_file:
|
||||
json.dump([], templates_file)
|
||||
|
||||
if os.path.exists(DEFAULT_TEMPLATES_PATH):
|
||||
with open(DEFAULT_TEMPLATES_PATH) as f:
|
||||
default_templates = json.load(f)
|
||||
|
||||
if os.path.exists(TEMPLATES_PATH):
|
||||
with open(TEMPLATES_PATH) as templates_file:
|
||||
templates = json.load(templates_file)
|
||||
|
||||
if isinstance(templates, dict):
|
||||
templates = [templates]
|
||||
|
||||
templates.extend(default_templates)
|
||||
|
||||
for template in templates:
|
||||
if _id := template.get("id"):
|
||||
if _id in widgets and template not in new_templates:
|
||||
new_templates.append(template)
|
||||
continue
|
||||
elif template.get("layout") or template.get("tabs"):
|
||||
if _tabs := template.get("tabs"):
|
||||
for v in _tabs.values():
|
||||
if v.get("layout", []) and all(
|
||||
item.get("i") in widgets_json for item in v.get("layout")
|
||||
):
|
||||
new_templates.append(template)
|
||||
break
|
||||
elif (
|
||||
template.get("layout")
|
||||
and all(
|
||||
item.get("i") in widgets_json for item in template["layout"]
|
||||
)
|
||||
and template not in new_templates
|
||||
):
|
||||
new_templates.append(template)
|
||||
|
||||
if new_templates:
|
||||
return JSONResponse(content=new_templates)
|
||||
|
||||
return JSONResponse(content=[])
|
||||
|
||||
|
||||
if COPILOTS:
|
||||
|
||||
@app.get("/copilots.json")
|
||||
async def get_copilots():
|
||||
"""Get the copilots.json file."""
|
||||
return JSONResponse(content=COPILOTS)
|
||||
|
||||
|
||||
def launch_api(**_kwargs): # noqa PRL0912
|
||||
@@ -162,7 +240,7 @@ def launch_api(**_kwargs): # noqa PRL0912
|
||||
try:
|
||||
package_name = __package__
|
||||
logger.info(
|
||||
"\nTo access this data from OpenBB Terminal, use the link displayed after the application startup completes."
|
||||
"\nTo access this data from OpenBB Workspace, use the link displayed after the application startup completes."
|
||||
"\nChrome is the recommended browser. Other browsers may conflict or require additional configuration."
|
||||
"\nDocumentation is available at /docs."
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
import socket
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from deepdiff import DeepDiff
|
||||
|
||||
@@ -16,10 +17,32 @@ Serve the OpenBB Platform API.
|
||||
|
||||
Launcher specific arguments:
|
||||
|
||||
--build Build the widgets.json file.
|
||||
--no-build Do not build the widgets.json file.
|
||||
--app Absolute path to the Python file with the target FastAPI instance. Default is the installed OpenBB Platform API.
|
||||
--editable Flag to make widgets.json an editable file that can be modified during runtime. Default is 'false'.
|
||||
--build If the file already exists, changes prompt action to overwrite/append/ignore. Only valid when --editable true.
|
||||
--no-build Do not build the widgets.json file. Use this flag to load an existing widgets.json file without checking for updates.
|
||||
--login Login to the OpenBB Platform.
|
||||
--no-filter Do not filter the widgets.json file.
|
||||
--exclude JSON encoded list of API paths to exclude from widgets.json. Disable entire routes with '*' - e.g. '["/api/v1/*"]'.
|
||||
--no-filter Do not filter out widgets in widget_settings.json file.
|
||||
--widgets-path Absolute path to the widgets.json file. Default is ~/envs/{env}/assets/widgets.json. Supplying this sets --editable true.
|
||||
--templates-path Absolute path to the workspace_templates.json file. Default is ~/OpenBBUserData/workspace_templates.json.
|
||||
--copilots-path Absolute path to the copilots.json file. Including this will add the /copilots endpoint to the API.
|
||||
|
||||
|
||||
The FastAPI app instance can be imported to another script, modified, and launched by using the --app argument.
|
||||
|
||||
Imported with:
|
||||
|
||||
>>> from openbb_platform_api.main import app
|
||||
>>>
|
||||
>>> @app.get()
|
||||
>>> async def hello(input: str = "Hello") -> str:
|
||||
>>> '''Widget description created by doctring.'''
|
||||
>>> return f"You entered: {input]"
|
||||
|
||||
Launched with:
|
||||
|
||||
>>> openbb-api --app /path/to/some_file.py
|
||||
|
||||
|
||||
All other arguments will be passed to uvicorn. Here are the most common ones:
|
||||
@@ -43,7 +66,7 @@ All other arguments will be passed to uvicorn. Here are the most common ones:
|
||||
[default: TLSv1]
|
||||
|
||||
Run `uvicorn --help` to get the full list of arguments.
|
||||
"""
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
def check_port(host, port):
|
||||
@@ -171,60 +194,120 @@ def get_user_settings(
|
||||
return _current_settings
|
||||
|
||||
|
||||
def get_widgets_json(_build: bool, _openapi, widget_exclude_filter: list):
|
||||
def get_widgets_json(
|
||||
_build: bool,
|
||||
_openapi,
|
||||
widget_exclude_filter: list,
|
||||
editable: bool = False,
|
||||
widgets_path: Optional[str] = None,
|
||||
):
|
||||
"""Generate and serve the widgets.json for the OpenBB Platform API."""
|
||||
python_path = Path(sys.executable)
|
||||
parent_path = python_path.parent if os.name == "nt" else python_path.parents[1]
|
||||
widgets_json_path = parent_path.joinpath("assets", "widgets.json").resolve()
|
||||
json_exists = widgets_json_path.exists()
|
||||
|
||||
if not json_exists:
|
||||
widgets_json_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
_build = True
|
||||
|
||||
existing_widgets_json: dict = {}
|
||||
|
||||
if json_exists:
|
||||
with open(widgets_json_path, encoding="utf-8") as f:
|
||||
existing_widgets_json = json.load(f)
|
||||
|
||||
_widgets_json = (
|
||||
existing_widgets_json
|
||||
if _build is False
|
||||
else build_json(_openapi, widget_exclude_filter)
|
||||
)
|
||||
|
||||
if _build:
|
||||
diff = DeepDiff(existing_widgets_json, _widgets_json, ignore_order=True)
|
||||
merge_prompt = None
|
||||
if diff and json_exists:
|
||||
print("Differences found:", diff) # noqa: T201
|
||||
merge_prompt = input(
|
||||
"\nDo you want to overwrite the existing widgets.json configuration?"
|
||||
"\nEnter 'n' to append existing with only new entries, or 'i' to ignore all changes. (y/n/i): "
|
||||
if editable is True:
|
||||
if widgets_path is None:
|
||||
python_path = Path(sys.executable)
|
||||
parent_path = (
|
||||
python_path.parent if os.name == "nt" else python_path.parents[1]
|
||||
)
|
||||
widgets_json_path = parent_path.joinpath("assets", "widgets.json").resolve()
|
||||
else:
|
||||
widgets_json_path = (
|
||||
Path(widgets_path).absolute().joinpath("widgets.json").resolve()
|
||||
)
|
||||
if merge_prompt.lower().startswith("n"):
|
||||
_widgets_json.update(existing_widgets_json)
|
||||
elif merge_prompt.lower().startswith("i"):
|
||||
_widgets_json = existing_widgets_json
|
||||
|
||||
if merge_prompt is None or not merge_prompt.lower().startswith("i"):
|
||||
try:
|
||||
with open(widgets_json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(_widgets_json, f, ensure_ascii=False, indent=4)
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
print( # noqa: T201
|
||||
f"Error writing widgets.json: {e}. Loading from memory instead."
|
||||
)
|
||||
_widgets_json = (
|
||||
existing_widgets_json
|
||||
if existing_widgets_json
|
||||
else build_json(_openapi, widget_exclude_filter)
|
||||
json_exists = widgets_json_path.exists()
|
||||
|
||||
if not json_exists:
|
||||
widgets_json_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
_build = True
|
||||
|
||||
existing_widgets_json: dict = {}
|
||||
|
||||
if json_exists:
|
||||
with open(widgets_json_path, encoding="utf-8") as f:
|
||||
existing_widgets_json = json.load(f)
|
||||
|
||||
_widgets_json = (
|
||||
existing_widgets_json
|
||||
if _build is False
|
||||
else build_json(_openapi, widget_exclude_filter)
|
||||
)
|
||||
|
||||
if _build:
|
||||
diff = DeepDiff(existing_widgets_json, _widgets_json, ignore_order=True)
|
||||
merge_prompt = None
|
||||
if diff and json_exists:
|
||||
print("Differences found:", diff) # noqa: T201
|
||||
merge_prompt = input(
|
||||
"\nDo you want to overwrite the existing widgets.json configuration?"
|
||||
"\nEnter 'n' to append existing with only new entries, or 'i' to ignore all changes. (y/n/i): "
|
||||
)
|
||||
if merge_prompt.lower().startswith("n"):
|
||||
_widgets_json.update(existing_widgets_json)
|
||||
elif merge_prompt.lower().startswith("i"):
|
||||
_widgets_json = existing_widgets_json
|
||||
|
||||
if merge_prompt is None or not merge_prompt.lower().startswith("i"):
|
||||
try:
|
||||
with open(widgets_json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(_widgets_json, f, ensure_ascii=False, indent=4)
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
print( # noqa: T201
|
||||
f"Error writing widgets.json: {e}. Loading from memory instead."
|
||||
)
|
||||
_widgets_json = (
|
||||
existing_widgets_json
|
||||
if existing_widgets_json
|
||||
else build_json(_openapi, widget_exclude_filter)
|
||||
)
|
||||
else:
|
||||
_widgets_json = build_json(_openapi, widget_exclude_filter)
|
||||
|
||||
return _widgets_json
|
||||
|
||||
|
||||
def import_app(app_path: str):
|
||||
"""Import the FastAPI app instance from a local file."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from fastapi import FastAPI # noqa
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from importlib import util
|
||||
from openbb_core.api.app_loader import AppLoader
|
||||
from openbb_core.api.rest_api import system
|
||||
|
||||
if not Path(app_path).exists():
|
||||
raise FileNotFoundError(f"Error: The app file '{app_path}' does not exist")
|
||||
|
||||
spec = util.spec_from_file_location("app", app_path)
|
||||
if spec is None:
|
||||
raise RuntimeError(f"Failed to load the file specs for '{app_path}'")
|
||||
|
||||
module = util.module_from_spec(spec) # type: ignore
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
|
||||
if not hasattr(module, "app"):
|
||||
raise AttributeError(
|
||||
f"Error: The app file '{app_path}' does not contain an 'app' instance"
|
||||
)
|
||||
|
||||
if not isinstance(module.app, FastAPI):
|
||||
raise TypeError(
|
||||
f"Error: The app instance in '{app_path}' is not an instance of FastAPI"
|
||||
)
|
||||
|
||||
app = module.app
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=system.api_settings.cors.allow_origins,
|
||||
allow_methods=system.api_settings.cors.allow_methods,
|
||||
allow_headers=system.api_settings.cors.allow_headers,
|
||||
)
|
||||
AppLoader.add_openapi_tags(app)
|
||||
AppLoader.add_exception_handlers(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse the launch script command line arguments."""
|
||||
args = sys.argv[1:].copy()
|
||||
@@ -241,9 +324,32 @@ def parse_args():
|
||||
value = args[i + 1]
|
||||
if isinstance(value, str) and value.lower() in ["false", "true"]:
|
||||
_kwargs[key] = value.lower() == "true"
|
||||
elif key == "exclude":
|
||||
_kwargs[key] = json.loads(value)
|
||||
else:
|
||||
_kwargs[key] = value
|
||||
else:
|
||||
_kwargs[key] = True
|
||||
|
||||
if isinstance(_kwargs.get("exclude"), str):
|
||||
_kwargs["exclude"] = [_kwargs["exclude"]]
|
||||
|
||||
if _kwargs.get("copilots-path"):
|
||||
_copilots_path = _kwargs.pop("copilots-path", None)
|
||||
if not Path(_copilots_path).exists():
|
||||
raise FileNotFoundError(
|
||||
f"Error: The copilots file '{_copilots_path}' does not exist"
|
||||
)
|
||||
with open(_copilots_path, encoding="utf-8") as f:
|
||||
_kwargs["copilots"] = json.load(f)
|
||||
|
||||
if _kwargs.get("app"):
|
||||
_app_path = _kwargs.pop("app", None)
|
||||
if not Path(_app_path).exists():
|
||||
raise FileNotFoundError(f"Error: The app file '{_app_path}' does not exist")
|
||||
_kwargs["app"] = import_app(_app_path)
|
||||
|
||||
if _kwargs.get("widgets_path"):
|
||||
_kwargs["editable"] = True
|
||||
|
||||
return _kwargs
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""OpenAPI parsing Utils."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from openbb_core.provider.utils.helpers import to_snake_case
|
||||
|
||||
TO_CAPS_STRINGS = [
|
||||
@@ -65,7 +67,10 @@ def extract_providers(params: list[dict]) -> list[str]:
|
||||
"""
|
||||
provider_params = [p for p in params if p["name"] == "provider"]
|
||||
if provider_params:
|
||||
return provider_params[0].get("schema", {}).get("enum", [])
|
||||
if provider_params[0].get("schema", {}).get("enum"):
|
||||
return provider_params[0]["schema"]["enum"]
|
||||
if provider_params[0].get("schema", {}).get("default"):
|
||||
return [str(provider_params[0]["schema"]["default"])]
|
||||
return []
|
||||
|
||||
|
||||
@@ -221,6 +226,9 @@ def set_parameter_options(p: dict, p_schema: dict, providers: list[str]) -> dict
|
||||
if is_provider_specific:
|
||||
p["available_providers"] = list(available_providers)
|
||||
|
||||
if (title := p_schema.get("title", "")) and title not in providers:
|
||||
p["label"] = title
|
||||
|
||||
return p
|
||||
|
||||
|
||||
@@ -288,6 +296,9 @@ def process_parameter(param: dict, providers: list[str]) -> dict:
|
||||
p = set_parameter_options(p, p_schema, providers)
|
||||
p = set_parameter_type(p, p_schema)
|
||||
|
||||
if _widget_config := p_schema.get("x-widget_config", {}):
|
||||
p.update(_widget_config)
|
||||
|
||||
return p
|
||||
|
||||
|
||||
@@ -331,7 +342,7 @@ def get_query_schema_for_widget(
|
||||
return route_params, has_chart
|
||||
|
||||
|
||||
def get_data_schema_for_widget(openapi_json, operation_id):
|
||||
def get_data_schema_for_widget(openapi_json, operation_id, route: Optional[str] = None):
|
||||
"""
|
||||
Get the data schema for a widget based on its operationId.
|
||||
|
||||
@@ -343,48 +354,62 @@ def get_data_schema_for_widget(openapi_json, operation_id):
|
||||
dict: The schema dictionary for the widget's data.
|
||||
"""
|
||||
# Find the route and method for the given operationId
|
||||
for _, methods in openapi_json["paths"].items():
|
||||
for _, details in methods.items():
|
||||
if details.get("operationId") == operation_id:
|
||||
# Get the reference to the schema from the successful response
|
||||
response_ref = details["responses"]["200"]["content"][
|
||||
"application/json"
|
||||
].get("schema", {}).get("$ref") or details["responses"]["200"][
|
||||
"content"
|
||||
][
|
||||
"application/json"
|
||||
].get(
|
||||
"schema"
|
||||
|
||||
if not route:
|
||||
for path, methods in openapi_json["paths"].items():
|
||||
for _method, details in methods.items():
|
||||
if details.get("operationId") == operation_id:
|
||||
route = path
|
||||
break
|
||||
|
||||
_route = openapi_json["paths"].get(route, {}).get("get", {})
|
||||
|
||||
if (
|
||||
schema := _route.get("responses", {})
|
||||
.get("200", {})
|
||||
.get("content", {})
|
||||
.get("application/json", {})
|
||||
.get("schema", {})
|
||||
):
|
||||
# Get the reference to the schema from the successful response
|
||||
|
||||
if "items" in schema:
|
||||
response_ref = schema["items"].get("$ref")
|
||||
else:
|
||||
response_ref = schema.get("$ref") or _route["responses"]["200"]["content"][
|
||||
"application/json"
|
||||
].get("schema")
|
||||
|
||||
if isinstance(response_ref, dict) and "type" in response_ref:
|
||||
response_ref = response_ref["type"]
|
||||
|
||||
if response_ref:
|
||||
# Extract the schema name from the reference
|
||||
schema_name = response_ref.split("/")[-1]
|
||||
# Fetch and return the schema from components
|
||||
if schema_name and schema_name in openapi_json.get("components", {}).get(
|
||||
"schemas", {}
|
||||
):
|
||||
props = openapi_json["components"]["schemas"][schema_name].get(
|
||||
"properties", {}
|
||||
)
|
||||
if props and "results" in props:
|
||||
return props["results"]
|
||||
|
||||
if isinstance(response_ref, dict) and "type" in response_ref:
|
||||
response_ref = response_ref["type"]
|
||||
|
||||
if response_ref:
|
||||
# Extract the schema name from the reference
|
||||
schema_name = response_ref.split("/")[-1]
|
||||
# Fetch and return the schema from components
|
||||
if (
|
||||
schema_name
|
||||
and schema_name in openapi_json["components"]["schemas"]
|
||||
):
|
||||
return (
|
||||
openapi_json["components"]["schemas"][schema_name]
|
||||
.get("properties", {})
|
||||
.get("results", {})
|
||||
)
|
||||
|
||||
return openapi_json["components"]["schemas"].get(schema_name, schema_name)
|
||||
# Return None if the schema is not found
|
||||
return None
|
||||
|
||||
|
||||
def data_schema_to_columns_defs(openapi_json, operation_id, provider):
|
||||
def data_schema_to_columns_defs( # noqa: PLR0912
|
||||
openapi_json, operation_id, provider, route: Optional[str] = None
|
||||
):
|
||||
"""Convert data schema to column definitions for the widget."""
|
||||
|
||||
# Initialize an empty list to hold the schema references
|
||||
schema_refs: list = []
|
||||
|
||||
result_schema_ref = get_data_schema_for_widget(openapi_json, operation_id)
|
||||
result_schema_ref = get_data_schema_for_widget(openapi_json, operation_id, route)
|
||||
|
||||
# Check if 'anyOf' is in the result_schema_ref and handle the nested structure
|
||||
if result_schema_ref and "anyOf" in result_schema_ref:
|
||||
for item in result_schema_ref["anyOf"]:
|
||||
@@ -409,6 +434,9 @@ def data_schema_to_columns_defs(openapi_json, operation_id, provider):
|
||||
if ref and ref in openapi_json["components"]["schemas"]
|
||||
]
|
||||
|
||||
if not schemas and result_schema_ref and "properties" in result_schema_ref:
|
||||
schemas.append(result_schema_ref)
|
||||
|
||||
# Proceed with finding common keys and generating column definitions
|
||||
if not schemas:
|
||||
return []
|
||||
@@ -517,10 +545,16 @@ def data_schema_to_columns_defs(openapi_json, operation_id, provider):
|
||||
column_def["headerName"].upper() if k != "symbol" else "Symbol"
|
||||
)
|
||||
|
||||
if k in ["fiscal_year", "year"]:
|
||||
if k in ["fiscal_year", "year", "year_born"]:
|
||||
column_def["cellDataType"] = "number"
|
||||
column_def["formatterFn"] = "none"
|
||||
|
||||
if "date" in column_def["headerTooltip"].lower():
|
||||
column_def["cellDataType"] = "date"
|
||||
|
||||
if _widget_config := prop.get("x-widget_config", {}):
|
||||
column_def.update(_widget_config)
|
||||
|
||||
column_defs.append(column_def)
|
||||
|
||||
return column_defs
|
||||
|
||||
@@ -1,6 +1,89 @@
|
||||
"""Utils for building the widgets.json file."""
|
||||
|
||||
from copy import deepcopy
|
||||
from typing import Union
|
||||
|
||||
|
||||
def deep_merge_configs(
|
||||
base: dict,
|
||||
update: dict,
|
||||
match_keys: Union[str, tuple, list, None] = None,
|
||||
) -> dict:
|
||||
"""Deep merge two nested dictionaries."""
|
||||
|
||||
if match_keys is None:
|
||||
match_keys = ["paramName", "field"]
|
||||
|
||||
if isinstance(match_keys, str):
|
||||
match_keys = (match_keys,)
|
||||
|
||||
def merge_values(base_val, update_val):
|
||||
"""Merge two values."""
|
||||
# Handle explicit empty values
|
||||
if update_val in ([], {}, None):
|
||||
return update_val
|
||||
|
||||
if isinstance(update_val, dict) and isinstance(base_val, dict):
|
||||
return deep_merge_configs(base_val, update_val, match_keys)
|
||||
|
||||
if isinstance(update_val, list) and isinstance(base_val, list):
|
||||
return merge_lists(base_val, update_val)
|
||||
|
||||
return update_val
|
||||
|
||||
def merge_lists(base_list: list, update_list: list) -> list:
|
||||
"""Merge two lists."""
|
||||
new_list: list = []
|
||||
update_items: dict = {}
|
||||
|
||||
# Handle nested structures in lists
|
||||
for item in update_list:
|
||||
if isinstance(item, dict):
|
||||
for match_key in match_keys:
|
||||
if match_key in item:
|
||||
update_items[item[match_key]] = item
|
||||
break
|
||||
elif isinstance(item, (list, dict)):
|
||||
new_list.append(item)
|
||||
|
||||
for base_item in base_list:
|
||||
if isinstance(base_item, dict):
|
||||
matched = False
|
||||
for match_key in match_keys:
|
||||
if match_key in base_item:
|
||||
item_id = base_item[match_key]
|
||||
if item_id in update_items:
|
||||
merged = base_item.copy()
|
||||
update_item = update_items.pop(item_id)
|
||||
for k, v in update_item.items():
|
||||
merged[k] = merge_values(merged.get(k), v)
|
||||
new_list.append(merged)
|
||||
matched = True
|
||||
break
|
||||
if not matched:
|
||||
new_list.append(base_item)
|
||||
elif isinstance(base_item, list):
|
||||
matching_update = next(
|
||||
(x for x in update_list if isinstance(x, list)), None
|
||||
)
|
||||
if matching_update:
|
||||
new_list.append(merge_lists(base_item, matching_update))
|
||||
else:
|
||||
new_list.append(base_item)
|
||||
else:
|
||||
new_list.append(base_item)
|
||||
|
||||
new_list.extend(update_items.values())
|
||||
|
||||
return new_list
|
||||
|
||||
for key, value in update.items():
|
||||
if key in base:
|
||||
base[key] = merge_values(base[key], value)
|
||||
else:
|
||||
base[key] = value
|
||||
|
||||
return base
|
||||
|
||||
|
||||
def modify_query_schema(query_schema: list[dict], provider_value: str):
|
||||
@@ -72,17 +155,55 @@ def build_json(openapi: dict, widget_exclude_filter: list):
|
||||
if not openapi:
|
||||
return {}
|
||||
|
||||
starred_list: list = []
|
||||
for item in widget_exclude_filter.copy():
|
||||
if "*" in item:
|
||||
starred_list.append(item)
|
||||
widget_exclude_filter.remove(item)
|
||||
|
||||
widgets_json: dict = {}
|
||||
routes = [
|
||||
p
|
||||
for p in openapi["paths"]
|
||||
if p.startswith("/api") and "get" in openapi["paths"][p]
|
||||
if openapi["paths"].get(p, {}) and "get" in openapi["paths"][p]
|
||||
]
|
||||
for route in routes:
|
||||
skip = False
|
||||
for starred in starred_list:
|
||||
if route.startswith(
|
||||
starred.replace("*", "")
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace('"', "")
|
||||
.replace("'", "")
|
||||
):
|
||||
skip = True
|
||||
break
|
||||
|
||||
if skip is True:
|
||||
continue
|
||||
|
||||
route_api = openapi["paths"][route]
|
||||
method = list(route_api)[0]
|
||||
widget_id = route_api[method]["operationId"]
|
||||
widget_id = (
|
||||
(
|
||||
route[1:].replace("/", "_")
|
||||
if route[0] == "/"
|
||||
else route.replace("/", "_")
|
||||
)
|
||||
.replace("api_", "")
|
||||
.replace("v1_", "")
|
||||
)
|
||||
|
||||
if widget_id in widget_exclude_filter:
|
||||
continue
|
||||
|
||||
widget_config_dict = route_api[method].get("widget_config", {})
|
||||
|
||||
# If the widget is marked as excluded, skip it.
|
||||
if widget_config_dict.get("exclude") is True:
|
||||
continue
|
||||
|
||||
# Prepare the query schema of the widget
|
||||
query_schema, has_chart = get_query_schema_for_widget(openapi, route)
|
||||
response_schema = route_api[method]["responses"]["200"]["content"][
|
||||
@@ -99,12 +220,21 @@ def build_json(openapi: dict, widget_exclude_filter: list):
|
||||
providers = ["custom"]
|
||||
|
||||
for provider in providers:
|
||||
columns_defs = data_schema_to_columns_defs(openapi, widget_id, provider)
|
||||
_cat = route.split("v1/")[-1]
|
||||
_cats = _cat.split("/")
|
||||
category = _cats[0].title()
|
||||
columns_defs = data_schema_to_columns_defs(
|
||||
openapi, widget_id, provider, route
|
||||
)
|
||||
_cats = [
|
||||
r
|
||||
for r in route.split("/")
|
||||
if r and r != "api" and r[0].lower() != "v" and not r[1:].isdigit()
|
||||
]
|
||||
category = _cats[0].title() if _cats else ""
|
||||
category = category.replace("Fixedincome", "Fixed Income")
|
||||
subcat = _cats[1].title().replace("_", " ") if len(_cats) > 2 else None
|
||||
subcat = (
|
||||
_cats[1].title().replace("_", " ")
|
||||
if len(_cats) > 2
|
||||
else _cats[1].replace("_", " ").title() if len(_cats) > 1 else None
|
||||
)
|
||||
name = (
|
||||
widget_id.replace("fixedincome", "fixed income")
|
||||
.replace("_", " ")
|
||||
@@ -127,6 +257,7 @@ def build_json(openapi: dict, widget_exclude_filter: list):
|
||||
"tmx": "TMX",
|
||||
"ecb": "ECB",
|
||||
"econdb": "EconDB",
|
||||
"eia": "EIA",
|
||||
"fmp": "FMP",
|
||||
"oecd": "OECD",
|
||||
"finra": "FINRA",
|
||||
@@ -143,17 +274,39 @@ def build_json(openapi: dict, widget_exclude_filter: list):
|
||||
provider.lower(), provider.replace("_", " ").title()
|
||||
)
|
||||
|
||||
data_key = (
|
||||
"results"
|
||||
if response_schema
|
||||
and isinstance(response_schema, dict)
|
||||
and "$ref" in response_schema
|
||||
and "/OBBject" in response_schema.get("$ref", "")
|
||||
else ""
|
||||
)
|
||||
|
||||
widget_type = (
|
||||
"markdown"
|
||||
if isinstance(response_schema, dict)
|
||||
and response_schema.get("type") == "string"
|
||||
else "table"
|
||||
)
|
||||
|
||||
widget_config = {
|
||||
"name": f"{name}",
|
||||
"description": route_api["get"]["description"],
|
||||
"category": category,
|
||||
"searchCategory": category,
|
||||
"name": f"{name}" if name else route_api["get"].get("summary"),
|
||||
"description": route_api["get"].get("description", ""),
|
||||
"category": category.replace("_", " ").title(),
|
||||
"type": widget_type,
|
||||
"searchCategory": category.replace("_", " ").title(),
|
||||
"widgetId": f"{widget_id}_{provider}_obb",
|
||||
"params": modified_query_schema,
|
||||
"endpoint": route.replace("/api", "api"),
|
||||
"endpoint": (
|
||||
route.replace("/api", "api")
|
||||
if "/api" in route
|
||||
else route[1:] if route[0] == "/" else route
|
||||
),
|
||||
"runButton": False,
|
||||
"gridData": {"w": 45, "h": 15},
|
||||
"data": {
|
||||
"dataKey": "results" if response_schema else "",
|
||||
"dataKey": data_key,
|
||||
"table": {
|
||||
"showAll": True,
|
||||
},
|
||||
@@ -178,9 +331,12 @@ def build_json(openapi: dict, widget_exclude_filter: list):
|
||||
if columns_defs:
|
||||
widget_config["data"]["table"]["columnsDefs"] = columns_defs
|
||||
|
||||
# Update the widget configuration with the supplied configurations in @router.command
|
||||
# Update the widget configuration with any supplied configurations in @router.command
|
||||
if widget_config_dict:
|
||||
widget_config.update(widget_config_dict)
|
||||
widget_config = deep_merge_configs(
|
||||
widget_config,
|
||||
widget_config_dict,
|
||||
)
|
||||
|
||||
# Add the widget configuration to the widgets.json
|
||||
if widget_config["widgetId"] not in widget_exclude_filter:
|
||||
@@ -208,7 +364,7 @@ def build_json(openapi: dict, widget_exclude_filter: list):
|
||||
widget_config_chart["gridData"]["w"] = 50
|
||||
widget_config_chart["defaultViz"] = "chart"
|
||||
widget_config_chart["data"]["dataKey"] = (
|
||||
"chart.content" if response_schema else ""
|
||||
"chart.content" if data_key else ""
|
||||
)
|
||||
if widget_config_chart["widgetId"] not in widget_exclude_filter:
|
||||
widgets_json[widget_config_chart["widgetId"]] = widget_config_chart
|
||||
|
||||
2002
openbb_platform/extensions/platform_api/poetry.lock
generated
vendored
2002
openbb_platform/extensions/platform_api/poetry.lock
generated
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
[tool.poetry]
|
||||
name = "openbb-platform-api"
|
||||
version = "1.0.4"
|
||||
description = "OpenBB Platform API: Launch script and widgets builder for the OpenBB Platform API and Terminal Pro Connector."
|
||||
description = "OpenBB Platform API: Launch script and widgets builder for the OpenBB Platform API and Workspace Backend Connector."
|
||||
authors = ["OpenBB <hello@openbb.co>"]
|
||||
license = "AGPL-3.0-only"
|
||||
readme = "README.md"
|
||||
@@ -15,12 +15,8 @@ openbb-api = "openbb_platform_api.main:main"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<3.13"
|
||||
poetry = "^1.8"
|
||||
setuptools = "*"
|
||||
openbb-core = "^1.3.6"
|
||||
openbb-core = "^1.3.7"
|
||||
deepdiff = "*"
|
||||
ruff = "*"
|
||||
black = "*"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"name": "SLOOS",
|
||||
"description": "Get Senior Loan Officers Opinion Survey.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_survey_sloos_fred_obb",
|
||||
"params": [
|
||||
@@ -125,6 +126,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/sloos",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -180,6 +182,7 @@
|
||||
"name": "University Of Michigan",
|
||||
"description": "Get University of Michigan Consumer Sentiment and Inflation Expectations Surveys.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_survey_university_of_michigan_fred_obb",
|
||||
"params": [
|
||||
@@ -293,6 +296,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/university_of_michigan",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -339,6 +343,7 @@
|
||||
"name": "Economic Conditions Chicago",
|
||||
"description": "Get The Survey Of Economic Conditions For The Chicago Region.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_survey_economic_conditions_chicago_fred_obb",
|
||||
"params": [
|
||||
@@ -452,6 +457,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/economic_conditions_chicago",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -545,6 +551,7 @@
|
||||
"name": "Manufacturing Outlook Texas",
|
||||
"description": "Get The Manufacturing Outlook Survey For The Texas Region.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_survey_manufacturing_outlook_texas_fred_obb",
|
||||
"params": [
|
||||
@@ -726,6 +733,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/manufacturing_outlook_texas",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -798,6 +806,7 @@
|
||||
"name": "Nonfarm Payrolls",
|
||||
"description": "Get Nonfarm Payrolls Survey.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_survey_nonfarm_payrolls_fred_obb",
|
||||
"params": [
|
||||
@@ -897,6 +906,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/survey/nonfarm_payrolls",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -980,9 +990,10 @@
|
||||
"subCategory": "Survey"
|
||||
},
|
||||
"economy_cpi_fred_obb": {
|
||||
"name": "CPI",
|
||||
"name": "Cpi",
|
||||
"description": "Get Consumer Price Index (CPI).\n\nReturns either the rescaled index value, or a rate of change (inflation).",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_cpi_fred_obb",
|
||||
"params": [
|
||||
@@ -1274,6 +1285,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/cpi",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -1312,12 +1324,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "CPI"
|
||||
},
|
||||
"economy_balance_of_payments_fred_obb": {
|
||||
"name": "Balance Of Payments",
|
||||
"description": "Balance of Payments Reports.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_balance_of_payments_fred_obb",
|
||||
"params": [
|
||||
@@ -1549,6 +1563,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/balance_of_payments",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -1721,12 +1736,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "Balance Of Payments"
|
||||
},
|
||||
"economy_fred_search_fred_obb": {
|
||||
"name": "FRED Search",
|
||||
"name": "Fred Search",
|
||||
"description": "Search for FRED series or economic releases by ID or string.\n\nThis does not return the observation values, only the metadata.\nUse this function to find series IDs for `fred_series()`.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_fred_search_fred_obb",
|
||||
"params": [
|
||||
@@ -1839,6 +1856,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_search",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -2012,12 +2030,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "FRED Search"
|
||||
},
|
||||
"economy_fred_series_fred_obb": {
|
||||
"name": "FRED Series",
|
||||
"name": "Fred Series",
|
||||
"description": "Get data by series ID from FRED.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_fred_series_fred_obb",
|
||||
"params": [
|
||||
@@ -2197,6 +2217,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_series",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -2220,12 +2241,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "FRED Series"
|
||||
},
|
||||
"economy_fred_series_fred_obb_chart": {
|
||||
"name": "FRED Series (Chart)",
|
||||
"name": "Fred Series (Chart)",
|
||||
"description": "Get data by series ID from FRED.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "chart",
|
||||
"widgetId": "economy_fred_series_fred_obb_chart",
|
||||
"params": [
|
||||
@@ -2414,6 +2437,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_series",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 50,
|
||||
"h": 20
|
||||
@@ -2438,12 +2462,14 @@
|
||||
"source": [
|
||||
"FRED"
|
||||
],
|
||||
"subCategory": "FRED Series",
|
||||
"defaultViz": "chart"
|
||||
},
|
||||
"economy_fred_release_table_fred_obb": {
|
||||
"name": "FRED Release Table",
|
||||
"name": "Fred Release Table",
|
||||
"description": "Get economic release data by ID and/or element from FRED.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_fred_release_table_fred_obb",
|
||||
"params": [
|
||||
@@ -2456,7 +2482,7 @@
|
||||
"paramName": "release_id"
|
||||
},
|
||||
{
|
||||
"label": "Element Id",
|
||||
"label": "Element ID",
|
||||
"description": "The element ID of a specific table in the release.",
|
||||
"optional": true,
|
||||
"value": null,
|
||||
@@ -2480,6 +2506,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_release_table",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -2574,12 +2601,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "FRED Release Table"
|
||||
},
|
||||
"economy_fred_regional_fred_obb": {
|
||||
"name": "FRED Regional",
|
||||
"name": "Fred Regional",
|
||||
"description": "Query the Geo Fred API for regional economic data by series group.\n\nThe series group ID is found by using `fred_search` and the `series_id` parameter.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_fred_regional_fred_obb",
|
||||
"params": [
|
||||
@@ -2841,6 +2870,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/fred_regional",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -2895,12 +2925,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "FRED Regional"
|
||||
},
|
||||
"economy_retail_prices_fred_obb": {
|
||||
"name": "Retail Prices",
|
||||
"description": "Get retail prices for common items.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_retail_prices_fred_obb",
|
||||
"params": [
|
||||
@@ -3043,6 +3075,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/retail_prices",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -3098,12 +3131,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "Retail Prices"
|
||||
},
|
||||
"economy_pce_fred_obb": {
|
||||
"name": "PCE",
|
||||
"name": "Pce",
|
||||
"description": "Get Personal Consumption Expenditures (PCE) reports.",
|
||||
"category": "Economy",
|
||||
"type": "table",
|
||||
"searchCategory": "Economy",
|
||||
"widgetId": "economy_pce_fred_obb",
|
||||
"params": [
|
||||
@@ -3167,6 +3202,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/economy/pce",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -3253,12 +3289,14 @@
|
||||
},
|
||||
"source": [
|
||||
"FRED"
|
||||
]
|
||||
],
|
||||
"subCategory": "PCE"
|
||||
},
|
||||
"regulators_sec_cik_map_sec_obb": {
|
||||
"name": "CIK Map",
|
||||
"description": "Map a ticker symbol to a CIK number.",
|
||||
"category": "Regulators",
|
||||
"type": "table",
|
||||
"searchCategory": "Regulators",
|
||||
"widgetId": "regulators_sec_cik_map_sec_obb",
|
||||
"params": [
|
||||
@@ -3286,6 +3324,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/cik_map",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -3305,6 +3344,7 @@
|
||||
"name": "Institutions Search",
|
||||
"description": "Search SEC-regulated institutions by name and return a list of results with CIK numbers.",
|
||||
"category": "Regulators",
|
||||
"type": "table",
|
||||
"searchCategory": "Regulators",
|
||||
"widgetId": "regulators_sec_institutions_search_sec_obb",
|
||||
"params": [
|
||||
@@ -3333,6 +3373,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/institutions_search",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -3369,6 +3410,7 @@
|
||||
"name": "Schema Files",
|
||||
"description": "Use tool for navigating the directory of SEC XML schema files by year.",
|
||||
"category": "Regulators",
|
||||
"type": "table",
|
||||
"searchCategory": "Regulators",
|
||||
"widgetId": "regulators_sec_schema_files_sec_obb",
|
||||
"params": [
|
||||
@@ -3405,6 +3447,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/schema_files",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -3424,6 +3467,7 @@
|
||||
"name": "Symbol Map",
|
||||
"description": "Map a CIK number to a ticker symbol, leading 0s can be omitted or included.",
|
||||
"category": "Regulators",
|
||||
"type": "table",
|
||||
"searchCategory": "Regulators",
|
||||
"widgetId": "regulators_sec_symbol_map_sec_obb",
|
||||
"params": [
|
||||
@@ -3452,6 +3496,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/symbol_map",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -3471,6 +3516,7 @@
|
||||
"name": "RSS Litigation",
|
||||
"description": "Get the RSS feed that provides links to litigation releases concerning civil lawsuits brought by the Commission in federal court.",
|
||||
"category": "Regulators",
|
||||
"type": "table",
|
||||
"searchCategory": "Regulators",
|
||||
"widgetId": "regulators_sec_rss_litigation_sec_obb",
|
||||
"params": [
|
||||
@@ -3481,6 +3527,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/rss_litigation",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
@@ -3544,6 +3591,7 @@
|
||||
"name": "SIC Search",
|
||||
"description": "Search for Industry Titles, Reporting Office, and SIC Codes. An empty query string returns all results.",
|
||||
"category": "Regulators",
|
||||
"type": "table",
|
||||
"searchCategory": "Regulators",
|
||||
"widgetId": "regulators_sec_sic_search_sec_obb",
|
||||
"params": [
|
||||
@@ -3572,6 +3620,7 @@
|
||||
}
|
||||
],
|
||||
"endpoint": "api/v1/regulators/sec/sic_search",
|
||||
"runButton": false,
|
||||
"gridData": {
|
||||
"w": 45,
|
||||
"h": 15
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
@@ -14,7 +14,7 @@ from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.helpers import check_item
|
||||
from openbb_fred.models.series import FredSeriesFetcher
|
||||
from openbb_fred.utils.fred_helpers import CPI_COUNTRIES, all_cpi_options
|
||||
from openbb_fred.utils.fred_helpers import CPI_COUNTRIES, CpiCountries, all_cpi_options
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class FREDConsumerPriceIndexQueryParams(ConsumerPriceIndexQueryParams):
|
||||
},
|
||||
}
|
||||
|
||||
country: str = Field(
|
||||
country: Union[CpiCountries, str] = Field(
|
||||
description=QUERY_DESCRIPTIONS.get("country"),
|
||||
default="united_states",
|
||||
)
|
||||
|
||||
@@ -207,25 +207,26 @@ frequency_dict = {
|
||||
class FredRetailPricesQueryParams(RetailPricesQueryParams):
|
||||
"""FRED Retail Prices Query Parameters."""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"item": {"multiple_items_allowed": False, "choices": ALL_ITEMS},
|
||||
"country": {"multiple_items_allowed": False, "choices": ["united_states"]},
|
||||
}
|
||||
|
||||
item: AllItems = Field(
|
||||
default="fuel",
|
||||
description="The item or basket of items to query.",
|
||||
json_schema_extra={"choices": ALL_ITEMS},
|
||||
)
|
||||
country: Literal["united_states"] = Field(
|
||||
default="united_states",
|
||||
description=QUERY_DESCRIPTIONS.get("country", ""),
|
||||
json_schema_extra={"choices": "united_states"},
|
||||
)
|
||||
region: Regions = Field(
|
||||
default="all_city",
|
||||
description="The region to get average price levels for.",
|
||||
json_schema_extra={"choices": REGIONS},
|
||||
)
|
||||
frequency: Literal["annual", "quarter", "monthly"] = Field(
|
||||
default="monthly",
|
||||
description=QUERY_DESCRIPTIONS.get("frequency"),
|
||||
json_schema_extra={"choices": ["annual", "quarter", "monthly"]},
|
||||
)
|
||||
transform: Union[
|
||||
None, Literal["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]
|
||||
@@ -243,9 +244,6 @@ class FredRetailPricesQueryParams(RetailPricesQueryParams):
|
||||
cca = Continuously Compounded Annual Rate of Change
|
||||
log = Natural Log
|
||||
""",
|
||||
json_schema_extra={
|
||||
"choices": ["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("item", mode="before", check_fields=False)
|
||||
|
||||
@@ -122,7 +122,57 @@ CPI_COUNTRIES = [
|
||||
"czech_republic",
|
||||
"denmark",
|
||||
"estonia",
|
||||
"euro_area",
|
||||
"finland",
|
||||
"france",
|
||||
"germany",
|
||||
"greece",
|
||||
"hungary",
|
||||
"iceland",
|
||||
"india",
|
||||
"indonesia",
|
||||
"ireland",
|
||||
"israel",
|
||||
"italy",
|
||||
"japan",
|
||||
"korea",
|
||||
"latvia",
|
||||
"lithuania",
|
||||
"luxembourg",
|
||||
"malta",
|
||||
"mexico",
|
||||
"netherlands",
|
||||
"new_zealand",
|
||||
"norway",
|
||||
"poland",
|
||||
"portugal",
|
||||
"romania",
|
||||
"russian_federation",
|
||||
"slovak_republic",
|
||||
"slovakia",
|
||||
"slovenia",
|
||||
"south_africa",
|
||||
"spain",
|
||||
"sweden",
|
||||
"switzerland",
|
||||
"turkey",
|
||||
"united_kingdom",
|
||||
"united_states",
|
||||
]
|
||||
|
||||
CpiCountries = Literal[
|
||||
"australia",
|
||||
"austria",
|
||||
"belgium",
|
||||
"brazil",
|
||||
"bulgaria",
|
||||
"canada",
|
||||
"chile",
|
||||
"china",
|
||||
"croatia",
|
||||
"cyprus",
|
||||
"czech_republic",
|
||||
"denmark",
|
||||
"estonia",
|
||||
"finland",
|
||||
"france",
|
||||
"germany",
|
||||
|
||||
@@ -34,6 +34,10 @@ class YFinanceKeyExecutivesData(KeyExecutivesData):
|
||||
default=None,
|
||||
description="Value of shares not exercised.",
|
||||
)
|
||||
fiscal_year: Optional[int] = Field(
|
||||
default=None,
|
||||
description="Fiscal year of the pay.",
|
||||
)
|
||||
|
||||
|
||||
class YFinanceKeyExecutivesFetcher(
|
||||
|
||||
Reference in New Issue
Block a user