diff --git a/.codespell.skip b/.codespell.skip index f83dcca0908d..1c827e0dd8dc 100644 --- a/.codespell.skip +++ b/.codespell.skip @@ -1,5 +1,6 @@ ./tests .git +*.bat *.css *.csv *.html @@ -8,6 +9,7 @@ *.js *.json *.lock +*.nsi *.scss *.txt *.yaml diff --git a/build/conda/README.md b/build/conda/README.md index e521f656d442..91efeda2bc2b 100644 --- a/build/conda/README.md +++ b/build/conda/README.md @@ -1,23 +1,36 @@ # OpenBB Platform Installer -The code in this folder allows creating a packaged installer for the OpenBB Platform using conda constructor. +The code in this folder creates a packaged installer for the OpenBB Platform, using Conda Constructor. The installers can be created for Windows (.exe), MacOS (.pkg). -The installer is distributed with a conda environment that contains python and package management tools. -The installer is created by conda constructor. +The installer is distributed with a Conda environment that contains Python and package management tools. -The dependencies required to run the application are installed from pypi by the constructor's post_install script. +The dependencies required to run the application are installed from PyPI by the constructor's post_install script, +and are defined in the `requirements.txt` file. + +The version of Python is defined in the `construct.yaml` file, it is set for 3.12. + +**Conda should be installed already, and initialized in the current shell profile.** # Build Instructions -1. Create the build environment (see the [Build environment](#build-environment) section) -2. Build the installer (see the [Build command](#build-command) section) +Navigate into the `build/conda` folder and then begin. + +1. Create the Conda environment. + - `conda env create -n constructor --file environments/constructor.yml` +2. Activate the environment. + - `conda activate constructor` +3. Build the installer. + - `constructor installer/.` + + +# For when we have a full NSIS -## Build environment +To check nsis file - run -The build environment should be created off the `environments/constructor-env.yml` file. +miniconda3\\envs\\constructor\\NSIS\\makensis.exe /V2 assets\\installer.nsi -## Build command +gives better output -In the `installer` folder run `constructor .` to build the installer. +miniconda3\\envs\\constructor\\NSIS\\makensis.exe /V4 assets\\installer.nsi diff --git a/build/conda/environments/constructor.yml b/build/conda/environments/constructor.yml new file mode 100644 index 000000000000..af4e48bad001 --- /dev/null +++ b/build/conda/environments/constructor.yml @@ -0,0 +1,7 @@ +name: constructor +channels: + - conda-forge + - defaults +dependencies: + - conda-build + - constructor diff --git a/build/conda/installer/README.md b/build/conda/installer/README.md new file mode 100644 index 000000000000..5c560fb3083b --- /dev/null +++ b/build/conda/installer/README.md @@ -0,0 +1,180 @@ +# OpenBB Platform + +Thanks for installing the OpenBB Platform. To get the most out of your experience, we recommend reviewing the contents of this document. + +If you have never used Python before, don't worry, your first time is just a double-click away. The sections below outline everything you need to know for getting started. + +## What Did I Install? + +This is a kitted out development container for Pythonic, open source, financial research. It has everything you need to get started immediately. + +Included, is a complete installation of the OpenBB Platform and CLI, along with Conda and Jupyter Notebook, in an isolated Python 3.12 Conda environment. + +If you already have an installation of Conda or Anaconda, the version in this folder will not rely on, interfere, or share base environment paths with, existing versions. + +Tools for managing, updating, and building environments allow you to take full control of your installation, as well as quickly spin up new containers within this project folder. + +To get started using right away, open one of the shortcuts described below. + +If you want to build your own end points, connections, and features, try installing the [example extensions](#examples) + +## Shortcuts + +At the root of the installation folder (where this document is) are shortcuts for launchers and to key file locations. + +- Bash (Windows: CMD): Opens a command line shell with the base Conda environment activated. Activate the OpenBB environment with: + - Mac/Linux: `source activate obb` + - Windows: `conda activate obb` +- Environments: Shortcut to the root "envs" folder, where the `obb` and any created environments are stored. + - "widgets.json" is located in the "assets" subdirectory. +- openbb-api: Opens a Terminal window and launches the OpenBB API, with optional prompts to login with your OpenBB Hub PAT. +- openbb-cli: Opens a Terminal window and launches the OpenBB CLI (formerly OpenBB Terminal). +- openbb-ipython: Opens a Terminal window and starts an IPython session with the OpenBB Platform package imported. +- openbb-notebook: Opens a Terminal window, starts the Jupyter development server, and opens a browser window with the Notebook application. +- OpenBBUserData: Shortcut to the OpenBBUserData folder. Files exported from CLI are saved here. +- Settings: Shortcut to the `~/.openbb_platform` location where `.env` files and `user_settings.json` are stored. + - If you cannot see the ".env" file, set Finder/Explorer to display hidden/system files. +- Update: Opens a Terminal window and updates the OpenBB Platform environment (obb) and packages. + +## Command Line Entry Points + +There are several command line entry points available when either: +- The OpenBB Platform environment (obb) is active. +- The "openbb_platform" package has been installed in the active Python environment, including when `--no-deps` or `--only-root` flags were used to install. + +### OpenBB Entry Points + +- openbb: Launches the OpenBB CLI. +- openbb-api: Launches the API and accepts select arguments. + - All args/kwargs available to `uvicorn run` are exposed. + - `--login`: True/False. Defeats the login prompt when False. + - `--no-build`: Skips building "widgets.json" + - To launch the API with no input prompts enter: `openbb-api --login False --no-build +- openbb-build: Runs the build script that generates the static assets for the OpenBB Pythobn interface. + - Run this after installing/uninstalling/updating OpenBB extensions + - Automatically run when `openbb-update` is run. +- openbb-update: Updates the environment packages defined in `pyproject.toml` and `poetry.lock` and rebuilds the OpenBB Python interface. + - Location: "/extensions/openbb_platform" + - Passes all args/kwargs to `poetry install`. + +## Installed Folder Structure + +At the root of the installation (where this document is), there will be two folders: + +- conda (Windows: This is named after the last folder path if the default was not used) +- extensions + +### Conda + +This is the folder where Conda is installed and all environment data (site-packages, etc.) is stored. The "Environments" shortcuts takes you to the root of the "envs" folder. + +### Extensions + +This folder contains two subfolders: + +#### examples + +Contains empty example extensions for the three types of OpenBB Platform extensions: + +- Router +- Provider +- OBBject + +To install all three examples in the OpenBB Platform environment, open the Bash/CMD shortcut and enter: + +```console +source activate obb # CMD: conda activate obb +cd extensions/examples +python install_examples.py +``` + +#### openbb_platform + +This is a meta package for installing and managing OpenBB Platform installation within any Python environment. +Poetry is used to update and resolve any dependencies that are defined in the `pyproject.toml` file. + +The `poetry.lock` file is updated every time the `Update` shortcut is run. + +There are several configurations available, via combinations of `--with`, `--without`, and `--only` parameters, when running `poetry install`. + +Choices are: + +- main +- openbb-all +- cli +- notebook + +Each parameter will accept multiple items, but `--without` takes priority for solving for the environment. + +## Uninstallation + +Mac/Linux users can uninstall by deleting the folder, while Windows users can run the Uninstall shortcut. + +The leftover artifacts will include: + +- Global configuration files stored by Poetry, PIP, Conda, and Jupyter. +- Third-party Python package caches and `.env` files. +- Folders: + - ~/.openbb_platform + - ~/OpenBBUserData + +Windows users will also need to remove the parent folder where it was originally installed, after running Uninstall. + +Your OpenBB Hub account can be deleted, along with all associated data, from the Account page in [my.openbb.co](https://my.openbb.co) + +## Additional Information + +### Conda Install vs. PIP Install vs. Poetry Install + +The three ways to install packages may appear to accomplish the same objectives - installing Python packages - but each has a distinctly different purpose. + +An easy way to think about the difference is, both `pip` and `poetry` require an existing Python installation; `conda` installs Python so that you can run `pip install poetry`. + +In general, you use: + +- `conda` + - When a new environment has been created. Packages here can be included in the creation command, `conda create`. + - When the specific package is only distributed through Conda or the `conda-forge` channel. + - When changing the version of Python used within the environment. +- `pip` + - When installing/uninstalling packages to the activated Python environment. + - Resort to `conda install` only if packages fail to install or wheels cannot be built. +- `poetry` + - Installing a local Python package. + - Syncing all package dependencies according to provided specs in `pyproject.toml` and `poetry.lock` files. + - Building and distributing Python packages. + +Conda is for container management, Poetry resolves the project's dependencies and provides build/distribution tools, while `pip` is for installing from [PyPI](https://pypi.org) + +### Documentation Resources + +### OpenBB +- [Quckstart](https://docs.openbb.co/platform/getting_started/quickstart) +- [OpenBB Pro](https://docs.openbb.co/pro) +- [OpenBB Pro Data Connectors](https://docs.openbb.co/pro/data-connectors) +- [OpenBB Platform](https://docs.openbb.co/platform) +- [OpenBB CLI](https://docs.openbb.co/cli) +- [OpenBB User Settings](https://docs.openbb.co/platform/user_guides/settings_and_environment_variables) +- [API Keys and Authorization](https://docs.openbb.co/platform/getting_started/api_keys) +- [REST API Requests](https://docs.openbb.co/platform/getting_started/api_requests) +- [OpenBB Platform Architecture Overview](https://docs.openbb.co/platform/developer_guide/architecture_overview) +- [Create A New Router Extension](https://docs.openbb.co/platform/getting_started/create_new_router_extension) +- [OpenBB Charting]() +- [PyWry](https://github.com/OpenBB-finance/pywry) + +### External + +- [python](https://docs.python.org/3/) +- [conda create](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#managing-environments) +- [pyproject.toml](https://python-poetry.org/docs/pyproject/) +- [requirements.txt](https://pip.pypa.io/en/stable/reference/requirements-file-format/) +- [pip](https://pip.pypa.io/en/stable/) +- [poetry config](https://python-poetry.org/docs/configuration) +- [pydantic](https://docs.pydantic.dev/latest/) +- [uvicorn](https://www.uvicorn.org/) +- [fastapi](https://fastapi.tiangolo.com/) +- [jupyter](https://docs.jupyter.org/en/latest/) +- [pandas](https://pandas.pydata.org/docs/) +- [numpy](https://numpy.org/doc/) +- [Plotly Graph Objects](https://plotly.com/python/graph-objects/) + diff --git a/build/conda/installer/assets/Installer_horizontal.bmp b/build/conda/installer/assets/Installer_horizontal.bmp new file mode 100644 index 000000000000..7de270256996 Binary files /dev/null and b/build/conda/installer/assets/Installer_horizontal.bmp differ diff --git a/build/conda/installer/assets/Installer_horizontal2.png b/build/conda/installer/assets/Installer_horizontal2.png new file mode 100644 index 000000000000..2261c710255e Binary files /dev/null and b/build/conda/installer/assets/Installer_horizontal2.png differ diff --git a/build/conda/installer/assets/Installer_vertical2.bmp b/build/conda/installer/assets/Installer_vertical2.bmp new file mode 100644 index 000000000000..54f9b618845d Binary files /dev/null and b/build/conda/installer/assets/Installer_vertical2.bmp differ diff --git a/build/conda/installer/assets/create_shortcut.vbs b/build/conda/installer/assets/create_shortcut.vbs index acf9d3ec4b01..fd3eb1b5e12e 100644 --- a/build/conda/installer/assets/create_shortcut.vbs +++ b/build/conda/installer/assets/create_shortcut.vbs @@ -1,16 +1,70 @@ Set objShell = CreateObject("WScript.Shell") -sDesktopPath = objShell.SpecialFolders("Desktop") +sdesktopPath = objShell.SpecialFolders("Desktop") -' Create shortcut for openbb-cli -Set objShortcut = objShell.CreateShortcut(sDesktopPath & "\openbb-cli.lnk") -objShortcut.TargetPath = objShell.ExpandEnvironmentStrings("%USERPROFILE%") & "\openbb\Scripts\openbb-cli.exe" -objShortcut.IconLocation = objShell.ExpandEnvironmentStrings("%USERPROFILE%") & "\openbb\assets\openbb.ico" -objShortcut.Description = "OpenBB CLI launcher" +Set fso = CreateObject("Scripting.FileSystemObject") +prefixPath = objShell.ExpandEnvironmentStrings("%PREFIX%") +userProfilePath = objShell.ExpandEnvironmentStrings("%USERPROFILE%") +currentPath = fso.GetAbsolutePathName(prefixPath) +parentFolder = fso.GetParentFolderName(currentPath) +shortcutFolder = parentFolder + +Set objFSO = CreateObject("Scripting.FileSystemObject") +If Not objFSO.FolderExists(shortcutFolder) Then + objFSO.CreateFolder(shortcutFolder) +End If + +If prefixPath = "%PREFIX%" Or userProfilePath = "%USERPROFILE%" Then + WScript.Echo "Environment variables %PREFIX% or %USERPROFILE% are not set correctly." + WScript.Quit 1 +End If + +Sub CreateShortcut(name, targetPath, iconPath) + Set objShell = CreateObject("WScript.Shell") + Set objShortcut = objShell.CreateShortcut(shortcutFolder & "\" & name & ".lnk") + objShortcut.TargetPath = objShell.ExpandEnvironmentStrings(targetPath) + objShortcut.IconLocation = objShell.ExpandEnvironmentStrings(iconPath) + objShortcut.Description = name + objShortcut.Save +End Sub + +CreateShortcut "openbb-cli", "C:\Windows\System32\cmd.exe", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "openbb-api", "C:\Windows\System32\cmd.exe", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "openbb-notebook", "C:\Windows\System32\cmd.exe", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "openbb-ipython", "C:\Windows\System32\cmd.exe", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "Update", "C:\Windows\System32\cmd.exe", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "CMD", "C:\Windows\System32\cmd.exe", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "Environments", prefixPath & "\envs", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "Settings", userProfilePath & "\.openbb_platform", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "OpenBBUserData", userProfilePath & "\OpenBBUserData", prefixPath & "\assets\openbb_icon.ico" +CreateShortcut "Uninstall", prefixPath & "\Uninstall-OpenBB.exe", prefixPath & "\assets\openbb_icon.ico" + +Set objShortcut = objShell.CreateShortcut(shortcutFolder & "\openbb-notebook.lnk") +objShortcut.Arguments = "/k PATH " & prefixPath & ";" & prefixPath & "\Scripts;" & prefixPath & "\Library\bin;%PATH% && activate " & prefixPath & "\envs\obb && cd " & userProfilePath & " && jupyter-notebook && exit" +objShortcut.WorkingDirectory = prefixPath +objShortcut.Save + +Set objShortcut = objShell.CreateShortcut(shortcutFolder & "\openbb-ipython.lnk") +objShortcut.Arguments = "/k PATH " & prefixPath & ";" & prefixPath & "\Scripts;" & prefixPath & "\Library\bin;%PATH% && activate " & prefixPath & "\envs\obb && ipython -c ""from openbb import obb;obb"" -i" +objShortcut.WorkingDirectory = prefixPath objShortcut.Save -' Create shortcut for openbb-api -Set objShortcut = objShell.CreateShortcut(sDesktopPath & "\openbb-api.lnk") -objShortcut.TargetPath = objShell.ExpandEnvironmentStrings("%USERPROFILE%") & "\openbb\Scripts\openbb-api.exe" -objShortcut.IconLocation = objShell.ExpandEnvironmentStrings("%USERPROFILE%") & "\openbb\assets\openbb.ico" -objShortcut.Description = "OpenBB API launcher" +Set objShortcut = objShell.CreateShortcut(shortcutFolder & "\openbb-api.lnk") +objShortcut.Arguments = "/k PATH " & prefixPath & ";" & prefixPath & "\Scripts;" & prefixPath & "\Library\bin;%PATH% && activate " & prefixPath & "\envs\obb && call openbb-api --login && exit" +objShortcut.WorkingDirectory = prefixPath objShortcut.Save + + +Set objShortcut = objShell.CreateShortcut(shortcutFolder & "\Update.lnk") +objShortcut.Arguments = "/k PATH " & prefixPath & ";" & prefixPath & "\Scripts;" & prefixPath & "\Library\bin;%PATH% && activate " & prefixPath & "\envs\obb && call openbb-update && exit" +objShortcut.WorkingDirectory = prefixPath +objShortcut.Save + +Set objShortcut = objShell.CreateShortcut(shortcutFolder & "\CMD.lnk") +objShortcut.Arguments = "/k PATH " & prefixPath & ";" & prefixPath & "\Scripts;" & prefixPath & "\Library\bin;%PATH% && activate base && echo Conda base environment is active. Use this shell to create new environments. && echo To activate the OpenBB environment, run 'conda activate obb'." +objShortcut.WorkingDirectory = shortcutFolder +objShortcut.Save + +Set objShortcut = objShell.CreateShortcut(shortcutFolder & "\openbb-cli.lnk") +objShortcut.Arguments = "/k PATH " & prefixPath & ";" & prefixPath & "\Scripts;" & prefixPath & "\Library\bin;%PATH% && activate " & prefixPath & "\envs\obb && call openbb && exit" +objShortcut.WorkingDirectory = prefixPath +objShortcut.Save \ No newline at end of file diff --git a/build/conda/installer/assets/custom_conclusion.nsi b/build/conda/installer/assets/custom_conclusion.nsi new file mode 100644 index 000000000000..ec0839a44d1f --- /dev/null +++ b/build/conda/installer/assets/custom_conclusion.nsi @@ -0,0 +1,86 @@ + + +Page Custom muiExtraPages_Create + +!include "FileFunc.nsh" + +var IntroText +var InstallationLink +var ExampleImg +var ExampleImgCtl +var PARENTDIR + +Function muiExtraPages_Create + Push $0 + + !insertmacro MUI_HEADER_TEXT_PAGE \ + "${PRODUCT_NAME}" \ + "Installation Successfully Complete" + + ${GetParent} "$INSTDIR" $PARENTDIR + + nsDialogs::Create /NOUNLOAD 1018 + ${NSD_CreateLabel} 10u 10u 280u 40u "Click the link below to open the installation folder:" + Pop $IntroText + + ${NSD_CreateLink} 10u 55u 200u 10u $PARENTDIR + Pop $InstallationLink + ${NSD_OnClick} $InstallationLink LaunchLinkOne + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS}|${SS_BITMAP}|${SS_REALSIZECONTROL} 0 10u 90u 280u 40u "" + Pop $ExampleImgCtl + StrCpy $0 $PLUGINSDIR\openbb_win.png + System::Call 'user32::LoadImage(i 0, t r0, i ${IMAGE_BITMAP}, i 0, i 0, i ${LR_LOADFROMFILE}|${LR_LOADTRANSPARENT}|${LR_LOADMAP3DCOLORS}) i.s' + Pop $ExampleImg + SendMessage $ExampleImgCtl ${STM_SETIMAGE} ${IMAGE_BITMAP} $ExampleImg + + nsDialogs::Show + + System::Call 'gdi32:DeleteObject(i $ExampleImg)' + + Pop $0 +FunctionEnd + +!define MUI_FINISHPAGE_TEXT "Select a resource to open.$\r$\n$\r$\n" +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_TEXT "OpenBB Platform Documentation" +!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLinkTwo" +!define MUI_PAGE_CUSTOMFUNCTION_SHOW MyFinishShow +!define MUI_PAGE_CUSTOMFUNCTION_LEAVE MyFinishLeave + +var CheckboxLinkThree +var CheckboxLinkFour + +Function LaunchLinkOne + ExecShell "open" "$PARENTDIR" +FunctionEnd + +Function LaunchLinkTwo + ExecShell "open" "https://docs.openbb.co/platform" +FunctionEnd + +Function MyFinishShow + ${NSD_CreateCheckbox} 120u 110u 100% 10u "OpenBB CLI Documentation" + Pop $CheckboxLinkThree + ${NSD_Check} $CheckboxLinkThree + SetCtlColors $CheckboxLinkThree "" "ffffff" + + ${NSD_CreateCheckbox} 120u 130u 100% 10u "OpenBB Pro Documentation" + Pop $CheckboxLinkFour + ${NSD_Check} $CheckboxLinkFour + SetCtlColors $CheckboxLinkFour "" "ffffff" +FunctionEnd + +Function MyFinishLeave + ${NSD_GetState} $CheckboxLinkThree $0 + ${If} $0 <> 0 + ExecShell "open" "https://docs.openbb.co/cli" + ${EndIf} + + ${NSD_GetState} $CheckboxLinkFour $0 + ${If} $0 <> 0 + ExecShell "open" "https://docs.openbb.co/pro" + ${EndIf} +FunctionEnd + +!insertmacro MUI_PAGE_FINISH \ No newline at end of file diff --git a/build/conda/installer/assets/custom_welcome.nsi b/build/conda/installer/assets/custom_welcome.nsi new file mode 100644 index 000000000000..5669910b50e2 --- /dev/null +++ b/build/conda/installer/assets/custom_welcome.nsi @@ -0,0 +1,41 @@ +# Below is an example of creating multiple pages after the welcome page of the installer. +# +# This file contains code that is inserted where the @CUSTOM_WELCOME_FILE@ is located +# in the main.nsi.tmpl. The main mechanism for extra pages occurs with the +# "Page Custom muiExtraPagesAfterWelcome_Create" line, which +# references the function "muiExtraPagesAfterWelcome_Create" for page creation. + +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageIfUACInnerInstance +!insertmacro MUI_PAGE_WELCOME + +Page Custom muiExtraPagesAfterWelcome_Create + +var IntroAfterWelcomeText +var InstallationAfterWelcomeLink +var ExampleAfterWelcomeImg +var ExampleImgAfterWelcomeCtl + +Function muiExtraPagesAfterWelcome_Create + Push $0 + + !insertmacro MUI_HEADER_TEXT_PAGE \ + "${PRODUCT_NAME}" \ + "Welcome to OpenBB Platform Installer" + + nsDialogs::Create /NOUNLOAD 1018 + ${NSD_CreateLabel} 10u 10u 280u 120u "Welcome to the OpenBB Platform Installer.$\r$\n$\r$\nThis application will install the latest version of the OpenBB Platform on your computer as a self-contained, Python 3.12, Conda environment.$\r$\n$\r$\nInstallation requires between 1-2 GB of storage space, with a minimum of 4GB RAM.$\r$\n$\r$\nIn order to install you need access to the Internet.$\r$\n$\r$\nThe selected installation path requires a depth of two - i.e, User/MyUserAccount/OpenBB/conda, where OpenBB is the target folder, and conda is the name of the folder where Conda and the installed environments live. The path, including username, should not contain any spaces." + Pop $IntroAfterWelcomeText + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS}|${SS_BITMAP}|${SS_REALSIZECONTROL} 0 10u 90u 280u 40u "" + Pop $ExampleImgAfterWelcomeCtl + StrCpy $0 $PLUGINSDIR\openbb_win.png + System::Call 'user32::LoadImage(i 0, t r0, i ${IMAGE_BITMAP}, i 0, i 0, i ${LR_LOADFROMFILE}|${LR_LOADTRANSPARENT}|${LR_LOADMAP3DCOLORS}) i.s' + Pop $ExampleAfterWelcomeImg + SendMessage $ExampleImgAfterWelcomeCtl ${STM_SETIMAGE} ${IMAGE_BITMAP} $ExampleAfterWelcomeImg + + nsDialogs::Show + + System::Call 'gdi32:DeleteObject(i $ExampleAfterWelcomeImg)' + + Pop $0 +FunctionEnd \ No newline at end of file diff --git a/build/conda/installer/assets/dmg_volume.icns b/build/conda/installer/assets/dmg_volume.icns new file mode 100644 index 000000000000..255d73c02aa0 Binary files /dev/null and b/build/conda/installer/assets/dmg_volume.icns differ diff --git a/build/conda/installer/assets/examples/README.md b/build/conda/installer/assets/examples/README.md new file mode 100644 index 000000000000..92b997508da2 --- /dev/null +++ b/build/conda/installer/assets/examples/README.md @@ -0,0 +1,36 @@ +# Example Extension Templates + +The extensions in this folder represent starting points for creating your own OpenBB extensions. The types of extensions that can be created are: + +- Router + - Create a new router path and define the functions for the app. +- Provider + - Add a new data provider source. +- OBBject + - Extend the response object returned by every command. + +## Installation + +To install all example extensions, activate the environment, then run: + +```console +python install_examples.py +``` + +To install an individual extension to the existing OpenBB environment, activate it, then navigate into the folder of the desired extension and enter: + +```console +poetry install --only-root +``` + +Then, rebuild the OpenBB Python interface with: + +```console +openbb-build +``` + +The new extension(s) will be available by importing the OpenBB package. + +```python +from openbb import obb +``` diff --git a/build/conda/installer/assets/examples/empty_obbject/README.md b/build/conda/installer/assets/examples/empty_obbject/README.md new file mode 100644 index 000000000000..d5f24806ed72 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_obbject/README.md @@ -0,0 +1,9 @@ +# Empty OBBject Extension + +This is an empty OpenBB Platform OBBJECT (Response Object) Extension. + +Install this extension locally with: + +```console +poetry install --only-root +``` \ No newline at end of file diff --git a/build/conda/installer/assets/examples/empty_obbject/empty_obbject/__init__.py b/build/conda/installer/assets/examples/empty_obbject/empty_obbject/__init__.py new file mode 100644 index 000000000000..4e8d88a52e06 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_obbject/empty_obbject/__init__.py @@ -0,0 +1,30 @@ +"""Empty OBBject extension.""" + +import warnings + +from openbb_core.app.model.extension import Extension +from openbb_core.app.model.obbject import OBBject + +warnings.filterwarnings( + "ignore", lineno=0 +) # This suppresses a warning you might see with IPython and OBBject Extensions on import and initialize. + +ext = Extension(name="empty") + +# OBBject Accessors are used to extend the functionality of the OBBject class. +# The name given to the Extension creates a new namespace in every output object of the Router. +# This is useful for formatting/processing the output of the function calls, where universal application is desired. + + +@ext.obbject_accessor +class Empty: + """An Empty OBBject extension.""" + + def __init__(self, obbject): + """Creates an instance of the Empty OBBject extension.""" + self._obbject: OBBject = obbject + + @staticmethod + def hello(): + """Print a greeting message.""" + print("Hello from the Empty OBBject extension!") # noqa: T201 diff --git a/build/conda/installer/assets/examples/empty_obbject/pyproject.toml b/build/conda/installer/assets/examples/empty_obbject/pyproject.toml new file mode 100644 index 000000000000..db8f7507167a --- /dev/null +++ b/build/conda/installer/assets/examples/empty_obbject/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "empty-obbject" +version = "0.0.1" +description = "An empty OBBject extension" +authors = ["OpenBB Team "] +readme = "README.md" +packages = [{ include = "empty_obbject" }] + +[tool.poetry.dependencies] +python = "^3.9,<3.13" +openbb-core = "^1.3.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.plugins."openbb_obbject_extension"] +empty = "empty_obbject:ext" diff --git a/build/conda/installer/assets/examples/empty_provider/README.md b/build/conda/installer/assets/examples/empty_provider/README.md new file mode 100644 index 000000000000..1c6572627501 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_provider/README.md @@ -0,0 +1,9 @@ +# Empty Provider Extension + +This is an empty OpenBB Platform Provider Extension. + +Install this extension locally with: + +```console +poetry install --only-root +``` \ No newline at end of file diff --git a/build/conda/installer/assets/examples/empty_provider/empty_provider/__init__.py b/build/conda/installer/assets/examples/empty_provider/empty_provider/__init__.py new file mode 100644 index 000000000000..b1010176fea1 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_provider/empty_provider/__init__.py @@ -0,0 +1,21 @@ +"""Empty Provider Module.""" + +# Import the fetchers from each model. +from openbb_core.provider.abstract.provider import Provider + +from empty_provider.models.empty_model import EmptyFetcher + +empty_provider = Provider( + name="empty", + website="http://empty.io", + description="""The empty provider is a supplier of promises.""", + # credentials=["api_key"], # Credentials added here are mapped to `user_settings.json` in the `credentials` key. + fetcher_dict={ + "Empty": EmptyFetcher # The key is the name of the model defined in the @router decorator. + }, +) + +# Every provider follows this same pattern, so it is possible to import the fetchers from other providers +# and map them to the fetcher_dict above. +# from openbb_yfinance import yfinance_provider +# yfinance_fetchers = yfinance_provider.fetcher_dict.copy() diff --git a/build/conda/installer/assets/examples/empty_provider/empty_provider/models/__init__.py b/build/conda/installer/assets/examples/empty_provider/empty_provider/models/__init__.py new file mode 100644 index 000000000000..b62c0791abc6 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_provider/empty_provider/models/__init__.py @@ -0,0 +1 @@ +"""Empty Provider Models.""" diff --git a/build/conda/installer/assets/examples/empty_provider/empty_provider/models/empty_model.py b/build/conda/installer/assets/examples/empty_provider/empty_provider/models/empty_model.py new file mode 100644 index 000000000000..984b98c8972a --- /dev/null +++ b/build/conda/installer/assets/examples/empty_provider/empty_provider/models/empty_model.py @@ -0,0 +1,106 @@ +"""Empty Fetcher Model. This model is used in conjunction with the 'empty_router' extension.""" + +# pylint: disable=unused-import +# flake8: noqa: F401 + +from datetime import ( + date as dateType, + datetime, +) +from typing import Any, Dict, Optional + +from openbb_core.provider.abstract.data import Data +from openbb_core.provider.abstract.fetcher import Fetcher +from openbb_core.provider.abstract.query_params import QueryParams +from pydantic import BaseModel, Field + + +class Address(BaseModel): + """Address Model.""" + + city: str = Field( + default="Atlanta", + description="City", + ) + state: str = Field( + default="GA", + description="State", + ) + + +class EmptyQueryParams(QueryParams): + """Empty Query Params""" + + some_param: Dict[Any, Any] = Field( + default=Address().model_dump(), + description="Some param", + ) + + +class EmptyData(Data): + """Empty Data""" + + date: Optional[dateType] = Field( + default=None, + description="Date of the data.", + ) + title: Optional[str] = Field( + default=None, + description="Title of the data.", + ) + + +class EmptyFetcher( + Fetcher[ + EmptyQueryParams, + EmptyData, # Change the Typing when returning a list of models - i.e, records. + ] +): + """Empty Fetcher.""" + + @staticmethod + def transform_query(params: Dict[str, Any]) -> EmptyQueryParams: + """Transform query params.""" + transformed_params = params.copy() + # if transformed_params.get("some_param"): + # do something + # + # This is where you can set default values for query parameters. + # Essentially, `@model_validate(mode='before')`. + return EmptyQueryParams(**transformed_params) + + @staticmethod + async def aextract_data( + query: EmptyQueryParams, + credentials: Optional[Dict[str, str]], + **kwargs: Any, + ) -> Dict: # Typing here should match the 'data' input of 'transform_data'. + """Extract data.""" + # pylint: disable=import-outside-toplevel + #from openbb_core.provider.utils.helpers import ( + # make_request, + # amake_request, + # amake_requests, + # get_querystring, + #) Use these to make HTTP requests. + + # We import here so that items are imported on execution, not on initialization. + # Critical for modules that introduce a heavy load on the system. + # Generally, any module required for data retrieval and parsing should be 'lazy' imported. + # This is to ensure that the module is only imported when needed. + + # print(query.some_param) + results = { + "date": datetime.now().date(), + "title": "Hello from the Empty Provider extension!", + } + return results + + @staticmethod + def transform_data( + query: EmptyQueryParams, + data: Dict, # Typing here matches the output of '(a)extract_data'. + **kwargs: Any, + ) -> EmptyData: # Typing here matches the Fetcher's definition. + """Transform data.""" + return EmptyData.model_validate(data) diff --git a/build/conda/installer/assets/examples/empty_provider/empty_provider/utils/__init__.py b/build/conda/installer/assets/examples/empty_provider/empty_provider/utils/__init__.py new file mode 100644 index 000000000000..da3e91ec2bdf --- /dev/null +++ b/build/conda/installer/assets/examples/empty_provider/empty_provider/utils/__init__.py @@ -0,0 +1 @@ +"""Utilities and Helpers.""" diff --git a/build/conda/installer/assets/examples/empty_provider/pyproject.toml b/build/conda/installer/assets/examples/empty_provider/pyproject.toml new file mode 100644 index 000000000000..082e212fe49a --- /dev/null +++ b/build/conda/installer/assets/examples/empty_provider/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "empty-provider" +version = "0.0.1" +description = "Empty provider extension for OpenBB" +authors = ["OpenBB Team "] +readme = "README.md" +packages = [{ include = "empty_provider" }] + +[tool.poetry.dependencies] +python = "^3.9,<3.13" +openbb-core = "^1.3.0" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.plugins."openbb_provider_extension"] +empty = "empty_provider:empty_provider" diff --git a/build/conda/installer/assets/examples/empty_router/README.md b/build/conda/installer/assets/examples/empty_router/README.md new file mode 100644 index 000000000000..e10523ea5805 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_router/README.md @@ -0,0 +1,9 @@ +# Empty Router Extension + +This is an empty OpenBB Platform Router Extension. + +Install this extension locally with: + +```console +poetry install --only-root +``` \ No newline at end of file diff --git a/build/conda/installer/assets/examples/empty_router/empty_router/__init__.py b/build/conda/installer/assets/examples/empty_router/empty_router/__init__.py new file mode 100644 index 000000000000..100f486d1016 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_router/empty_router/__init__.py @@ -0,0 +1 @@ +"""An Empty OpenBB Router extension.""" diff --git a/build/conda/installer/assets/examples/empty_router/empty_router/empty_router.py b/build/conda/installer/assets/examples/empty_router/empty_router/empty_router.py new file mode 100644 index 000000000000..705e35bc709b --- /dev/null +++ b/build/conda/installer/assets/examples/empty_router/empty_router/empty_router.py @@ -0,0 +1,53 @@ +"""Empty Router Extenision for OpenBB Platform.""" + +from datetime import datetime + +from openbb_core.app.model.command_context import CommandContext +from openbb_core.app.model.example import APIEx, PythonEx +from openbb_core.app.model.obbject import OBBject +from openbb_core.app.provider_interface import ( + ExtraParams, + ProviderChoices, + StandardParams, +) +from openbb_core.app.query import Query +from openbb_core.app.router import Router +from openbb_core.provider.abstract.data import Data + +router = Router(prefix="", description="An Empty OpenBB Router Extension.") + +# The first function call will take longer than the rest, as modules are loaded on the first call. + +# Note that all routers must return an instance of `OBBject`, where the output is the `results` attribute. + + +# This is a standard router "get" command. +@router.command(methods=["GET"]) +async def hello() -> ( + OBBject[dict] +): # The output of every router command must be an instance of `OBBject`. + """OpenBB Hello World.""" + return OBBject(results={datetime.now().strftime("%Y-%m-%d"):"Hello from the Empty Router extension!"}) + + +# This uses the Provider Interface to call the empty provider fetcher. +@router.command( + model="Empty", + examples=[ + APIEx(parameters={"provider": "empty"}), + PythonEx( + description="Say Hello.", + code=[ + "result = obb.empty.hello()", + ], + ), + ], +) +async def empty_function( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject[Data]: + """An empty function using the Provider Interface.""" + return await OBBject.from_query(Query(**locals())) diff --git a/build/conda/installer/assets/examples/empty_router/empty_router/empty_views.py b/build/conda/installer/assets/examples/empty_router/empty_router/empty_views.py new file mode 100644 index 000000000000..aa9fa999b7d7 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_router/empty_router/empty_views.py @@ -0,0 +1,29 @@ +"""Views for the Empty Extension.""" + +# pylint: disable=unused-import +# flake8: noqa: F401 + +from typing import TYPE_CHECKING, Any, Dict, Tuple + +if TYPE_CHECKING: + from openbb_charting.core.openbb_figure import OpenBBFigure + +# If `openbb-charting` was installed, this will be used to create a chart. +# Process the data by accessing, "kwargs["obbject_item"]", the function results. +# The return is a Tuple of the OpenBBFigure and a Dict of the data, so it can be +# returned to the Fast API endpoint. Use `fig.to_plotly_json()` to convert the +# OpenBBFigure to a JSON serializable object. + + +class EmptyViews: + """Empty Views.""" + + # @staticmethod + # def empty_hello( # noqa: PLR0912 + # **kwargs, + # ) -> Tuple["OpenBBFigure", Dict[str, Any]]: + # """Get Derivatives Price Historical Chart.""" + # pylint: disable=import-outside-toplevel + # from openbb_charting.charts.price_historical import price_historical + + # return price_historical(**kwargs) diff --git a/build/conda/installer/assets/examples/empty_router/pyproject.toml b/build/conda/installer/assets/examples/empty_router/pyproject.toml new file mode 100644 index 000000000000..84c061daa101 --- /dev/null +++ b/build/conda/installer/assets/examples/empty_router/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "empty-router" +version = "0.0.1" +description = "An empty OpenBB Router extension" +authors = ["OpenBB Team "] +readme = "README.md" +packages = [{ include = "empty_router" }] + +[tool.poetry.dependencies] +python = "^3.9,<3.13" +openbb-core = "^1.3.1" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.plugins."openbb_core_extension"] +empty = "empty_router.empty_router:router" diff --git a/build/conda/installer/assets/examples/install_examples.py b/build/conda/installer/assets/examples/install_examples.py new file mode 100644 index 000000000000..35e2d175af25 --- /dev/null +++ b/build/conda/installer/assets/examples/install_examples.py @@ -0,0 +1,39 @@ +"""Script to install the example extensions in develop mode to the current Python environment.""" + +def main(): + """Run the setup script.""" + # pylint: disable=import-outside-toplevel + import glob + import os + import subprocess + from pathlib import Path + + try: + import openbb # noqa: F401 # pylint: disable=unused-import + except ImportError: + raise ImportError( + "OpenBB is not installed. Please install the 'openbb_platform' package before running this script." + "\nTo install OpenBB, navigate to the 'extensions/openbb_platform' directory and run:" + "poetry install --only main" + ) + + base_dir = Path(__file__).parent + + directories = [ + os.path.join(base_dir, d) + for d in os.listdir(base_dir) + if os.path.isdir(os.path.join(base_dir, d)) and glob.glob(os.path.join(base_dir, d, "*.toml")) + ] + + for directory in directories: + subprocess.check_call([os.sys.executable, "-m", "poetry", "install", "-C", directory, "--only-root"]) # noqa: S603 + + subprocess.check_call([os.sys.executable, "openbb-build"]) # noqa: S603 + + print( # noqa: T201 + "\nExample extensions have been installed and are ready-to-use." + "\nTo connect the examples to OpenBB Pro and edit the code live, run: openbb-api --reload\n" + ) + +if __name__ == "__main__": + main() diff --git a/build/conda/installer/assets/installer.nsi b/build/conda/installer/assets/installer.nsi new file mode 100644 index 000000000000..65506d5e8a08 --- /dev/null +++ b/build/conda/installer/assets/installer.nsi @@ -0,0 +1,264 @@ +;-------------------------------- +; Includes + + !include "MUI2.nsh" + !include "logiclib.nsh" + + +#if enable_debugging is True +# Special logging build needed for ENABLE_LOGGING +# See https://nsis.sourceforge.io/Special_Builds +!define ENABLE_LOGGING=True +#endif + +# Comes from https://nsis.sourceforge.io/Logging:Enable_Logs_Quickly +!define LogSet "!insertmacro LogSetMacro" +!macro LogSetMacro SETTING + !ifdef ENABLE_LOGGING + LogSet ${SETTING} + !endif +!macroend + +!define LogText "!insertmacro LogTextMacro" +!macro LogTextMacro INPUT_TEXT + !ifdef ENABLE_LOGGING + LogText ${INPUT_TEXT} + !endif +!macroend + +;-------------------------------- +; Custom defines + !define NAME "OpenBB Platform" + !define COMPANY "OpenBB" + !define VERSION "1.0.0" + !define SLUG "${NAME} v${VERSION}" + +;-------------------------------- +; General + Name "${NAME}" + InstallDir "${PROFILE}\OpenBB" + RequestExecutionLevel user + +;-------------------------------- +; UI + + !define MUI_ICON "assets\openbb_icon.ico" + !define MUI_UNICON "assets\openbb_icon.ico" + !define MUI_HEADERIMAGE + !define MUI_WELCOMEFINISHPAGE_BITMAP "assets\installer_vertical2.bmp" + !define MUI_HEADERIMAGE_BITMAP "assets\installer_horizontal.bmp" + !define MUI_ABORTWARNING + !define MUI_WELCOMEPAGE_TITLE "${SLUG} Setup" + !define UninstId "OpenBBPlatform" ; You might want to use a GUID here + +;-------------------------------- +; Pages + + ; Installer pages + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_LICENSE "assets\installer_license.txt" +; !insertmacro MUI_PAGE_COMPONENTS + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + + ; Uninstaller pages + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + + ; Set UI language + !insertmacro MUI_LANGUAGE "English" + + + ; Var /GLOBAL installerPath + + + + + +; StrContains +; This function does a case sensitive searches for an occurrence of a substring in a string. +; It returns the substring if it is found. +; Otherwise it returns null(""). +; Written by kenglish_hi +; Adapted from StrReplace written by dandaman32 + + +Var STR_HAYSTACK +Var STR_NEEDLE +Var STR_CONTAINS_VAR_1 +Var STR_CONTAINS_VAR_2 +Var STR_CONTAINS_VAR_3 +Var STR_CONTAINS_VAR_4 +Var STR_RETURN_VAR + +Function StrContains + Exch $STR_NEEDLE + Exch 1 + Exch $STR_HAYSTACK + ; Uncomment to debug + ;MessageBox MB_OK 'STR_NEEDLE = $STR_NEEDLE STR_HAYSTACK = $STR_HAYSTACK ' + StrCpy $STR_RETURN_VAR "" + StrCpy $STR_CONTAINS_VAR_1 -1 + StrLen $STR_CONTAINS_VAR_2 $STR_NEEDLE + StrLen $STR_CONTAINS_VAR_4 $STR_HAYSTACK + loop: + IntOp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_1 + 1 + StrCpy $STR_CONTAINS_VAR_3 $STR_HAYSTACK $STR_CONTAINS_VAR_2 $STR_CONTAINS_VAR_1 + StrCmp $STR_CONTAINS_VAR_3 $STR_NEEDLE found + StrCmp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_4 done + Goto loop + found: + StrCpy $STR_RETURN_VAR $STR_NEEDLE + Goto done + done: + Pop $STR_NEEDLE ;Prevent "invalid opcode" errors and keep the + Exch $STR_RETURN_VAR +FunctionEnd + +!macro _StrContainsConstructor OUT NEEDLE HAYSTACK + Push `${HAYSTACK}` + Push `${NEEDLE}` + Call StrContains + Pop `${OUT}` +!macroend + +!define StrContains '!insertmacro "_StrContainsConstructor"' + + +;------------------------------- +; Uninstall Previous Version if exists + +!macro UninstallExisting exitcode uninstcommand +Push `${uninstcommand}` +Call UninstallExisting +Pop ${exitcode} +!macroend +Function UninstallExisting +Exch $1 ; uninstcommand +Push $2 ; Uninstaller +Push $3 ; Len +StrCpy $3 "" +StrCpy $2 $1 1 +StrCmp $2 '"' qloop sloop +sloop: + StrCpy $2 $1 1 $3 + IntOp $3 $3 + 1 + StrCmp $2 "" +2 + StrCmp $2 ' ' 0 sloop + IntOp $3 $3 - 1 + Goto run +qloop: + StrCmp $3 "" 0 +2 + StrCpy $1 $1 "" 1 ; Remove initial quote + IntOp $3 $3 + 1 + StrCpy $2 $1 1 $3 + StrCmp $2 "" +2 + StrCmp $2 '"' 0 qloop +run: + StrCpy $2 $1 $3 ; Path to uninstaller + StrCpy $1 161 ; ERROR_BAD_PATHNAME + GetFullPathName $3 "$2\.." ; $InstDir + IfFileExists "$2" 0 +4 + ExecWait '"$2" /S _?=$3' $1 ; This assumes the existing uninstaller is a NSIS uninstaller, other uninstallers don't support /S nor _?= + IntCmp $1 0 "" +2 +2 ; Don't delete the installer if it was aborted + Delete "$2" ; Delete the uninstaller + RMDir "$3" ; Try to delete $InstDir + RMDir "$3\.." ; (Optional) Try to delete the parent of $InstDir +Pop $3 +Pop $2 +Exch $1 ; exitcode +FunctionEnd + + +Function .onInit +ReadRegStr $0 HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "UninstallString" +${If} $0 != "" +${AndIf} ${Cmd} `MessageBox MB_YESNO|MB_ICONQUESTION "It is highly recommended to uninstall the previous version of OpenBB Terminal - Please click Yes to proceed (Note - You will not lose your custom settings) - Or you can uninstall manually. " /SD IDYES IDYES` + !insertmacro UninstallExisting $0 $0 + ${If} $0 <> 0 + MessageBox MB_YESNO|MB_ICONSTOP "Failed to uninstall, continue anyway?" /SD IDYES IDYES +2 + Abort + ${EndIf} +${EndIf} +FunctionEnd + +;-------------------------------- +; Section - Install App + + Section "-hidden app" + SectionIn RO + ${StrContains} $0 "\OpenBB" "$INSTDIR" + ; Making sure here if user manually removes \openbb from their path that it still installs there + ; so we dont have issues with uninstaller later. + StrCmp $0 "" notfound + ; MessageBox MB_OK 'Found string $0 $INSTDIR' + SetOutPath "$INSTDIR" + Goto done + notfound: + ; MessageBox MB_OK "$INSTDIR is 'bla'" + SetOutPath "$INSTDIR\OpenBB" + StrCpy $InstDir "$INSTDIR\OpenBB" + ; MessageBox MB_OK 'Did not find string "$INSTDIR\OpenBB" "$installerPath"' + done: + + ; File /r "app\*.*" + WriteRegStr HKCU "Software\${NAME}" "" $INSTDIR + WriteUninstaller "$INSTDIR\Uninstall.exe" + WriteRegStr HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "OpenBBPlatform" "OpenBB" + WriteRegStr HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "UninstallString" '"$InstDir\Uninstall.exe"' + WriteRegStr HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "QuietUninstallString" '"$InstDir\Uninstall.exe" /S' + SectionEnd + + +;-------------------------------- +; Remove empty parent directories + + Function un.RMDirUP + !define RMDirUP '!insertmacro RMDirUPCall' + + !macro RMDirUPCall _PATH + push '${_PATH}' + Call un.RMDirUP + !macroend + + ; $0 - current folder + ClearErrors + + Exch $0 + ;DetailPrint "ASDF - $0\.." + RMDir "$0\.." + + IfErrors Skip + ${RMDirUP} "$0\.." + Skip: + + Pop $0 + + FunctionEnd + +;-------------------------------- +; Section - Uninstaller + +Section "Uninstall" + + ;Delete Shortcut + Delete "$DESKTOP\${NAME}.lnk" + + ;Delete Directory + Delete '$SMPROGRAMS\${Company}\${NAME}' + + ;Delete Reg Key + DeleteRegKey HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" + + ;Delete Uninstall + Delete "$INSTDIR\Uninstall.exe" + + + ;Delete Folder + RMDir /r "$INSTDIR" + ${RMDirUP} "$INSTDIR" + + DeleteRegKey /ifempty HKCU "Software\${NAME}" + +SectionEnd \ No newline at end of file diff --git a/build/conda/installer/assets/installer_conclusion.txt b/build/conda/installer/assets/installer_conclusion.txt index dbdbf3ed5113..ece681de7f8b 100644 --- a/build/conda/installer/assets/installer_conclusion.txt +++ b/build/conda/installer/assets/installer_conclusion.txt @@ -1,3 +1,7 @@ Thank you for installing OpenBB Platform! -To get started, launch the OpenBB Platform CLI and explore the available commands. +To get started, open the installed folder, at the root of your user account profile, and review the README.md file. Or, open one of the launchers from the provided shortcuts. + +The OpenBB Platform Documentation is found here: https://docs.openbb.co + +Bugs/Issues/Requests can be posted here: https://github.com/OpenBB-finance/OpenBB/issues diff --git a/build/conda/installer/assets/installer_readme.txt b/build/conda/installer/assets/installer_readme.txt index 8418e4eae8a5..e7b204bdc96f 100644 --- a/build/conda/installer/assets/installer_readme.txt +++ b/build/conda/installer/assets/installer_readme.txt @@ -2,6 +2,24 @@ OpenBB Platform provides a convenient way to access raw financial data from mult Ensure your system meets all requirements before proceeding with the installation. +- 4GB RAM minimum +- 1-2 GB of storage space + +Important Notes: + +The installation is for the current user only, system-wide installations are not allowed. + +In order to install you need access to the Internet. + +If a folder named "OpenBB" already exists at the user root, please move or rename it before installing. + +The installation will take approximately 1-2 GB of storage. + +The installed packages can be reconfigured, post-installation, for custom configurations. + +If you move the folder post-installation, shortcuts will not be updated to reflect the change. Command line entry points will continue to work. + + This application is intended strictly for research and educational purposes. By proceeding with the installation, you agree not to use this application for any purpose that is unlawful or prohibited by international and national laws, regulations, or governmental and industry requirements. diff --git a/build/conda/installer/assets/installer_welcome.txt b/build/conda/installer/assets/installer_welcome.txt index 4d131622a33b..7c7383465682 100644 --- a/build/conda/installer/assets/installer_welcome.txt +++ b/build/conda/installer/assets/installer_welcome.txt @@ -1,6 +1,8 @@ -Welcome to the OpenBB Installer. +Welcome to the OpenBB Platform Installer. -This application will install Python and the latest version of OpenBB Platform on your computer. +This application will install the latest version of the OpenBB Platform on your computer in a self-contained, Python 3.12, Conda environment. It contains everything required to get started immediately, both as a user and a developer. -Note: In order to install you need access to the Internet. +Several launch scripts are included; OpenBB CLI, OpenBB API, Jupyter Notebook, IPython, Update, and shortcuts to key folders. + +Find them at the base of the installation folder, along with the README.md file for getting started. diff --git a/build/conda/installer/assets/openbb.icns b/build/conda/installer/assets/openbb.icns new file mode 100644 index 000000000000..d02e2301f45f Binary files /dev/null and b/build/conda/installer/assets/openbb.icns differ diff --git a/build/conda/installer/assets/openbb_icon.ico b/build/conda/installer/assets/openbb_icon.ico new file mode 100644 index 000000000000..ec1a7d484f45 Binary files /dev/null and b/build/conda/installer/assets/openbb_icon.ico differ diff --git a/build/conda/installer/assets/openbb_platform/README.md b/build/conda/installer/assets/openbb_platform/README.md new file mode 100644 index 000000000000..21cfeb2c56ca --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/README.md @@ -0,0 +1,48 @@ +# OpenBB Platform Package + +This package is for installing the OpenBB Platform packages into any Python environment. Versions between 3.9 and 3.12, inclusively, are supported. + +The `obb` virtual environment that came with the installation is pre-loaded, and this configuration can be duplicated by creating a new environment and running, `poetry install`, from this location. + +## Installation + +Assuming the current working directory is the location of this document, the code block below will create a new environment, activate it, update the dependencies, and then install the OpenBB Platform. + +Open the Terminal command line with the Bash/CMD shortcut. + +```console +conda create -n my_env python=3.10 -y +conda activate my_env +pip install poetry +poetry lock +poetry install +openbb-build +``` + +## Updating Packages + +Update the environment dependencies with: + +``` +poetry lock +poetry install +``` + +## Installation Groups + +The package can be installed in different configurations, using Poetry groups as `--with`, `--without`, or `--only` keyword arguments. + +- main +- openbb-all +- cli +- notebook + +To install, in a fresh environment, only the `openbb-core` package and the base dependencies: + +```console +poetry install --only main --sync +``` + +The `--sync` flag with sync the environment to the `poetry.lock` file for the flagged group(s). All items are installed when no arguments are supplied. Use this as a way to restore corrupted environments. + +Do not use the `--sync` flag if you wish to keep installed packages that are not defined in the `pyproject.toml` and `poetry.lock` files. \ No newline at end of file diff --git a/build/conda/installer/assets/openbb_platform/obb-env.yml b/build/conda/installer/assets/openbb_platform/obb-env.yml new file mode 100644 index 000000000000..a201e45b581a --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/obb-env.yml @@ -0,0 +1,5 @@ +name: obb +channels: + - conda-forge +dependencies: + - python=3.12 \ No newline at end of file diff --git a/build/conda/installer/assets/openbb_platform/openbb_platform/__init__.py b/build/conda/installer/assets/openbb_platform/openbb_platform/__init__.py new file mode 100644 index 000000000000..cf160bd8ddb3 --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/openbb_platform/__init__.py @@ -0,0 +1,3 @@ +"""Placeholder for the OpenBB Platform Installer package.""" + +__version__ = "1.0.0" diff --git a/build/conda/installer/assets/openbb_platform/openbb_platform/api.py b/build/conda/installer/assets/openbb_platform/openbb_platform/api.py new file mode 100644 index 000000000000..1e5e7cc26f8f --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/openbb_platform/api.py @@ -0,0 +1,344 @@ +"""Generate and serve the widgets.json for the OpenBB Platform API.""" + +# flake8: noqa: T201 + +import json +import os +import socket +from pathlib import Path +from typing import Dict + +from deepdiff import DeepDiff +from fastapi.responses import JSONResponse +from openbb_core.api.rest_api import app + +from openbb_platform.utils import ( + data_schema_to_columns_defs, + get_data_schema_for_widget, + get_query_schema_for_widget, +) + +HOME = os.environ.get("HOME") or os.environ.get("USERPROFILE") + +CURRENT_USER_SETTINGS = os.path.join(HOME, ".openbb_platform", "user_settings.json") +USER_SETTINGS_COPY = os.path.join(HOME, ".openbb_platform", "user_settings_backup.json") + + +def check_port(host, port) -> int: + """Check if the port number is free.""" + not_free = True + port = int(port) - 1 + while not_free: + port = port + 1 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + res = sock.connect_ex((host, port)) + if res != 0: + not_free = False + return port + + +def get_user_settings(login: bool): + """Login to the OpenBB Platform.""" + import getpass + + if Path(CURRENT_USER_SETTINGS).exists(): + with open(CURRENT_USER_SETTINGS) as f: + current_settings = json.load(f) + else: + current_settings = { + "credentials": {}, + "preferences": {}, + "defaults": {"commands": {}}, + } + if (isinstance(login, str) and login.lower() == "false") or not login: + return current_settings + + pat = getpass.getpass( + "\n\nEnter your personal access token (PAT) to authorize the API and update your local settings." + + "\nSkip to use a pre-configured 'user_settings.json' file." + + "\nPress Enter to skip or copy (entered values are not displayed on screen) your PAT to the command line: " + ) + + if pat: + from openbb_core.app.service.hub_service import HubService + + try: + Hub = HubService() + _ = Hub.connect(pat=pat) + hub_settings = Hub.pull() + hub_credentials = json.loads(hub_settings.credentials.model_dump_json()) + hub_preferences = json.loads(hub_settings.preferences.model_dump_json()) + hub_defaults = json.loads(hub_settings.defaults.model_dump_json()) + except Exception as e: + print(f"\n\nError connecting with Hub:\n{e}\n\nUsing the local settings.\n") + hub_credentials = {} + hub_preferences = {} + hub_defaults = {} + + if hub_credentials: + # Prompt the user to ask if they want to persist the new settings + persist_input = ( + input( + "\n\nDo you want to persist the new settings?" + + " Not recommended for public machines. (yes/no): " + ) + .strip() + .lower() + ) + + if persist_input in ["yes", "y"]: + PERSIST = True + elif persist_input in ["no", "n"]: + PERSIST = False + else: + print( + "\n\nInvalid input. Defaulting to not persisting the new settings." + ) + PERSIST = False + + # Save the current settings to restore at the end of the session. + if PERSIST is False: + with open(USER_SETTINGS_COPY, "w") as f: + json.dump(current_settings, f, indent=4) + + new_settings = current_settings.copy() + new_settings.setdefault("credentials", {}) + new_settings.setdefault("preferences", {}) + new_settings.setdefault("defaults", {"commands": {}}) + + # Update the current settings with the new settings + if hub_credentials: + for k, v in hub_credentials.items(): + if v: + new_settings["credentials"][k] = v + + if hub_preferences: + for k, v in hub_credentials.items(): + if v: + new_settings["preferences"][k] = v + + if hub_defaults: + for k, v in hub_defaults.items(): + if k == "commands": + for key, value in hub_defaults["commands"].items(): + if value: + new_settings["defaults"]["commands"][key] = value + elif v: + new_settings["defaults"][k] = v + else: + continue + + # Write the new settings to the user_settings.json file + with open(CURRENT_USER_SETTINGS, "w") as f: + json.dump(new_settings, f, indent=4) + + current_settings = new_settings + + return current_settings + + +def build_json(openapi): + """Build the widgets.json file.""" + widgets_json: Dict = {} + routes = [ + p + for p in openapi["paths"] + if p.startswith("/api") and "get" in openapi["paths"][p] + ] + for route in routes: + route_api = openapi["paths"][route] + widget_id = route_api["get"]["operationId"] + + # Prepare the query schema of the widget + query_schema, has_chart = get_query_schema_for_widget(openapi, route) + + # Prepare the data schema of the widget + data_schema = get_data_schema_for_widget(openapi, widget_id) + if ( + data_schema + and "properties" in data_schema + and "results" in data_schema["properties"] + ): + response_schema_refs = data_schema["properties"]["results"] + columns_defs = data_schema_to_columns_defs( # noqa F841 + openapi, response_schema_refs + ) + + widget_config = { + "name": f'OBB {route_api["get"]["operationId"].replace("_", " ").title()}', + "description": route_api["get"]["description"], + "category": route_api["get"]["tags"][0].title(), + "widgetType": route_api["get"]["tags"][0], + "widgetId": f"OBB {widget_id}", + "params": query_schema, # Use the fetched query schema + "endpoint": route.replace("/api", "api"), + "gridData": {"w": 45, "h": 15}, + "data": { + "dataKey": "results", + "table": { + "showAll": False, + }, + }, + } + + # if columns_defs: + # widget_config["data"]["table"]["columnsDefs"] = columns_defs + # if "date" in columns_defs: + # widget_config["data"]["table"]["index"] = "date" + # if "period" in columns_defs: + # widget_config["data"]["table"]["index"] = "period" + + # Add the widget configuration to the widgets.json + widgets_json[widget_config["widgetId"]] = widget_config + + if has_chart: + # deepcopy the widget_config + widget_config_chart = json.loads(json.dumps(widget_config)) + del widget_config_chart["data"]["table"] + + widget_config_chart["name"] = f"{widget_config_chart['name']} Chart" + widget_config_chart["widgetId"] = f"{widget_config_chart['widgetId']}_chart" + widget_config_chart["params"]["chart"] = True + + widget_config_chart["defaultViz"] = "chart" + widget_config_chart["data"]["dataKey"] = "chart.content" + widget_config_chart["data"]["chart"] = { + "type": "line", + } + + widgets_json[widget_config_chart["widgetId"]] = widget_config_chart + + return widgets_json + + +def get_widgets_json(build: bool, openapi): + """Generate and serve the widgets.json for the OpenBB Platform API.""" + python_path = Path(os.sys.executable) + widgets_json_path = ( + python_path.parents[0 if os.name == "nt" else 1] + .joinpath("assets") + .resolve() + .joinpath("widgets.json") + ) + 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) + + if build: + diff = DeepDiff(existing_widgets_json, widgets_json, ignore_order=True) + if diff and json_exists: + print("Differences found:", diff) + merge_prompt = input( + "\nDo you want to overwrite the existing widgets.json configuration?" + "\nEnter 'n' to append existing with only new entries (y/n): " + ) + if merge_prompt.lower().startswith("n"): + widgets_json.update(existing_widgets_json) + + with open(widgets_json_path, "w", encoding="utf-8") as f: + json.dump(widgets_json, f, ensure_ascii=False, indent=4) + + return widgets_json + + +def main(): + """Main function.""" + import uvicorn + + args = os.sys.argv[1:].copy() + kwargs: Dict = {} + for i in range(len(args)): + if args[i].startswith("--"): + key = args[i][2:] + if i + 1 < len(args) and not args[i + 1].startswith("--"): + value = args[i + 1] + kwargs[key] = value + else: + kwargs[key] = True + + openapi = app.openapi() + build = kwargs.pop("build", True) + build = False if kwargs.pop("no-build", None) else build + login = kwargs.pop("login", False) + # We don't need the current settings, + # but we need to call the function to update login and/or identify the settings file. + current_settings = get_user_settings(login) # noqa F841 + + widgets_json = get_widgets_json(build, openapi) + + @app.get("/") + async def get_root(): + """API Root.""" + return JSONResponse( + content="Welcome to the OpenBB Platform API." + + " Learn how to connect to Pro in docs.openbb.co/pro/data-connectors," + + " or see the API documentation here: /docs" + ) + + @app.get("/widgets.json") + async def get_widgets(): + """Widgets configuration file for the OpenBB Terminal Pro.""" + return JSONResponse(content=widgets_json) + + def launch_api(**kwargs): # noqa PRL0912 + """Main function.""" + host = kwargs.pop("host", os.getenv("OPENBB_API_HOST", "127.0.0.1")) + if not host: + print( + "\n\nOPENBB_API_HOST is set incorrectly. It should be an IP address or hostname." + ) + host = input("Enter the host IP address or hostname: ") + if not host: + host = "127.0.0.1" + + port = kwargs.pop("port", os.getenv("OPENBB_API_PORT", "8000")) + + try: + port = int(port) + except ValueError: + print( + "\n\nOPENBB_API_PORT is set incorrectly. It should be an port number." + ) + port = input("Enter the port number: ") + try: + port = int(port) + except ValueError: + print("\n\nInvalid port number. Defaulting to 8000.") + port = 8000 + if port < 1025: + port = 8000 + print("\n\nInvalid port number, must be above 1024. Defaulting to 8000.") + + free_port = check_port(host, port) + + if free_port != port: + print(f"\n\nPort {port} is already in use. Using port {free_port}.\n") + port = free_port + + try: + uvicorn.run("openbb_platform.api:app", host=host, port=port, **kwargs) + finally: + # If user_settings_copy.json exists, then restore the original settings. + if os.path.exists(USER_SETTINGS_COPY): + print("\n\nRestoring the original settings.\n") + os.replace(USER_SETTINGS_COPY, CURRENT_USER_SETTINGS) + + launch_api(**kwargs) + + +if __name__ == "__main__": + + try: + main() + except KeyboardInterrupt: + print("Restoring the original settings.") diff --git a/build/conda/installer/assets/openbb_platform/openbb_platform/build.py b/build/conda/installer/assets/openbb_platform/openbb_platform/build.py new file mode 100644 index 000000000000..176d4752d196 --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/openbb_platform/build.py @@ -0,0 +1,24 @@ +"""Entry point for the OpenBB Python interface build script.""" + +# pylint: disable=import-outside-toplevel,unused-import +# flake8: noqa + +def main(): + """Build the OpenBB Python interface.""" + + import sys + import subprocess + try: + import openbb + except ImportError: + print("\nThe OpenBB Python interface build script was not found, installing it now...\n") + subprocess.run([sys.executable, "-m", "pip", "install", "openbb", "--no-deps"]) # noqa + + print("\nBuilding the OpenBB Python interface...\n") + + subprocess.run([sys.executable, "-c", "import openbb;openbb.build()"]) + + print("\nThe build was completed!\n") + +if __name__ == "__main__": + main() diff --git a/build/conda/installer/assets/openbb_platform/openbb_platform/update.py b/build/conda/installer/assets/openbb_platform/openbb_platform/update.py new file mode 100644 index 000000000000..4c3b4feba150 --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/openbb_platform/update.py @@ -0,0 +1,30 @@ +"""Entry point for the OpenBB Updater script.""" + +# pylint: disable=import-outside-toplevel,unused-import +# flake8: noqa + +def main(): + """Update the OpenBB Platform and rebuild the Python interface.""" + + import os + import sys + import subprocess + from pathlib import Path + + args = os.sys.argv[1:].copy() if os.sys.argv[1:] else [] + + cwd = Path(os.path.dirname(os.path.realpath(__file__))).parent.resolve() + + command = ( + f"{sys.executable} -m pip install -U pip && " + f"{sys.executable} -m poetry lock && " + f"{sys.executable} -m poetry install {' '.join(args)} && " + "openbb-build" + ) + + subprocess.run(command, shell=True, cwd=cwd) + + input("\nUpdate complete. Press Enter to exit...") + +if __name__ == "__main__": + main() diff --git a/build/conda/installer/assets/openbb_platform/openbb_platform/utils.py b/build/conda/installer/assets/openbb_platform/openbb_platform/utils.py new file mode 100644 index 000000000000..67a9481236d0 --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/openbb_platform/utils.py @@ -0,0 +1,202 @@ +"""Utils for openbb_widgets_api.""" + +from datetime import datetime, timedelta + + +def get_query_schema_for_widget( + openapi_json: dict, command_route: str +) -> tuple[dict, bool]: + """Extract the query schema for a widget. + + Does that based on operationId, with special handling for certain parameters like + chart, sort, limit, order... + + Args: + openapi_json (dict): The OpenAPI specification as a dictionary. + route (str): The route of the widget. + + Returns: + dict: A dictionary containing the query schema for the widget, excluding specified parameters. + """ + _query_schema: dict = {"optional": {}} + _has_chart: bool = False + + command_schema = openapi_json["paths"][command_route]["get"] + for param in command_schema.get("parameters", []): + if param["in"] == "query": + param_name = param["name"] + # Skip "sort", "limit", "order" parameters. This is handled by the widget + if param_name in ["sort", "limit", "order"]: + continue + # Identify if there is a "chart" available + if param_name == "chart": + _has_chart = True + continue + # Handle single provider + if ( + param_name == "provider" + and "enum" in param["schema"] + and len(param["schema"]["enum"]) == 1 + ): + _query_schema["provider"] = param["schema"]["enum"][0] + continue + + # Direct enum in schema + if "enum" in param["schema"]: + _query_schema["optional"][param_name] = param["schema"]["enum"] + # Enum within anyOf + elif "anyOf" in param["schema"]: + enums = [] + for sub_schema in param["schema"]["anyOf"]: + if "enum" in sub_schema: + enums.extend(sub_schema["enum"]) + if enums: # If any enums were found, remove duplicates + _query_schema["optional"][param_name] = list(set(enums)) + else: # Handle other types within anyOf + types = [ + sub_schema.get("type") + for sub_schema in param["schema"]["anyOf"] + if "type" in sub_schema + ] + # Default handling for common types + if "string" in types: + _query_schema["optional"][param_name] = "string" + elif "integer" in types: + _query_schema["optional"][param_name] = 0 + elif "null" in types: + _query_schema["optional"][param_name] = None + + # Handling other types not within anyOf + elif param["schema"].get("type") == "string": + _query_schema["optional"][param_name] = "string" + elif param["schema"].get("type") == "integer": + _query_schema["optional"][param_name] = 0 + + # Handle default dates + if param_name == "start_date": + # Set the default start date 3 months in the past + _query_schema["optional"]["start_date"] = ( + datetime.now() - timedelta(days=90) + ).strftime("%Y-%m-%d") + + return _query_schema, _has_chart + + +def get_data_schema_for_widget(openapi_json, operation_id): + """ + Fetches the data schema for a widget based on its operationId. + + Args: + openapi (dict): The OpenAPI specification as a dictionary. + operation_id (str): The operationId of the widget. + + Returns: + 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" + ]["schema"]["$ref"] + # Extract the schema name from the reference + schema_name = response_ref.split("/")[-1] + # Fetch and return the schema from components + return openapi_json["components"]["schemas"][schema_name] + # Return None if the schema is not found + return None + + +def data_schema_to_columns_defs(openapi_json, result_schema_ref): + """Convert data schema to column definitions for the widget.""" + # Initialize an empty list to hold the schema references + schema_refs = [] + + # Check if 'anyOf' is in the result_schema_ref and handle the nested structure + if "anyOf" in result_schema_ref: + for item in result_schema_ref["anyOf"]: + # When there are multiple providers a 'oneOf' is used + if "items" in item and "oneOf" in item["items"]: + # Extract the $ref values + schema_refs.extend( + [ + oneOf_item["$ref"].split("/")[-1] + for oneOf_item in item["items"]["oneOf"] + if "$ref" in oneOf_item + ] + ) + # When there's only one model there is no oneOf + elif "items" in item and "$ref" in item["items"]: + schema_refs.append(item["items"]["$ref"].split("/")[-1]) + + # Fetch the schemas using the extracted references + schemas = [ + openapi_json["components"]["schemas"][ref] + for ref in schema_refs + if ref in openapi_json["components"]["schemas"] + ] + + # Proceed with finding common keys and generating column definitions + if not schemas: + return [] + + # If there's only one schema, use its properties directly + if len(schemas) == 1: + common_keys = schemas[0]["properties"].keys() + else: + # Find common keys across all schemas if there are multiple + common_keys = set(schemas[0]["properties"].keys()) + for schema in schemas[1:]: + common_keys.intersection_update(schema["properties"].keys()) + + column_defs = [] + for key in common_keys: + cell_data_type = None + prop = schemas[0]["properties"][key] + # Handle prop types for both when there's a single prop type or multiple + if "anyOf" in prop: + types = [ + sub_prop.get("type") for sub_prop in prop["anyOf"] if "type" in sub_prop + ] + if "number" in types or "integer" in types or "float" in types: + cell_data_type = "number" + elif "string" in types and any( + sub_prop.get("format") in ["date", "date-time"] + for sub_prop in prop["anyOf"] + if "format" in sub_prop + ): + cell_data_type = "date" + else: + cell_data_type = "text" + else: + prop_type = prop.get("type", None) + if prop_type in ["number", "integer", "float"]: + cell_data_type = "number" + elif "format" in prop and prop["format"] in ["date", "date-time"]: + cell_data_type = "date" + else: + cell_data_type = "text" + + column_def = {} + column_def["field"] = key + column_def["headerName"] = prop.get("title", key.title()) + column_def["description"] = prop.get("description", prop.get("title", key.title())) + column_def["cellDataType"] = cell_data_type + + column_def["chartDataType"] = ( + "series" if cell_data_type in ["number", "integer", "float"] else "category" + ) + + measurement = prop.get("x-unit_measurement") + if measurement == "percent": + column_def["formatterFn"] = "normalizedPercent" if prop.get("x-frontend_multiply") == 100 else "percent" + elif cell_data_type == "date": + column_def["formatterFn"] = "date" + elif cell_data_type == "number": + column_def["formatterFn"] = "none" + + column_defs.append(column_def) + + return column_defs diff --git a/build/conda/installer/assets/openbb_platform/pyproject.toml b/build/conda/installer/assets/openbb_platform/pyproject.toml new file mode 100644 index 000000000000..2cacebe3579b --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/pyproject.toml @@ -0,0 +1,53 @@ +[tool.poetry] +name = "openbb_platform" # Change this to your package name +version = "1.0.0" # Change this to your package version +description = "OpenBB Platform: Investment research for everyone, anywhere." # Change this to your description +authors = ["My Name "] # Change this to your name and email +license = "AGPL-3.0-only" # This license must be compatible with the OpenBB license +readme = "README.md" # Change this to your README file +homepage = "https://my.website.com" # Change this to your website +repository = "https://github.com/Some-User/openbb-platform" # Change this to your repository +documentation = "https://docs.website.com" # Change this to your documentation +packages = [{ include = "openbb_platform" }] # Update accordingly - these are build scripts and entry points. + +[tool.poetry.scripts] +openbb-api = "openbb_platform.api:main" +openbb-build = "openbb_platform.build:main" +openbb-update = "openbb_platform.update:main" + +[tool.poetry.dependencies] +python = ">=3.9,<3.13" +poetry = "^1.8" +setuptools = "*" +openbb-core = "*" +deepdiff = "*" +types-python-dateutil = "*" +types-toml = "*" +pandas-stubs = "*" +codespell = "*" +ruff = "*" +mypy = "*" +black = "*" +pylint = "^3" +pydocstyle = "~6.3" + +[tool.poetry.group.openbb-all] + +[tool.poetry.group.openbb-all.dependencies] +openbb = { version = "^4", extras = ["all"] } + +[tool.poetry.group.cli] + +[tool.poetry.group.cli.dependencies] +openbb-cli = "^1" + +[tool.poetry.group.notebook] + +[tool.poetry.group.notebook.dependencies] +notebook = "^7" +nbclassic = "^1" +jedi-language-server = "^0.41" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/build/conda/installer/assets/openbb_platform/requirements-full.txt b/build/conda/installer/assets/openbb_platform/requirements-full.txt new file mode 100644 index 000000000000..8606b0914bb6 --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/requirements-full.txt @@ -0,0 +1,17 @@ +poetry>=1.8 +setuptools +types-python-dateutil +types-toml +pandas-stubs +codespell +ruff +mypy +black +pylint>=3 +pydocstyle~=6.3 +openbb-core +openbb[all] +openbb-cli +notebook +nbclassic +jedi-language-server \ No newline at end of file diff --git a/build/conda/installer/assets/openbb_platform/requirements-min.txt b/build/conda/installer/assets/openbb_platform/requirements-min.txt new file mode 100644 index 000000000000..b93577bb687f --- /dev/null +++ b/build/conda/installer/assets/openbb_platform/requirements-min.txt @@ -0,0 +1,12 @@ +poetry>=1.8 +setuptools +types-python-dateutil +types-toml +pandas-stubs +codespell +ruff +mypy +black +pylint>=3 +pydocstyle~=6.3 +openbb-core \ No newline at end of file diff --git a/build/conda/installer/construct.yaml b/build/conda/installer/construct.yaml index 593424f17fa9..de7a73d9341a 100644 --- a/build/conda/installer/construct.yaml +++ b/build/conda/installer/construct.yaml @@ -1,38 +1,100 @@ name: "OpenBB" -version: 4 +version: "Platform" company: OpenBB, Inc. license_file: ./assets/installer_license.txt -installer_type: pkg # [osx] -installer_type: exe # [win] +installer_type: all register_python: false # [win] register_python_default: false # [win] +register_envs: false +initialize_by_default: false +initialize_conda: false +write_condarc: true -channels: - - defaults - - conda-forge +default_location_pkg: OpenBB # [osx] +default_prefix_domain_user: "%USERPROFILE%\\OpenBB\\conda" # [win] +default_prefix: "${HOME}/OpenBB" # [linux] +default_prefix: "%USERPROFILE%\\OpenBB\\conda" # [win] +pkg_name: conda specs: + - conda + - bash # [unix] - python=3.12 - - pip - - compilers -post_install: post_install.sh # [osx] +extra_envs: + obb: + environment_file: ./assets/openbb_platform/obb-env.yml + +channels: + - conda-forge + +condarc: + {channels: [conda-forge], + default_channels: [conda-forge], + allow_softlinks: false, + auto_activate_base: true, + envs_dirs: [$PREFIX/envs], + pkgs_dirs: [$PREFIX/pkgs], + always_copy: true, + register_envs: false, + pip_interop_enabled: true} + + +pkg_domains: + enable_anywhere: false + enable_currentUserHome: true + +post_install: post_install.sh # [unix] post_install: post_install.bat # [win] extra_files: # Bundle launcher assets - - ../../../images/openbb_icon.ico: assets/openbb.ico # [win] + - ./README.md: ../README.md + - ./assets/openbb_platform/obb-env.yml: ../extensions/openbb_platform/obb-env.yml + - ./assets/openbb_platform/pyproject.toml: ../extensions/openbb_platform/pyproject.toml + - ./assets/openbb_platform/README.md: ../extensions/openbb_platform/README.md + - ./assets/openbb_platform/openbb_platform/__init__.py: ../extensions/openbb_platform/openbb_platform/__init__.py + - ./assets/openbb_platform/openbb_platform/api.py: ../extensions/openbb_platform/openbb_platform/api.py + - ./assets/openbb_platform/openbb_platform/build.py: ../extensions/openbb_platform/openbb_platform/build.py + - ./assets/openbb_platform/openbb_platform/update.py: ../extensions/openbb_platform/openbb_platform/update.py + - ./assets/openbb_platform/openbb_platform/utils.py: ../extensions/openbb_platform/openbb_platform/utils.py + - ./assets/openbb_icon.ico: assets/openbb_icon.ico # [win] + - ./assets/openbb.icns: ../openbb_icon.icns # [osx] - ./assets/create_shortcut.vbs: assets/create_shortcut.vbs # [win] + - ./assets/installer_license.txt: assets/installer_license.txt # [win] + - ./assets/installer_vertical2.bmp: assets/installer_vertical2.bmp # [win] + - ./assets/installer_horizontal.bmp: assets/installer_horizontal.bmp # [win] + - ./assets/examples/README.md: ../extensions/examples/README.md + - ./assets/examples/install_examples.py: ../extensions/examples/install_examples.py + - ./assets/examples/empty_provider/pyproject.toml: ../extensions/examples/empty_provider/pyproject.toml + - ./assets/examples/empty_provider/README.md: ../extensions/examples/empty_provider/README.md + - ./assets/examples/empty_provider/empty_provider/__init__.py: ../extensions/examples/empty_provider/empty_provider/__init__.py + - ./assets/examples/empty_provider/empty_provider/models/__init__.py: ../extensions/examples/empty_provider/empty_provider/models/__init__.py + - ./assets/examples/empty_provider/empty_provider/models/empty_model.py: ../extensions/examples/empty_provider/empty_provider/models/empty_model.py + - ./assets/examples/empty_provider/empty_provider/utils/__init__.py: ../extensions/examples/empty_provider/empty_provider/utils/__init__.py + - ./assets/examples/empty_router/pyproject.toml: ../extensions/examples/empty_router/pyproject.toml + - ./assets/examples/empty_router/README.md: ../extensions/examples/empty_router/README.md + - ./assets/examples/empty_router/empty_router/__init__.py: ../extensions/examples/empty_router/empty_router/__init__.py + - ./assets/examples/empty_router/empty_router/empty_router.py: ../extensions/examples/empty_router/empty_router/empty_router.py + - ./assets/examples/empty_router/empty_router/empty_views.py: ../extensions/examples/empty_router/empty_router/empty_views.py + - ./assets/examples/empty_obbject/pyproject.toml: ../extensions/examples/empty_obbject/pyproject.toml + - ./assets/examples/empty_obbject/README.md: ../extensions/examples/empty_obbject/README.md + - ./assets/examples/empty_obbject/empty_obbject/__init__.py: ../extensions/examples/empty_obbject/empty_obbject/__init__.py -icon_image: ../../../images/dmg_volume.icns # [osx] -icon_image: ../../../images/openbb_icon.ico # [win] - -welcome_image: ./assets/openbb_osx.png # [osx] -welcome_image: ./assets/openbb_win.png # [win] -header_image: ./assets/header_win.png # [win] +icon_image: ../openbb_icon.icns # [osx] +icon_image: ./assets/openbb_icon.ico # [win] +welcome_image: ./assets/openbb_osx.png # [unix] +welcome_image: ./assets/Installer_vertical2.bmp # [win] +header_image: ./assets/Installer_horizontal2.png # [win] welcome_image_text: "OpenBB Platform" -welcome_file: ./assets/installer_welcome.txt -readme_file: ./assets/installer_readme.txt -conclusion_file: ./assets/installer_conclusion.txt +readme_file: ./assets/installer_readme.txt # [unix] +conclusion_file: ./assets/installer_conclusion.txt # [unix] +welcome_file: ./assets/installer_welcome.txt # [unix] +welcome_file: ./assets/custom_welcome.nsi # [win] +conclusion_file: ./assets/custom_conclusion.nsi # [win] + +# Add extra files to the installer - this is another way - was playing with this +temp_extra_files: + - ./assets/Installer_vertical2.bmp: Installer_vertical2.bmp # [win] diff --git a/build/conda/installer/post_install.bat b/build/conda/installer/post_install.bat index 62ce869e5c19..c2d4dbce5a82 100644 --- a/build/conda/installer/post_install.bat +++ b/build/conda/installer/post_install.bat @@ -1,28 +1,46 @@ @echo off -echo Running post-installation environment setup. -REM Full path to the Python executable inside the constructed environment -SET PYTHON_EXEC="%PREFIX%\python.exe" -SET LOG_FILE="%PREFIX%\post_install_log.txt" +echo Installing environment, this may take a few minutes... Watch for changes in post_install_log.txt file at the root of the installation directory. + +cd "%PREFIX%\..\extensions\openbb_platform" + +PATH %PREFIX%;%PREFIX%\Scripts;%PREFIX%\Library\bin;%PATH% +SET LOG_FILE="%PREFIX%\..\post_install_log.txt" + +call "%PREFIX%\Scripts\activate.bat" + +call conda activate "%PREFIX%\envs\obb" >> "%LOG_FILE%" 2>&1 + +python -m pip install -U pip >> "%LOG_FILE%" 2>&1 + +pip install -U setuptools >> "%LOG_FILE%" 2>&1 + +pip install poetry >> "%LOG_FILE%" 2>&1 + +poetry config virtualenvs.path "%PREFIX%\envs" --local >> "%LOG_FILE%" 2>&1 + +poetry config virtualenvs.create false --local >> "%LOG_FILE%" 2>&1 + +poetry lock >> "%LOG_FILE%" 2>&1 + +poetry install >> "%LOG_FILE%" 2>&1 -REM Use the specific Python that comes bundled with the installer -"%PYTHON_EXEC%" -m pip install -U openbb[all] openbb-cli openbb-platform-pro-backend >> "%LOG_FILE%" 2>&1 IF ERRORLEVEL 1 ( - echo %date% %time% "Error during post-installation: pip install failed." >> %LOG_FILE% + echo %date% %time% "Error during post-installation: poetry install failed." >> %LOG_FILE% exit /b 1 ) ELSE ( - echo %date% %time% "pip install completed successfully." >> %LOG_FILE% + echo %date% %time% "Python environment successfully installed... Building the OpenBB Python interface..." >> %LOG_FILE% ) -REM Build OpenBB's python interface -"%PYTHON_EXEC%" -c "import openbb; openbb.build()" >> "%LOG_FILE%" 2>&1 +echo Python environment successfully installed... Building the OpenBB Python interface... + +call openbb-build >> "%LOG_FILE%" 2>&1 IF ERRORLEVEL 1 ( - echo %date% %time% "Error during post-installation: building OpenBB's python interface failed." >> %LOG_FILE% + call :log_with_timestamp "Error during post-installation: building OpenBB's Python interface failed." exit /b 1 ) ELSE ( - echo %date% %time% "OpenBB's python interface built successfully." >> %LOG_FILE% + call :log_with_timestamp "OpenBB's Python interface built successfully." ) -REM Create shortcuts using the VBS script cscript "%PREFIX%\assets\create_shortcut.vbs" >> "%LOG_FILE%" 2>&1 IF ERRORLEVEL 1 ( echo %date% %time% "Error during post-installation: creating shortcuts failed." >> %LOG_FILE% @@ -32,4 +50,12 @@ IF ERRORLEVEL 1 ( ) echo Post-installation steps completed successfully. + exit /b 0 + +goto :eof + +REM Function to add timestamp +:log_with_timestamp + echo %date%_%time% %1 >> %LOG_FILE% + goto :eof \ No newline at end of file diff --git a/build/conda/installer/post_install.sh b/build/conda/installer/post_install.sh index 1b61d44812b6..9b6f5752c498 100755 --- a/build/conda/installer/post_install.sh +++ b/build/conda/installer/post_install.sh @@ -1,42 +1,143 @@ #!/bin/bash -# Full path to the Python executable inside the constructed environment -PYTHON_EXEC="$PREFIX/bin/python" -LOG_FILE="$PREFIX/post_install_log.txt" +# Setup environment. +export PATH="$PREFIX/bin:$PATH" +LOG_FILE="$PREFIX/../post_install_log.txt" +CWDIR=$(dirname "$PREFIX") -# Function to add timestamp +# Function to add timestamp. log_with_timestamp() { echo "$(date '+%Y-%m-%d_%H:%M:%S') $1" >>"$LOG_FILE" } -# Use the specific Python that comes bundled with the installer -if "$PYTHON_EXEC" -m pip install -U "openbb[all]" openbb-cli openbb-platform-pro-backend >>"$LOG_FILE" 2>&1; then - log_with_timestamp "pip install completed successfully." +cd "$PREFIX/../extensions/openbb_platform" >>"$LOG_FILE" 2>&1 + +source "$PREFIX/etc/profile.d/conda.sh" && conda activate "$PREFIX" + +conda activate obb >>"$LOG_FILE" 2>&1 + +python -m pip install -U pip >>"$LOG_FILE" 2>&1 + +pip install -U setuptools poetry >>"$LOG_FILE" 2>&1 + +poetry config virtualenvs.path "$PREFIX/envs" --local >>"$LOG_FILE" 2>&1 + +poetry config virtualenvs.create false --local >>"$LOG_FILE" 2>&1 + +poetry lock >>"$LOG_FILE" 2>&1 + + +# Install OpenBB packages. +if poetry install >>"$LOG_FILE" 2>&1; then + log_with_timestamp "OpenBB Platform installation completed successfully." else - log_with_timestamp "Error during post-installation: pip install failed." + log_with_timestamp "Error during post-installation: poetry install failed." exit 1 fi -# Build OpenBB's python interface -"$PYTHON_EXEC" - <>"$LOG_FILE" 2>&1 +# Build OpenBB Python interface. +python - <>"$LOG_FILE" 2>&1 import openbb openbb.build() -exit() EOF -log_with_timestamp "OpenBB's python interface built successfully." - -# Create symlinks -if { - ln -s "$PREFIX/bin/openbb" "$PREFIX/openbb-cli" - ln -s "$PREFIX/bin/openbb-api" "$PREFIX/openbb-api" -} >>"$LOG_FILE" 2>&1; then - log_with_timestamp "Symlinks created successfully." +log_with_timestamp "OpenBB's Python Interface built successfully." + +# IPython launcher script initialized with OpenBB. +IPYTHON_WRAPPER_SCRIPT="$PREFIX/envs/obb/bin/openbb-ipython-launcher" + +cat > "$IPYTHON_WRAPPER_SCRIPT" < "$SHELL_WRAPPER_SCRIPT" < "$NOTEBOOK_WRAPPER_SCRIPT" < "$API_WRAPPER_SCRIPT" < "$CLI_WRAPPER_SCRIPT" < "$OPENBB_UPDATER_SCRIPT" <>"$LOG_FILE" 2>&1 else - log_with_timestamp "Error during post-installation: creating symlinks failed." - exit 1 + log_with_timestamp "Error during post-installation: creating symlinks failed." >>"$LOG_FILE" 2>&1 fi -# Verify symlinks verify_symlink() { if [ -L "$1" ] && [ -e "$1" ]; then log_with_timestamp "Symlink $1 verified successfully." @@ -46,5 +147,12 @@ verify_symlink() { fi } -verify_symlink "$PREFIX/openbb-cli" -verify_symlink "$PREFIX/openbb-api" +verify_symlink "$TARGET_DIR/openbb-cli" +verify_symlink "$TARGET_DIR/openbb-api" +verify_symlink "$TARGET_DIR/openbb-notebook" +verify_symlink "$TARGET_DIR/openbb-ipython" +verify_symlink "$TARGET_DIR/Bash" +verify_symlink "$TARGET_DIR/Update" +verify_symlink "$TARGET_DIR/Settings" +verify_symlink "$TARGET_DIR/OpenBBUserData" +verify_symlink "$TARGET_DIR/Environments"