From a116d9bbf2fb8f1a5308697cf3b8e651145f26a0 Mon Sep 17 00:00:00 2001 From: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com> Date: Tue, 14 May 2024 17:38:06 +0200 Subject: [PATCH 1/3] [Feature] - OpenBB Platform CLI Unit tests (#6397) * Unit test batch 1 * CLI controller * Test batch 3 * Test batch 4 * Test batch 5 * clean some workflows and setup actions * test * rename wfs * rename * update action * Skip * fix cli tests --------- Co-authored-by: Henrique Joaquim Co-authored-by: Diogo Sousa Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com> --- .github/scripts/noxfile.py | 20 +- .github/workflows/README.md | 226 ++++-------------- ...pi-nightly.yml => deploy-pypi-nightly.yml} | 0 ...pypi_platform.yml => deploy-test-pypi.yml} | 2 +- .github/workflows/draft-release.yml | 2 +- .../{linting.yml => general-linting.yml} | 4 - .../{labels-PR.yml => gh-pr-labels.yml} | 0 .github/workflows/labels-issue.yml | 27 --- .github/workflows/nightly-build.yml | 35 --- .github/workflows/pypi.yml | 78 ------ ...test.yml => test-integration-platform.yml} | 2 +- .github/workflows/test-unit-cli.yml | 46 ++++ ...atform-core.yml => test-unit-platform.yml} | 4 +- .github/workflows/unit-test.yml | 72 ------ .pre-commit-config.yaml | 2 +- cli/openbb_cli/config/menu_text.py | 14 -- cli/tests/test_argparse_translator.py | 94 ++++++++ ...st_argparse_translator_obbject_registry.py | 89 +++++++ cli/tests/test_cli.py | 45 ++++ cli/tests/test_config_completer.py | 78 ++++++ cli/tests/test_config_console.py | 47 ++++ cli/tests/test_config_menu_text.py | 68 ++++++ cli/tests/test_config_setup.py | 49 ++++ cli/tests/test_config_style.py | 60 +++++ cli/tests/test_controllers_base_controller.py | 79 ++++++ ...st_controllers_base_platform_controller.py | 70 ++++++ cli/tests/test_controllers_choices.py | 51 ++++ cli/tests/test_controllers_cli_controller.py | 79 ++++++ .../test_controllers_controller_factory.py | 61 +++++ cli/tests/test_controllers_script_parser.py | 106 ++++++++ .../test_controllers_settings_controller.py | 135 +++++++++++ cli/tests/test_controllers_utils.py | 157 ++++++++++++ cli/tests/test_models_settings.py | 72 ++++++ cli/tests/test_session.py | 44 ++++ openbb_platform/dev_install.py | 2 +- 35 files changed, 1503 insertions(+), 417 deletions(-) rename .github/workflows/{pypi-nightly.yml => deploy-pypi-nightly.yml} (100%) rename .github/workflows/{test_pypi_platform.yml => deploy-test-pypi.yml} (96%) rename .github/workflows/{linting.yml => general-linting.yml} (96%) rename .github/workflows/{labels-PR.yml => gh-pr-labels.yml} (100%) delete mode 100644 .github/workflows/labels-issue.yml delete mode 100644 .github/workflows/nightly-build.yml delete mode 100644 .github/workflows/pypi.yml rename .github/workflows/{platform-api-integration-test.yml => test-integration-platform.yml} (98%) create mode 100644 .github/workflows/test-unit-cli.yml rename .github/workflows/{platform-core.yml => test-unit-platform.yml} (89%) delete mode 100644 .github/workflows/unit-test.yml create mode 100644 cli/tests/test_argparse_translator.py create mode 100644 cli/tests/test_argparse_translator_obbject_registry.py create mode 100644 cli/tests/test_cli.py create mode 100644 cli/tests/test_config_completer.py create mode 100644 cli/tests/test_config_console.py create mode 100644 cli/tests/test_config_menu_text.py create mode 100644 cli/tests/test_config_setup.py create mode 100644 cli/tests/test_config_style.py create mode 100644 cli/tests/test_controllers_base_controller.py create mode 100644 cli/tests/test_controllers_base_platform_controller.py create mode 100644 cli/tests/test_controllers_choices.py create mode 100644 cli/tests/test_controllers_cli_controller.py create mode 100644 cli/tests/test_controllers_controller_factory.py create mode 100644 cli/tests/test_controllers_script_parser.py create mode 100644 cli/tests/test_controllers_settings_controller.py create mode 100644 cli/tests/test_controllers_utils.py create mode 100644 cli/tests/test_models_settings.py create mode 100644 cli/tests/test_session.py diff --git a/.github/scripts/noxfile.py b/.github/scripts/noxfile.py index ed4411601c58..5bc4af2ce43d 100644 --- a/.github/scripts/noxfile.py +++ b/.github/scripts/noxfile.py @@ -9,10 +9,12 @@ PLATFORM_TESTS = [ str(PLATFORM_DIR / p) for p in ["tests", "core", "providers", "extensions"] ] +CLI_DIR = ROOT_DIR / "cli" +CLI_TESTS = CLI_DIR / "tests" @nox.session(python=["3.9", "3.10", "3.11"]) -def tests(session): +def unit_test_platform(session): """Run the test suite.""" session.install("poetry", "toml") session.run( @@ -27,3 +29,19 @@ def tests(session): session.run( "pytest", *PLATFORM_TESTS, f"--cov={PLATFORM_DIR}", "-m", "not integration" ) + + +@nox.session(python=["3.9", "3.10", "3.11"]) +def unit_test_cli(session): + """Run the test suite.""" + session.install("poetry", "toml") + session.run( + "python", + str(PLATFORM_DIR / "dev_install.py"), + "-e", + "all", + external=True, + ) + session.install("pytest") + session.install("pytest-cov") + session.run("pytest", CLI_TESTS, f"--cov={CLI_DIR}") diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 87caf3212758..6b03303131b2 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,27 +1,9 @@ # OpenBB Workflows + This directory contains the workflows for the OpenBB 🦋 Project. The workflows are: -| Workflows | Summary | Branches -| :-------------------- |:------------------ | :------------------ -| branch-name-check.yml | Checks if the branch name is valid and follows the naming convention. | all branches -| build-release.yml | Builds the project and runs the tests. | main, release/* -| docker-build.yml | Builds the docker image and pushes it to the docker hub. | all branches (only pushes to docker hub on main) -| draft-release.yml | Creates a draft release when a new tag is pushed. | - -| gh-pages.yml | Builds the documentation and deploy to github pages. | main and release/* -| integration-test.yml | Runs the integration tests. | all branches -| labels-issue.yml | Creates an issue when a new bug is reported. | - -| labels-PR.yml | Adds labels to the issues and pull requests. | - -| linting.yml | Runs the linters. | all branches -| macos-build.yml | Builds the project on M1 Macs. | develop, main, release/* -| macos-ml.yml | Builds the project on Mac OS X Full Clean Build with ML. | main -| nightly-build.yml | Builds the project and runs the integration tests every night on the `develop` branch. | develop -| pypi.yml | Publishes the package to PyPI. | all branches (only pushes to PyPI on main) -| pypi-nightly.yml | Publishes the package to PyPI every night on the `develop` branch. | develop -| unit-test.yml | Runs the unit tests. | all branches -| windows_ml.yml | Builds the project on Windows 10 Full Clean Build with ML. | main -| windows10_build.yml | Builds the project on Windows 10. | all branches - -## Branch Name Check Workflow +## Branch Name Check + Objective: To check if pull request branch names follow the GitFlow naming convention before merging. Triggered by: A pull request event where the target branch is either develop or main. @@ -30,137 +12,22 @@ Branches checked: The source branch of a pull request and the target branch of a Steps: -1. Extract branch names: Using the jq tool, the source and target branch names are extracted from the pull request event. The branch names are then stored in environment variables and printed as output. +1. Extract branch names: Using the jq tool, the source and target branch names are extracted from the pull request event. The branch names are then stored in environment variables and printed as output. -2. Show Output result for source-branch and target-branch: The source and target branch names are printed to the console. +2. Show Output result for source-branch and target-branch: The source and target branch names are printed to the console. -3. Check branch name for develop PRs: If the target branch is develop, then the source branch is checked against a regular expression to ensure that it follows the GitFlow naming convention. If the branch name is invalid, a message is printed to the console and the workflow exits with a status code of 1. +3. Check branch name for develop PRs: If the target branch is develop, then the source branch is checked against a regular expression to ensure that it follows the GitFlow naming convention. If the branch name is invalid, a message is printed to the console and the workflow exits with a status code of 1. -4. Check branch name for main PRs: If the target branch is main, then the source branch is checked against a regular expression to ensure that it is either a hotfix or a release branch. If the branch name is invalid, a message is printed to the console and the workflow exits with a status code of 1. +4. Check branch name for main PRs: If the target branch is main, then the source branch is checked against a regular expression to ensure that it is either a hotfix or a release branch. If the branch name is invalid, a message is printed to the console and the workflow exits with a status code of 1. Note: The GitFlow naming convention for branches is as follows: -- Feature branches: feature/ -- Hotfix branches: hotfix/ -- Release branches: release/(rc) - -## Build Release Workflow -This GitHub Actions workflow is responsible for building and releasing software for multiple platforms (Windows, M1 MacOS, Intel MacOS, and Docker). -The workflow has four jobs: - -- `trigger-windows-build` -- `trigger-macos-build` -- `trigger-intel-build` -- `trigger-docker-build` - -Each job uses the `aurelien-baudet/workflow-dispatch` action to trigger another workflow, respectively `windows10_build.yml`, `m1_macos_build.yml`, `intel_macos_build.yml`, and `docker.yml`. The `GITHUB_TOKEN` is passed as a secret so that the triggered workflows have access to the necessary permissions. The `wait-for-completion-timeout` is set to 2 hours, which is the maximum amount of time the job will wait for the triggered workflow to complete. - -## Docker Workflow -This GitHub Actions workflow is responsible for building and pushing the docker image to the itHub Container Registry. This workflow is triggered when a new change is pushed to the main branch of the repository, and the Docker image is published to the GitHub Container Registry. - -Steps ------ - -1. Checkout Code: This step checks out the code from the GitHub repository. - -2. Login to GitHub Container Registry: This step logs into the GitHub Container Registry using the GitHub Actions token. - -3. Setup Commit Hash: This step sets the commit hash of the code that is being built. - -4. Build Env File: This step builds the environment file for the Docker image. - -5. Building the Docker Image: This step builds the Docker image using the scripts in the `build/docker` directory. - -6. Publishing the Docker Image: This step publishes the Docker image to the GitHub Container Registry. The Docker image is only pushed to the registry if the branch being built is `main`. - -## Release Drafter Workflow -This GitHub Actions workflow is designed to automatically generate and update draft releases in a GitHub repository. The workflow is triggered when it is manually dispatched, allowing you to control when the draft releases are updated. - -## GH Pages Workflow -This GitHub Actions workflow is responsible for building the documentation and deploying it to GitHub Pages. This workflow is triggered when a new change is pushed to the `main` or `release` branch of the repository, and the documentation is published to GitHub Pages. - -## Integration Test Workflow -This GitHub Action is used to run integration tests on your code repository. It is triggered on pushes to the `release/*` or `main` branches, and it runs on the latest version of Ubuntu. - -The workflow consists of the following steps: - -1. Check out the code from the repository -2. Set up Python 3.9 -3. Install Poetry, a package and dependency manager for Python -4. Load a cached virtual environment created by Poetry, to speed up the process if possible -5. Install dependencies specified in the `poetry.lock` file -6. Run the integration tests using the `terminal.py` script -7. Upload a summary of the test results to Slack - -The results of the tests are captured in a file called `result.txt`. The summary of the tests, including information about failed tests, is then uploaded to Slack using the `adrey/slack-file-upload-action` GitHub Action. - -## Linting Workflow -This GitHub Actions workflow is responsible for running linting checks on the codebase. This workflow is triggered on pull request events such as `opened`, `synchronize`, and `edited`, and push events on branches with names that start with `feature/`, `hotfix/`, or `release/`. The workflow also sets a number of environment variables and uses Github Actions caching to improve performance. - -It consists of two jobs: `code-linting` and `markdown-link-check`. - -The first job, `code-linting`, runs on an Ubuntu machine and performs several linting tasks on the code in the repository, including: - -- Checking out the code from the repository -- Setting up Python 3.9 -- Installing a number of Python packages necessary for the linting tasks -- Running `bandit` to check for security vulnerabilities -- Running `black` to check the code formatting -- Running `codespell` to check the spelling of comments, strings, and variable names -- Running `ruff` to check the use of Python -- Running `mypy` to check the type annotations -- Running `pyupgrade` to upgrade Python 2 code to Python 3 -- Running `pylint` to perform static analysis of the code - -The second job, `markdown-link-check`, runs on an Ubuntu machine and performs linting of the markdown files in the repository. It uses a Docker container `avtodev/markdown-lint` to perform the linting. - -## MacOS Build Workflow -This GitHub Actions workflow is used to build a version of the OpenBB Platform CLI for M1 MacOS. The build process includes installing necessary dependencies, building the terminal application using PyInstaller, creating a DMG file for distribution, and running integration tests on the built application. - -Jobs ----- - -The workflow consists of a single job named `Build` which runs on self-hosted MacOS systems with ARM64 architecture. The job performs the following steps: - -1. Checkout: The main branch of the repository is checked out, allowing for the commit hashes to line up. -2. Git Log: The log of the Git repository is displayed. -3. Install create-dmg: The `create-dmg` tool is installed using Homebrew. -4. Clean Previous Path: The previous PATH environment variable is cleared and restored to its default values. -5. Setup Conda Caching: The miniconda environment is set up using a caching mechanism for faster workflow execution after the first run. -6. Setup Miniconda: Miniconda is set up using the `conda-3-9-env-full.yaml` environment file, with channels `conda-forge` and `defaults`, and with the `build_env` environment activated. -7. Run Poetry: Poetry is used to install the dependencies for the project. -8. Install PyInstaller: PyInstaller is installed using Poetry. -9. Poetry Install Portfolio Optimization and Forecasting Toolkits: The portfolio optimization and forecasting toolkits are installed using Poetry. -10. Install Specific Papermill: A specific version of Papermill is installed using pip. -11. Build Bundle: The terminal application is built using PyInstaller, with icons and assets copied to the DMG directory. -12. Create DMG: The DMG file is created using the `create-dmg` tool. -13. Clean up Build Artifacts: The build artifacts such as the terminal directory and DMG directory are removed. -14. Save Build Artifact DMG: The DMG file is saved as a build artifact. -15. Convert & Mount DMG: The DMG file is converted and mounted. -16. Directory Change: The current directory is changed to the mounted DMG file. -17. Unmount DMG: The mounted DMG file is unmounted. -18. Run Integration Tests: The built terminal application is run with integration tests, and the results are displayed. - -Finally, the integration tests are run and the results are logged. The workflow is configured to run only when triggered by a workflow dispatch event and runs in a concurrent group, with the ability to cancel in-progress jobs. - -## Nightly Build Workflow -This code is a GitHub Actions workflow configuration file that is used to trigger other workflows when certain events occur. The main purpose of this workflow is to trigger builds on different platforms when a release is made or a pull request is made to the main branch. - -This workflow is triggered at UTC+0 daily by the GitHub Action schedule event. - -The job includes the following steps: - -1. Trigger Windows Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the windows10_build.yml workflow. - -2. Trigger macOS Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the m1_macos_build.yml workflow - -3. Trigger Intel Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the intel_macos_build.yml workflow +- Feature branches: feature/ +- Hotfix branches: hotfix/ +- Release branches: release/(rc) -4. Trigger Docker Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the docker.yml workflow +## Deploy to PyPI - Nightly -This workflow also uses a concurrency setting that groups the jobs by the workflow and ref, and cancels any in-progress jobs. - -## Nightly PyPI Publish Workflow This workflow is used to publish the latest version of the OpenBB Platform CLI to PyPI. The workflow is triggered at UTC+0 daily by the GitHub Action schedule event. It does this by first updating the `pyproject.toml` file with a pre-determined version string of the form `.dev`, where `` represents the current day's date as a 8 digit number. @@ -169,12 +36,13 @@ Then, the code installs `pypa/build` and uses `python -m build` to create a bina Finally, it uses the PyPA specific action `gh-action-pypi-publish` to publish the created files to PyPI. -## PYPI publish Workflow +## Deploy the OpenBB Platform to Test PyPI + The Github Action code `Deploy to PyPI` is used to deploy a Python project to PyPI (Python Package Index) and TestPyPI, which is a separate package index for testing purposes. The code is triggered on two events: -1. Push event: The code is triggered whenever there is a push to the `release/*` and `main` branches. +1. Push event: The code is triggered whenever there is a push to the `release/*` and `main` branches. -2. Workflow dispatch event: The code can be manually triggered by the workflow dispatch event. +2. Workflow dispatch event: The code can be manually triggered by the workflow dispatch event. The code sets the concurrency to the `group` and the option `cancel-in-progress` is set to `true` to ensure that the running jobs in the same `group` are cancelled in case another job is triggered. @@ -186,47 +54,47 @@ Similarly, the `deploy-pypi` job is triggered only if the pushed branch starts w Note: The code uses the `pypa/build` package for building the binary wheel and source tarball, and the `pypa/gh-action-pypi-publish@release/v1` Github Action for publishing the distributions to PyPI and TestPyPI. -## Unit Tests Workflow -This workflow is used to run unit tests on the OpenBB Platform CLI. The workflow is triggered on the following events: -The events this workflow will respond to are: +## Draft release -1. Pull requests that are opened, synchronized, edited, or closed. The pull request must be made to the `develop` or `main` branches. +This GitHub Actions workflow is designed to automatically generate and update draft releases in a GitHub repository. The workflow is triggered when it is manually dispatched, allowing you to control when the draft releases are updated. -2. Pushes to the `release/*` branches. +## General Linting -Each job in the workflow specifies a set of steps that are executed in order. +This GitHub Actions workflow is responsible for running linting checks on the codebase. This workflow is triggered on pull request events such as `opened`, `synchronize`, and `edited`, and push events on branches with names that start with `feature/`, `hotfix/`, or `release/`. The workflow also sets a number of environment variables and uses Github Actions caching to improve performance. -The first job, `check-files-changed`, checks whether there are any changes to certain file types in the repository, such as Python files and lockfiles. If there are changes, then the `check-changes` output variable is set to `true`. +It consists of two jobs: `code-linting` and `markdown-link-check`. + +The first job, `code-linting`, runs on an Ubuntu machine and performs several linting tasks on the code in the repository, including: + +- Checking out the code from the repository +- Setting up Python 3.9 +- Installing a number of Python packages necessary for the linting tasks +- Running `bandit` to check for security vulnerabilities +- Running `black` to check the code formatting +- Running `codespell` to check the spelling of comments, strings, and variable names +- Running `ruff` to check the use of Python +- Running `pylint` to perform static analysis of the code +- Running `mypy` to check the type annotations +- Running `pydocstyle` to check the docstrings + +The second job, `markdown-link-check`, runs on an Ubuntu machine and performs linting of the markdown files in the repository. It uses a Docker container `avtodev/markdown-lint` to perform the linting. + +## Deploy to GitHub Pages + +This GitHub Actions workflow is responsible for building the documentation and deploying it to GitHub Pages. This workflow is triggered when a new change is pushed to the `main` or `release` branch of the repository, and the documentation is published to GitHub Pages. -The next job, `base-test`, runs a series of tests if `check-changes` is `true` and the base branch of the pull request is `develop`. This job sets up a Python 3.9 environment, installs Poetry, and then runs tests using `pytest`. Finally, it starts the terminal and exits. +## Pull Request Labels -The next job, `tests-python`, runs tests for different versions of Python (3.8, 3.9, and 3.10) on the `ubuntu-latest` operating system. It sets up the specified Python version, installs Poetry and dependencies, and then runs tests using `pytest`. +Automatic labelling of pull requests. -The next job, `full-test`, uses the GitHub Actions `checkout` action to checkout the code, followed by the `setup-python` action to set up the specified version of Python. Then, the `install-poetry` action is used to install the package manager Poetry, and a cache is set up using the `actions/cache` action to avoid re-installing dependencies. After that, the dependencies are installed using Poetry, and a list of installed packages is displayed. Then, the tests are run using `pytest`, and finally, the `terminal.py` script is started and exited. +## 🚉 Integration test Platform (API) -The last job, `tests-conda`, sets up a Miniconda environment using the `setup-miniconda` action. The environment is specified using a YAML file and is activated. Then, the tests are run. +Run `openbb_platform` API integration tests, -## Windows 10 Build Workflow -This is a GitHub Actions workflow file that automates the build and testing process for the OpenBB Platform CLI on Windows 10. The workflow consists of two jobs: +## 🖥️ Unit test CLI -1. Windows-Build -2. Build-Exe +Run `cli` directory unit tests. -- The Windows-Build job does the following: - - Sets up the Windows Git configuration for long file paths. - - Checks out the repository code. - - Sets up Python 3.9 and creates an OpenBB environment using poetry. - - Installs necessary packages and builds the terminal using PyInstaller. - - Uploads the built artifact to GitHub as an artifact. -- The Build-Exe job does the following: - - Sets up the Windows Git configuration for long file paths. - - Checks out the repository code. - - Downloads the built artifact from the previous Windows-Build job. - - Copies the files into an app folder for building the EXE file. - - Builds the EXE file using NSIS. - - Uploads the built EXE as an artifact to GitHub. - - Runs integration tests on the terminal and saves the results to a text file. - - Uploads the test results summary to Slack. - - Cleans up previous build files and artifacts. +## 🚉 Unit test Platform -This workflow is triggered by the `workflow_dispatch` event and runs in concurrency with other workflows in the same group, with the ability to cancel in-progress builds. The concurrency group is defined as `${{ github.workflow }}-${{ github.ref }}`. \ No newline at end of file +Run `openbb_platform` directory unit tests - providers, extensions, etc. diff --git a/.github/workflows/pypi-nightly.yml b/.github/workflows/deploy-pypi-nightly.yml similarity index 100% rename from .github/workflows/pypi-nightly.yml rename to .github/workflows/deploy-pypi-nightly.yml diff --git a/.github/workflows/test_pypi_platform.yml b/.github/workflows/deploy-test-pypi.yml similarity index 96% rename from .github/workflows/test_pypi_platform.yml rename to .github/workflows/deploy-test-pypi.yml index 54fe09f7dc24..c5a3bb6a3756 100644 --- a/.github/workflows/test_pypi_platform.yml +++ b/.github/workflows/deploy-test-pypi.yml @@ -1,4 +1,4 @@ -name: Deploy the OpenBB Platform and the OpenBBTerminal to Test PyPI +name: Deploy the OpenBB Platform to Test PyPI on: push: diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 3dab21e7812d..01e52ee80d56 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -1,4 +1,4 @@ -name: Release Drafter +name: Draft release on: workflow_dispatch: diff --git a/.github/workflows/linting.yml b/.github/workflows/general-linting.yml similarity index 96% rename from .github/workflows/linting.yml rename to .github/workflows/general-linting.yml index 2abf2e5455f0..02cf0b3454db 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/general-linting.yml @@ -1,10 +1,6 @@ name: General Linting env: - OPENBB_ENABLE_QUICK_EXIT: true - OPENBB_LOG_COLLECT: false - OPENBB_USE_PROMPT_TOOLKIT: false - OPENBB_FILE_OVERWRITE: true PIP_DEFAULT_TIMEOUT: 100 on: diff --git a/.github/workflows/labels-PR.yml b/.github/workflows/gh-pr-labels.yml similarity index 100% rename from .github/workflows/labels-PR.yml rename to .github/workflows/gh-pr-labels.yml diff --git a/.github/workflows/labels-issue.yml b/.github/workflows/labels-issue.yml deleted file mode 100644 index 01486ba0824a..000000000000 --- a/.github/workflows/labels-issue.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "Set Issue Label and Assignee" -on: - issues: - types: [opened] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: Naturalclar/issue-action@v2.0.2 - with: - title-or-body: "both" - parameters: - '[ {"keywords": ["bug", "error", "issue"], "labels": ["bug"]}, - {"keywords": ["help", "guidance"], "labels": ["help-wanted"], "assignees": ["colin99d"]}, - {"keywords": ["portfolio"], "labels": ["portfolio"], "assignees": ["JerBouma", "montezdesousa"]}, - {"keywords": ["dashboards"], "labels": ["dashboards"], "assignees": ["colin99d"]}, - {"keywords": ["dependencies"], "labels": ["dependencies"], "assignees": ["piiq"]}, - {"keywords": ["build"], "labels": ["build"], "assignees": ["piiq"]}, - {"keywords": ["jupyter"], "labels": ["jupyterlab"], "assignees": ["piiq"]}, - {"keywords": ["reports"], "labels": ["notebookreports"], "assignees": ["piiq"]}, - {"keywords": ["installer"], "labels": ["installer"], "assignees": ["piiq", "andrewkenreich"]}, - {"keywords": ["pytest", "tests"], "labels": ["tests"], "assignees": ["Chavithra"]}, - {"keywords": ["guides"], "labels": ["guides"], "assignees": ["JerBouma"]}, - {"keywords": ["crypto"], "labels": ["crypto"], "assignees": ["minhhoang1023"]} - ]' - github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml deleted file mode 100644 index 90d68cb9de5a..000000000000 --- a/.github/workflows/nightly-build.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Nightly Build - -env: - OPENBB_ENABLE_QUICK_EXIT: true - OPENBB_LOG_COLLECT: false - OPENBB_USE_PROMPT_TOOLKIT: false - PIP_DEFAULT_TIMEOUT: 100 - OPENBB_FILE_OVERWRITE: true - PYTHONNOUSERSITE: 1 - -on: - schedule: - - cron: "0 0 * * *" - -permissions: - actions: write - -jobs: - trigger-pypi-build: - runs-on: ubuntu-latest - steps: - - name: Trigger PyPI Build - uses: aurelien-baudet/workflow-dispatch@v2 - with: - workflow: pypi-nightly.yml - token: ${{ secrets.GITHUB_TOKEN }} - - trigger-api-integration-test: - runs-on: ubuntu-latest - steps: - - name: Trigger Platform API Integration Test - uses: aurelien-baudet/workflow-dispatch@v2 - with: - workflow: platform-api-integration-test.yml - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml deleted file mode 100644 index efc2fe5d9d8f..000000000000 --- a/.github/workflows/pypi.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Deploy to PyPI - -on: - push: - branches: - - release/v3/* - - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - deploy-test-pypi: - name: Build and publish 📦 to TestPyPI - if: startsWith(github.ref, 'refs/heads/release/') - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Setup Python 3.9 - uses: actions/setup-python@v4 - with: - python-version: "3.9" - - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - . - - - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} - repository_url: https://test.pypi.org/legacy/ - - deploy-pypi: - name: Build and publish 📦 to PyPI - if: startsWith(github.ref, 'refs/heads/main') - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Setup Python 3.9 - uses: actions/setup-python@v4 - with: - python-version: "3.9" - - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - . - - - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/platform-api-integration-test.yml b/.github/workflows/test-integration-platform.yml similarity index 98% rename from .github/workflows/platform-api-integration-test.yml rename to .github/workflows/test-integration-platform.yml index 11b4c9c5d40f..143ca20d50a7 100644 --- a/.github/workflows/platform-api-integration-test.yml +++ b/.github/workflows/test-integration-platform.yml @@ -1,4 +1,4 @@ -name: API Integration Tests +name: 🚉 Integration test Platform (API) on: workflow_dispatch: diff --git a/.github/workflows/test-unit-cli.yml b/.github/workflows/test-unit-cli.yml new file mode 100644 index 000000000000..38c6348fc03a --- /dev/null +++ b/.github/workflows/test-unit-cli.yml @@ -0,0 +1,46 @@ +name: 🖥️ Unit test CLI + +on: + pull_request: + branches: + - develop + paths: + - 'cli/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + unit_tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + matrix: + python_version: + ["3.9", "3.10", "3.11"] + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Install Python ${{ matrix.python_version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python_version }} + allow-prereleases: true + cache: "pip" + + - name: Cache pip packages + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('cli/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Run tests + run: | + pip install nox + nox -f .github/scripts/noxfile.py -s unit_test_cli --python ${{ matrix.python_version }} diff --git a/.github/workflows/platform-core.yml b/.github/workflows/test-unit-platform.yml similarity index 89% rename from .github/workflows/platform-core.yml rename to .github/workflows/test-unit-platform.yml index 84ac783cfaf1..e5323f380500 100644 --- a/.github/workflows/platform-core.yml +++ b/.github/workflows/test-unit-platform.yml @@ -1,4 +1,4 @@ -name: Test Platform V4 +name: 🚉 Unit test Platform on: pull_request: @@ -43,4 +43,4 @@ jobs: - name: Run tests run: | pip install nox - nox -f .github/scripts/noxfile.py -s tests --python ${{ matrix.python_version }} + nox -f .github/scripts/noxfile.py -s unit_test_platform --python ${{ matrix.python_version }} diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml deleted file mode 100644 index 32dfc58d5aa7..000000000000 --- a/.github/workflows/unit-test.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Unit Test - -env: - OPENBB_ENABLE_QUICK_EXIT: true - OPENBB_LOG_COLLECT: false - OPENBB_USE_PROMPT_TOOLKIT: false - OPENBB_FILE_OVERWRITE: true - OPENBB_ENABLE_CHECK_API: false - OPENBB_PREVIOUS_USE: true - OPENBB_USE_INTERACTIVE_DF: false - PIP_DEFAULT_TIMEOUT: 100 - -on: - pull_request: - branches: - - develop - - main - types: [opened, synchronize, edited, closed, labeled] - push: - branches: - - release/* - merge_group: - types: [checks_requested] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - check-files-changed: - name: Check for changes - runs-on: ubuntu-latest - # Run this job only if the PR is not merged, PR != draft and the event is not a push - if: github.event.pull_request.merged == false && github.event_name != 'push' && github.event.pull_request.draft == false - outputs: - check-changes: ${{ steps.check-changes.outputs.check-changes }} - check-platform-changes: ${{ steps.check-platform-changes.outputs.check-platform-changes }} # New output for openbb_platform changes - steps: - - name: Checkout - uses: actions/checkout@v1 # v1 is used to preserve the git history and works with the git diff command - with: - fetch-depth: 100 - # The GitHub token is preserved by default but this job doesn't need - # to be able to push to GitHub. - - # Check for changes to python files, lockfiles and the openbb_terminal folder - - name: Check for changes to files to trigger unit test - id: check-changes - run: | - current_branch=$(jq -r .pull_request.base.ref "$GITHUB_EVENT_PATH") - - if git diff --name-only origin/$current_branch HEAD | grep -E ".py$|openbb_terminal\/.*|pyproject.toml|poetry.lock|requirements.txt|requirements-full.txt"; then - echo "check-changes=true" >> $GITHUB_OUTPUT - else - echo "check-changes=false" >> $GITHUB_OUTPUT - fi - - # Check for changes to openbb_platform - - name: Check for changes to openbb_platform - id: check-platform-changes - run: | - current_branch=$(jq -r .pull_request.base.ref "$GITHUB_EVENT_PATH") - - if git diff --name-only origin/$current_branch HEAD | grep -E "openbb_platform\/.*"; then - echo "check-platform-changes=true" >> $GITHUB_OUTPUT - else - echo "check-platform-changes=false" >> $GITHUB_OUTPUT - fi - - - name: Show output of previous step - run: | - echo "check-changes=${{ steps.check-changes.outputs.check-changes }}" - echo "check-platform-changes=${{ steps.check-platform-changes.outputs.check-platform-changes }}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f507829eedd..887df4e3e2c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: "--ignore-words-list=VAI,MIS,shs,gard,te,commun,parana,ro,zar,vie,hel,jewl,zlot,ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold,ist,varian,datas,ake,creat,statics,ket,toke,certi,buidl,ot,fo", "--quiet-level=2", "--skip=./**/tests/**,./**/test_*.py,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml,build/pyinstaller/*,./website/config.toml", - "-x=.github/workflows/linting.yml" + "-x=.github/workflows/general-linting.yml" ] - repo: local hooks: diff --git a/cli/openbb_cli/config/menu_text.py b/cli/openbb_cli/config/menu_text.py index ac3d0b9ca7dc..ed2ba6ddb428 100644 --- a/cli/openbb_cli/config/menu_text.py +++ b/cli/openbb_cli/config/menu_text.py @@ -106,20 +106,6 @@ def add_raw(self, text: str, left_spacing: bool = False): else: self.menu_text += text - def add_section( - self, text: str, description: str = "", leading_new_line: bool = False - ): - """Append raw text (without translation).""" - spacing = (self.CMD_NAME_LENGTH - len(text) + self.SECTION_SPACING) * " " - if description: - text = f"{text}{spacing}{description}\n" - - if leading_new_line: - self.menu_text += "\n" + text - - else: - self.menu_text += text - def add_info(self, text: str): """Append information text (after translation).""" self.menu_text += f"[info]{text}:[/info]\n" diff --git a/cli/tests/test_argparse_translator.py b/cli/tests/test_argparse_translator.py new file mode 100644 index 000000000000..e88eb1b811f2 --- /dev/null +++ b/cli/tests/test_argparse_translator.py @@ -0,0 +1,94 @@ +"""Test the Argparse Translator.""" + +from argparse import ArgumentParser + +import pytest +from openbb_cli.argparse_translator.argparse_translator import ( + ArgparseTranslator, + CustomArgument, + CustomArgumentGroup, +) + +# pylint: disable=protected-access + + +def test_custom_argument_action_validation(): + """Test that CustomArgument raises an error for invalid actions.""" + with pytest.raises(ValueError) as excinfo: + CustomArgument( + name="test", + type=bool, + dest="test", + default=False, + required=True, + action="store", + help="Test argument", + nargs=None, + choices=None, + ) + assert 'action must be "store_true"' in str(excinfo.value) + + +def test_custom_argument_remove_props_on_store_true(): + """Test that CustomArgument removes type, nargs, and choices on store_true.""" + argument = CustomArgument( + name="verbose", + type=None, + dest="verbose", + default=None, + required=False, + action="store_true", + help="Verbose output", + nargs=None, + choices=None, + ) + assert argument.type is None + assert argument.nargs is None + assert argument.choices is None + + +def test_custom_argument_group(): + """Test the CustomArgumentGroup class.""" + args = [ + CustomArgument( + name="test", + type=int, + dest="test", + default=1, + required=True, + action="store", + help="Test argument", + nargs=None, + choices=None, + ) + ] + group = CustomArgumentGroup(name="Test Group", arguments=args) + assert group.name == "Test Group" + assert len(group.arguments) == 1 + assert group.arguments[0].name == "test" + + +def test_argparse_translator_setup(): + """Test the ArgparseTranslator setup.""" + + def test_function(test_arg: int): + """A test function.""" + return test_arg * 2 + + translator = ArgparseTranslator(func=test_function) + parser = translator.parser + assert isinstance(parser, ArgumentParser) + assert "--test_arg" in parser._option_string_actions + + +def test_argparse_translator_execution(): + """Test the ArgparseTranslator execution.""" + + def test_function(test_arg: int) -> int: + """A test function.""" + return test_arg * 2 + + translator = ArgparseTranslator(func=test_function) + parsed_args = translator.parser.parse_args(["--test_arg", "3"]) + result = translator.execute_func(parsed_args) + assert result == 6 diff --git a/cli/tests/test_argparse_translator_obbject_registry.py b/cli/tests/test_argparse_translator_obbject_registry.py new file mode 100644 index 000000000000..a37e5a335415 --- /dev/null +++ b/cli/tests/test_argparse_translator_obbject_registry.py @@ -0,0 +1,89 @@ +"""Test OBBject Registry.""" + +from unittest.mock import Mock + +import pytest +from openbb_cli.argparse_translator.obbject_registry import Registry +from openbb_core.app.model.obbject import OBBject + +# pylint: disable=redefined-outer-name, protected-access + + +@pytest.fixture +def registry(): + """Fixture to create a Registry instance for testing.""" + return Registry() + + +@pytest.fixture +def mock_obbject(): + """Fixture to create a mock OBBject for testing.""" + + class MockModel: + """Mock model for testing.""" + + def __init__(self, value): + self.mock_value = value + self._model_json_schema = "mock_json_schema" + + def model_json_schema(self): + return self._model_json_schema + + obb = Mock(spec=OBBject) + obb.id = "123" + obb.provider = "test_provider" + obb.extra = {"command": "test_command"} + obb._route = "/test/route" + obb._standard_params = Mock() + obb._standard_params.__dict__ = {} + obb.results = [MockModel(1), MockModel(2)] + return obb + + +def test_listing_all_obbjects(registry, mock_obbject): + """Test listing all obbjects with additional properties.""" + registry.register(mock_obbject) + + all_obbjects = registry.all + assert len(all_obbjects) == 1 + assert all_obbjects[0]["command"] == "test_command" + assert all_obbjects[0]["provider"] == "test_provider" + + +def test_registry_initialization(registry): + """Test the Registry is initialized correctly.""" + assert registry.obbjects == [] + + +def test_register_new_obbject(registry, mock_obbject): + """Test registering a new OBBject.""" + registry.register(mock_obbject) + assert mock_obbject in registry.obbjects + + +def test_register_duplicate_obbject(registry, mock_obbject): + """Test that duplicate OBBjects are not added.""" + registry.register(mock_obbject) + registry.register(mock_obbject) + assert len(registry.obbjects) == 1 + + +def test_get_obbject_by_index(registry, mock_obbject): + """Test retrieving an obbject by its index.""" + registry.register(mock_obbject) + retrieved = registry.get(0) + assert retrieved == mock_obbject + + +def test_remove_obbject_by_index(registry, mock_obbject): + """Test removing an obbject by index.""" + registry.register(mock_obbject) + registry.remove(0) + assert mock_obbject not in registry.obbjects + + +def test_remove_last_obbject_by_default(registry, mock_obbject): + """Test removing the last obbject by default.""" + registry.register(mock_obbject) + registry.remove() + assert not registry.obbjects diff --git a/cli/tests/test_cli.py b/cli/tests/test_cli.py new file mode 100644 index 000000000000..1fe19344f0f7 --- /dev/null +++ b/cli/tests/test_cli.py @@ -0,0 +1,45 @@ +"""Test the CLI module.""" + +from unittest.mock import patch + +from openbb_cli.cli import main + + +@patch("openbb_cli.cli.bootstrap") +@patch("openbb_cli.cli.launch") +@patch("sys.argv", ["openbb", "--dev", "--debug"]) +def test_main_with_dev_and_debug(mock_launch, mock_bootstrap): + """Test the main function with dev and debug flags.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(True, True) + + +@patch("openbb_cli.cli.bootstrap") +@patch("openbb_cli.cli.launch") +@patch("sys.argv", ["openbb"]) +def test_main_without_arguments(mock_launch, mock_bootstrap): + """Test the main function without arguments.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(False, False) + + +@patch("openbb_cli.cli.bootstrap") +@patch("openbb_cli.cli.launch") +@patch("sys.argv", ["openbb", "--dev"]) +def test_main_with_dev_only(mock_launch, mock_bootstrap): + """Test the main function with dev flag only.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(True, False) + + +@patch("openbb_cli.cli.bootstrap") +@patch("openbb_cli.cli.launch") +@patch("sys.argv", ["openbb", "--debug"]) +def test_main_with_debug_only(mock_launch, mock_bootstrap): + """Test the main function with debug flag only.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(False, True) diff --git a/cli/tests/test_config_completer.py b/cli/tests/test_config_completer.py new file mode 100644 index 000000000000..71efad99d5b8 --- /dev/null +++ b/cli/tests/test_config_completer.py @@ -0,0 +1,78 @@ +"""Test the Config completer.""" + +import pytest +from openbb_cli.config.completer import WordCompleter +from prompt_toolkit.completion import CompleteEvent +from prompt_toolkit.document import Document + +# pylint: disable=redefined-outer-name, import-outside-toplevel + + +@pytest.fixture +def word_completer(): + """Return a simple word completer.""" + words = ["test", "example", "demo"] + return WordCompleter(words, ignore_case=True) + + +def test_word_completer_simple(word_completer): + """Test the word completer with a simple word list.""" + doc = Document(text="ex", cursor_position=2) + completions = list(word_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 1 + assert completions[0].text == "example" + + +def test_word_completer_case_insensitive(word_completer): + """Test the word completer with case-insensitive matching.""" + doc = Document(text="Ex", cursor_position=2) + completions = list(word_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 1 + assert completions[0].text == "example" + + +def test_word_completer_no_match(word_completer): + """Test the word completer with no matches.""" + doc = Document(text="xyz", cursor_position=3) + completions = list(word_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 0 + + +@pytest.fixture +def nested_completer(): + """Return a nested completer.""" + from openbb_cli.config.completer import NestedCompleter + + data = { + "show": { + "version": None, + "interfaces": None, + "clock": None, + "ip": {"interface": {"brief": None}}, + }, + "exit": None, + "enable": None, + } + return NestedCompleter.from_nested_dict(data) + + +def test_nested_completer_root_command(nested_completer): + """Test the nested completer with a root command.""" + doc = Document(text="sh", cursor_position=2) + completions = list(nested_completer.get_completions(doc, CompleteEvent())) + assert "show" in [c.text for c in completions] + + +def test_nested_completer_sub_command(nested_completer): + """Test the nested completer with a sub-command.""" + doc = Document(text="show ", cursor_position=5) + completions = list(nested_completer.get_completions(doc, CompleteEvent())) + assert "version" in [c.text for c in completions] + assert "interfaces" in [c.text for c in completions] + + +def test_nested_completer_no_match(nested_completer): + """Test the nested completer with no matches.""" + doc = Document(text="random ", cursor_position=7) + completions = list(nested_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 0 diff --git a/cli/tests/test_config_console.py b/cli/tests/test_config_console.py new file mode 100644 index 000000000000..60ff91a88ac1 --- /dev/null +++ b/cli/tests/test_config_console.py @@ -0,0 +1,47 @@ +"""Test Config Console.""" + +from unittest.mock import patch + +import pytest +from openbb_cli.config.console import Console +from rich.text import Text + +# pylint: disable=redefined-outer-name, unused-argument, unused-variable, protected-access + + +@pytest.fixture +def mock_settings(): + """Mock settings to inject into Console.""" + + class MockSettings: + TEST_MODE = False + ENABLE_RICH_PANEL = True + SHOW_VERSION = True + VERSION = "1.0" + + return MockSettings() + + +@pytest.fixture +def console(mock_settings): + """Create a Console instance with mocked settings.""" + with patch("rich.console.Console") as MockRichConsole: # noqa: F841 + return Console(settings=mock_settings) + + +def test_print_without_panel(console, mock_settings): + """Test printing without a rich panel when disabled.""" + mock_settings.ENABLE_RICH_PANEL = False + with patch.object(console._console, "print") as mock_print: + console.print(text="Hello, world!", menu="Home Menu") + mock_print.assert_called_once_with("Hello, world!") + + +def test_blend_text(): + """Test blending text colors.""" + message = "Hello" + color1 = (255, 0, 0) # Red + color2 = (0, 0, 255) # Blue + blended_text = Console._blend_text(message, color1, color2) + assert isinstance(blended_text, Text) + assert "Hello" in blended_text.plain diff --git a/cli/tests/test_config_menu_text.py b/cli/tests/test_config_menu_text.py new file mode 100644 index 000000000000..10956ccbac55 --- /dev/null +++ b/cli/tests/test_config_menu_text.py @@ -0,0 +1,68 @@ +"""Test Config Menu Text.""" + +import pytest +from openbb_cli.config.menu_text import MenuText + +# pylint: disable=redefined-outer-name, protected-access + + +@pytest.fixture +def menu_text(): + """Fixture to create a MenuText instance for testing.""" + return MenuText(path="/test/path") + + +def test_initialization(menu_text): + """Test initialization of the MenuText class.""" + assert menu_text.menu_text == "" + assert menu_text.menu_path == "/test/path" + assert menu_text.warnings == [] + + +def test_add_raw(menu_text): + """Test adding raw text.""" + menu_text.add_raw("Example raw text") + assert "Example raw text" in menu_text.menu_text + + +def test_add_info(menu_text): + """Test adding informational text.""" + menu_text.add_info("Info text") + assert "[info]Info text:[/info]" in menu_text.menu_text + + +def test_add_cmd(menu_text): + """Test adding a command.""" + menu_text.add_cmd("command", "Performs an action") + assert "command" in menu_text.menu_text + assert "Performs an action" in menu_text.menu_text + + +def test_format_cmd_name(menu_text): + """Test formatting of command names that are too long.""" + long_name = "x" * 50 # Assuming CMD_NAME_LENGTH is 23 + formatted_name = menu_text._format_cmd_name(long_name) + assert len(formatted_name) <= menu_text.CMD_NAME_LENGTH + assert menu_text.warnings # Check that a warning was added + + +def test_format_cmd_description(menu_text): + """Test truncation of long descriptions.""" + long_description = "y" * 100 # Assuming CMD_DESCRIPTION_LENGTH is 65 + formatted_description = menu_text._format_cmd_description("cmd", long_description) + assert len(formatted_description) <= menu_text.CMD_DESCRIPTION_LENGTH + + +def test_add_menu(menu_text): + """Test adding a menu item.""" + menu_text.add_menu("Settings", "Configure your settings") + assert "Settings" in menu_text.menu_text + assert "Configure your settings" in menu_text.menu_text + + +def test_add_setting(menu_text): + """Test adding a setting.""" + menu_text.add_setting("Enable Feature", True, "Feature description") + assert "Enable Feature" in menu_text.menu_text + assert "Feature description" in menu_text.menu_text + assert "[green]" in menu_text.menu_text diff --git a/cli/tests/test_config_setup.py b/cli/tests/test_config_setup.py new file mode 100644 index 000000000000..7c01469063b4 --- /dev/null +++ b/cli/tests/test_config_setup.py @@ -0,0 +1,49 @@ +"""Test the Config Setup.""" + +from unittest.mock import patch + +import pytest +from openbb_cli.config.setup import bootstrap + +# pylint: disable=unused-variable + + +def test_bootstrap_creates_directory_and_file(): + """Test that bootstrap creates the settings directory and environment file.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch: + bootstrap() + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) + mock_touch.assert_called_once_with(exist_ok=True) + + +def test_bootstrap_directory_exists(): + """Test bootstrap when the directory already exists.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch: + bootstrap() + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) + mock_touch.assert_called_once_with(exist_ok=True) + + +def test_bootstrap_file_exists(): + """Test bootstrap when the environment file already exists.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch: + bootstrap() + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) + mock_touch.assert_called_once_with(exist_ok=True) + + +def test_bootstrap_permission_error(): + """Test bootstrap handles permission errors gracefully.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch, pytest.raises( # noqa: F841 + PermissionError + ): + mock_mkdir.side_effect = PermissionError("No permission to create directory") + bootstrap() # Expecting to raise a PermissionError and be caught by pytest.raises diff --git a/cli/tests/test_config_style.py b/cli/tests/test_config_style.py new file mode 100644 index 000000000000..72e9c2a2f38f --- /dev/null +++ b/cli/tests/test_config_style.py @@ -0,0 +1,60 @@ +"""Test Config Style.""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.config.style import Style + +# pylint: disable=redefined-outer-name, protected-access + + +@pytest.fixture +def mock_style_directory(tmp_path): + """Fixture to create a mock styles directory.""" + (tmp_path / "styles" / "default").mkdir(parents=True, exist_ok=True) + return tmp_path / "styles" + + +@pytest.fixture +def style(mock_style_directory): + """Fixture to create a Style instance for testing.""" + return Style(directory=mock_style_directory) + + +def test_initialization(style): + """Test that Style class initializes with default properties.""" + assert style.line_width == 1.5 + assert isinstance(style.console_style, dict) + + +@patch("pathlib.Path.exists", MagicMock(return_value=True)) +@patch("pathlib.Path.rglob") +def test_load_styles(mock_rglob, style, mock_style_directory): + """Test loading styles from directories.""" + mock_rglob.return_value = [mock_style_directory / "default" / "dark.richstyle.json"] + style._load(mock_style_directory) + assert "dark" in style.console_styles_available + + +@patch("builtins.open", new_callable=MagicMock) +@patch("json.load", MagicMock(return_value={"background": "black"})) +def test_from_json(mock_open, style, mock_style_directory): + """Test loading style from a JSON file.""" + json_file = mock_style_directory / "dark.richstyle.json" + result = style._from_json(json_file) + assert result == {"background": "black"} + mock_open.assert_called_once_with(json_file) + + +def test_apply_invalid_style(style, mock_style_directory, capsys): + """Test applying an invalid style and falling back to default.""" + style.apply("nonexistent", mock_style_directory) + captured = capsys.readouterr() + assert "Invalid console style" in captured.out + + +def test_available_styles(style): + """Test listing available styles.""" + style.console_styles_available = {"dark": Path("/path/to/dark.richstyle.json")} + assert "dark" in style.available_styles diff --git a/cli/tests/test_controllers_base_controller.py b/cli/tests/test_controllers_base_controller.py new file mode 100644 index 000000000000..465a5b61e5e6 --- /dev/null +++ b/cli/tests/test_controllers_base_controller.py @@ -0,0 +1,79 @@ +"""Test the base controller.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.base_controller import BaseController + +# pylint: disable=unused-argument, unused-variable + + +class TestableBaseController(BaseController): + """Testable Base Controller.""" + + def __init__(self, queue=None): + """Initialize the TestableBaseController.""" + self.PATH = "/valid/path/" + super().__init__(queue=queue) + + def print_help(self): + """Print help.""" + + +def test_base_controller_initialization(): + """Test the initialization of the base controller.""" + with patch.object(TestableBaseController, "check_path", return_value=None): + controller = TestableBaseController() + assert controller.path == ["valid", "path"] # Checking for correct path split + + +def test_path_validation(): + """Test the path validation method.""" + controller = TestableBaseController() + + with pytest.raises(ValueError): + controller.PATH = "invalid/path" + controller.check_path() + + with pytest.raises(ValueError): + controller.PATH = "/invalid/path" + controller.check_path() + + with pytest.raises(ValueError): + controller.PATH = "/Invalid/Path/" + controller.check_path() + + controller.PATH = "/valid/path/" + + +def test_parse_input(): + """Test the parse input method.""" + controller = TestableBaseController() + input_str = "cmd1/cmd2/cmd3" + expected = ["cmd1", "cmd2", "cmd3"] + result = controller.parse_input(input_str) + assert result == expected + + +def test_switch(): + """Test the switch method.""" + controller = TestableBaseController() + with patch.object(controller, "call_exit", MagicMock()) as mock_exit: + controller.queue = ["exit"] + controller.switch("exit") + mock_exit.assert_called_once() + + +def test_call_help(): + """Test the call help method.""" + controller = TestableBaseController() + with patch("openbb_cli.controllers.base_controller.session.console.print"): + controller.call_help(None) + + +def test_call_exit(): + """Test the call exit method.""" + controller = TestableBaseController() + with patch.object(controller, "save_class", MagicMock()): + controller.queue = ["quit"] + controller.call_exit(None) diff --git a/cli/tests/test_controllers_base_platform_controller.py b/cli/tests/test_controllers_base_platform_controller.py new file mode 100644 index 000000000000..cabfe44cd6bd --- /dev/null +++ b/cli/tests/test_controllers_base_platform_controller.py @@ -0,0 +1,70 @@ +"""Test the BasePlatformController.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.base_platform_controller import PlatformController, Session + +# pylint: disable=redefined-outer-name, protected-access, unused-argument, unused-variable + + +@pytest.fixture +def mock_session(): + """Mock session fixture.""" + with patch( + "openbb_cli.controllers.base_platform_controller.session", + MagicMock(spec=Session), + ) as mock: + yield mock + + +def test_initialization_with_valid_params(mock_session): + """Test the initialization of the BasePlatformController.""" + translators = {"dummy_translator": MagicMock()} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + assert controller._name == "test" + assert controller.translators == translators + + +def test_initialization_without_required_params(): + """Test the initialization of the BasePlatformController without required params.""" + with pytest.raises(ValueError): + PlatformController(name="test", parent_path=["parent"]) + + +def test_command_generation(mock_session): + """Test the command generation method.""" + translator = MagicMock() + translators = {"test_command": translator} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + + # Check if command function is correctly linked + assert "test_command" in controller.translators + + +def test_print_help(mock_session): + """Test the print help method.""" + translators = {"test_command": MagicMock()} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + + with patch( + "openbb_cli.controllers.base_platform_controller.MenuText" + ) as mock_menu_text: + controller.print_help() + mock_menu_text.assert_called_once_with("/parent/test/") + + +def test_sub_controller_generation(mock_session): + """Test the sub controller generation method.""" + translators = {"test_menu_item": MagicMock()} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + + assert "test_menu_item" in controller.translators diff --git a/cli/tests/test_controllers_choices.py b/cli/tests/test_controllers_choices.py new file mode 100644 index 000000000000..6d604751b1f6 --- /dev/null +++ b/cli/tests/test_controllers_choices.py @@ -0,0 +1,51 @@ +"""Test the choices controller.""" + +from argparse import ArgumentParser +from unittest.mock import patch + +import pytest +from openbb_cli.controllers.choices import ( + build_controller_choice_map, +) + +# pylint: disable=redefined-outer-name, protected-access, unused-argument, unused-variable + + +class MockController: + """Mock controller class for testing.""" + + CHOICES_COMMANDS = ["test_command"] + controller_choices = ["test_command", "help"] + + def call_test_command(self, args): + """Mock function for test_command.""" + parser = ArgumentParser() + parser.add_argument( + "--example", choices=["option1", "option2"], help="Example argument." + ) + return parser.parse_args(args) + + +@pytest.fixture +def mock_controller(): + """Mock controller fixture.""" + return MockController() + + +def test_build_command_choice_map(mock_controller): + """Test the building of a command choice map.""" + with patch( + "openbb_cli.controllers.choices._get_argument_parser" + ) as mock_get_parser: + parser = ArgumentParser() + parser.add_argument( + "--option", choices=["opt1", "opt2"], help="A choice option." + ) + mock_get_parser.return_value = parser + + choice_map = build_controller_choice_map(controller=mock_controller) + + assert "test_command" in choice_map + assert "--option" in choice_map["test_command"] + assert "opt1" in choice_map["test_command"]["--option"] + assert "opt2" in choice_map["test_command"]["--option"] diff --git a/cli/tests/test_controllers_cli_controller.py b/cli/tests/test_controllers_cli_controller.py new file mode 100644 index 000000000000..7cb7c6578a92 --- /dev/null +++ b/cli/tests/test_controllers_cli_controller.py @@ -0,0 +1,79 @@ +"""Test the CLI controller.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.cli_controller import ( + CLIController, + handle_job_cmds, + parse_and_split_input, + run_cli, +) + +# pylint: disable=redefined-outer-name, unused-argument + + +def test_parse_and_split_input_custom_filters(): + """Test the parse_and_split_input function with custom filters.""" + input_cmd = "query -q AAPL/P" + result = parse_and_split_input( + input_cmd, custom_filters=[r"((\ -q |\ --question|\ ).*?(/))"] + ) + assert ( + "AAPL/P" not in result + ), "Should filter out terms that look like a sorting parameter" + + +@patch("openbb_cli.controllers.cli_controller.CLIController.print_help") +def test_cli_controller_print_help(mock_print_help): + """Test the CLIController print_help method.""" + controller = CLIController() + controller.print_help() + mock_print_help.assert_called_once() + + +@pytest.mark.parametrize( + "controller_input, expected_output", + [ + ("settings", True), + ("random_command", False), + ], +) +def test_CLIController_has_command(controller_input, expected_output): + """Test the CLIController has_command method.""" + controller = CLIController() + assert hasattr(controller, f"call_{controller_input}") == expected_output + + +def test_handle_job_cmds_with_export_path(): + """Test the handle_job_cmds function with an export path.""" + jobs_cmds = ["export /path/to/export some_command"] + result = handle_job_cmds(jobs_cmds) + expected = "some_command" + assert expected in result[0] # type: ignore + + +@patch("openbb_cli.controllers.cli_controller.CLIController.switch", return_value=[]) +@patch("openbb_cli.controllers.cli_controller.print_goodbye") +def test_run_cli_quit_command(mock_print_goodbye, mock_switch): + """Test the run_cli function with the quit command.""" + run_cli(["quit"], test_mode=True) + mock_print_goodbye.assert_called_once() + + +@pytest.mark.skip("This test is not working as expected") +def test_execute_openbb_routine_with_mocked_requests(): + """Test the call_exe function with mocked requests.""" + with patch("requests.get") as mock_get: + response = MagicMock() + response.status_code = 200 + response.json.return_value = {"script": "print('Hello World')"} + mock_get.return_value = response + # Here we need to call the correct function, assuming it's something like `call_exe` for URL-based scripts + controller = CLIController() + controller.call_exe( + ["--url", "https://my.openbb.co/u/test/routine/test.openbb"] + ) + mock_get.assert_called_with( + "https://my.openbb.co/u/test/routine/test.openbb?raw=true", timeout=10 + ) diff --git a/cli/tests/test_controllers_controller_factory.py b/cli/tests/test_controllers_controller_factory.py new file mode 100644 index 000000000000..8832885b47af --- /dev/null +++ b/cli/tests/test_controllers_controller_factory.py @@ -0,0 +1,61 @@ +"""Test the Controller Factory.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.platform_controller_factory import ( + PlatformControllerFactory, +) + +# pylint: disable=redefined-outer-name, unused-argument + + +@pytest.fixture +def mock_processor(): + """Fixture to mock ArgparseClassProcessor.""" + with patch( + "openbb_cli.controllers.platform_controller_factory.ArgparseClassProcessor" + ) as mock: + instance = mock.return_value + instance.paths = {"settings": "menu"} + instance.translators = {"test_router_settings": MagicMock()} + yield instance + + +@pytest.fixture +def platform_router(): + """Fixture to provide a mock platform_router class.""" + + class MockRouter: + pass + + return MockRouter + + +@pytest.fixture +def factory(platform_router, mock_processor): + """Fixture to create a PlatformControllerFactory with mocked dependencies.""" + return PlatformControllerFactory( + platform_router=platform_router, reference={"test": "ref"} + ) + + +def test_init(mock_processor): + """Test the initialization of the PlatformControllerFactory.""" + factory = PlatformControllerFactory( + platform_router=MagicMock(), reference={"test": "ref"} + ) + assert factory.router_name.lower() == "magicmock" + assert factory.controller_name == "MagicmockController" + + +def test_create_controller(factory): + """Test the creation of a controller class.""" + ControllerClass = factory.create() + + assert "PlatformController" in [base.__name__ for base in ControllerClass.__bases__] + assert ControllerClass.CHOICES_GENERATION + assert "settings" in ControllerClass.CHOICES_MENUS + assert "test_router_settings" not in [ + cmd.replace("test_router_", "") for cmd in ControllerClass.CHOICES_COMMANDS + ] diff --git a/cli/tests/test_controllers_script_parser.py b/cli/tests/test_controllers_script_parser.py new file mode 100644 index 000000000000..4363b0d79fe0 --- /dev/null +++ b/cli/tests/test_controllers_script_parser.py @@ -0,0 +1,106 @@ +"""Test Script parser.""" + +from datetime import datetime, timedelta + +import pytest +from openbb_cli.controllers.script_parser import ( + match_and_return_openbb_keyword_date, + parse_openbb_script, +) + +# pylint: disable=import-outside-toplevel, unused-variable, line-too-long + + +@pytest.mark.parametrize( + "command, expected", + [ + ("reset", True), + ("r", True), + ("r\n", True), + ("restart", False), + ], +) +def test_is_reset(command, expected): + """Test the is_reset function.""" + from openbb_cli.controllers.script_parser import is_reset + + assert is_reset(command) == expected + + +@pytest.mark.parametrize( + "keyword, expected_date", + [ + ( + "$LASTFRIDAY", + ( + datetime.now() + - timedelta(days=((datetime.now().weekday() - 4) % 7 + 7) % 7) + ).strftime("%Y-%m-%d"), + ), + ], +) +def test_match_and_return_openbb_keyword_date(keyword, expected_date): + """Test the match_and_return_openbb_keyword_date function.""" + result = match_and_return_openbb_keyword_date(keyword) + assert result == expected_date + + +def test_parse_openbb_script_basic(): + """Test the parse_openbb_script function.""" + raw_lines = ["echo 'Hello World'"] + error, script = parse_openbb_script(raw_lines) + assert error == "" + assert script == "/echo 'Hello World'" + + +def test_parse_openbb_script_with_variable(): + """Test the parse_openbb_script function.""" + raw_lines = ["$VAR = 2022-01-01", "echo $VAR"] + error, script = parse_openbb_script(raw_lines) + assert error == "" + assert script == "/echo 2022-01-01" + + +def test_parse_openbb_script_with_foreach_loop(): + """Test the parse_openbb_script function.""" + raw_lines = ["foreach $$DATE in 2022-01-01,2022-01-02", "echo $$DATE", "end"] + error, script = parse_openbb_script(raw_lines) + assert error == "" + assert script == "/echo 2022-01-01/echo 2022-01-02" + + +def test_parse_openbb_script_with_error(): + """Test the parse_openbb_script function.""" + raw_lines = ["$VAR = ", "echo $VAR"] + error, script = parse_openbb_script(raw_lines) + assert "Variable $VAR not given" in error + + +@pytest.mark.parametrize( + "line, expected", + [ + ( + "foreach $$VAR in 2022-01-01", + "[red]The script has a foreach loop that doesn't terminate. Add the keyword 'end' to explicitly terminate loop[/red]", # noqa: E501 + ), + ("echo Hello World", ""), + ( + "end", + "[red]The script has a foreach loop that terminates before it gets started. Add the keyword 'foreach' to explicitly start loop[/red]", # noqa: E501 + ), + ], +) +def test_parse_openbb_script_foreach_errors(line, expected): + """Test the parse_openbb_script function.""" + error, script = parse_openbb_script([line]) + assert error == expected + + +def test_date_keyword_last_friday(): + """Test the match_and_return_openbb_keyword_date function.""" + today = datetime.now() + last_friday = today - timedelta(days=(today.weekday() - 4 + 7) % 7) + if last_friday > today: + last_friday -= timedelta(days=7) + expected_date = last_friday.strftime("%Y-%m-%d") + assert match_and_return_openbb_keyword_date("$LASTFRIDAY") == expected_date diff --git a/cli/tests/test_controllers_settings_controller.py b/cli/tests/test_controllers_settings_controller.py new file mode 100644 index 000000000000..439281392057 --- /dev/null +++ b/cli/tests/test_controllers_settings_controller.py @@ -0,0 +1,135 @@ +"""Test the SettingsController class.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.settings_controller import SettingsController + +# pylint: disable=redefined-outer-name, unused-argument + + +@pytest.fixture +def mock_session(): + with patch("openbb_cli.controllers.settings_controller.session") as mock: + + mock.settings.USE_INTERACTIVE_DF = False + mock.settings.ALLOWED_NUMBER_OF_ROWS = 20 + mock.settings.TIMEZONE = "UTC" + + mock.settings.set_item = MagicMock() + + yield mock + + +def test_call_interactive(mock_session): + controller = SettingsController() + controller.call_interactive(None) + mock_session.settings.set_item.assert_called_once_with("USE_INTERACTIVE_DF", True) + + +@pytest.mark.parametrize( + "input_rows, expected", + [ + (10, 10), + (15, 15), + ], +) +def test_call_n_rows(input_rows, expected, mock_session): + controller = SettingsController() + args = ["--rows", str(input_rows)] + controller.call_n_rows(args) + mock_session.settings.set_item.assert_called_with( + "ALLOWED_NUMBER_OF_ROWS", expected + ) + + +def test_call_n_rows_no_args_provided(mock_session): + controller = SettingsController() + controller.call_n_rows([]) + mock_session.console.print.assert_called_with("Current number of rows: 20") + + +@pytest.mark.parametrize( + "timezone, valid", + [ + ("UTC", True), + ("Mars/Phobos", False), + ], +) +def test_call_timezone(timezone, valid, mock_session): + with patch( + "openbb_cli.controllers.settings_controller.is_timezone_valid", + return_value=valid, + ): + controller = SettingsController() + args = ["--timezone", timezone] + controller.call_timezone(args) + if valid: + mock_session.settings.set_item.assert_called_with("TIMEZONE", timezone) + else: + mock_session.settings.set_item.assert_not_called() + + +def test_call_console_style(mock_session): + controller = SettingsController() + args = ["--style", "dark"] + controller.call_console_style(args) + mock_session.console.print.assert_called() + + +def test_call_console_style_no_args(mock_session): + mock_session.settings.RICH_STYLE = "default" + controller = SettingsController() + controller.call_console_style([]) + mock_session.console.print.assert_called_with("Current console style: default") + + +def test_call_flair(mock_session): + controller = SettingsController() + args = ["--flair", "rocket"] + controller.call_flair(args) + + +def test_call_flair_no_args(mock_session): + mock_session.settings.FLAIR = "star" + controller = SettingsController() + controller.call_flair([]) + mock_session.console.print.assert_called_with("Current flair: star") + + +def test_call_obbject_display(mock_session): + controller = SettingsController() + args = ["--number", "5"] + controller.call_obbject_display(args) + mock_session.settings.set_item.assert_called_once_with( + "N_TO_DISPLAY_OBBJECT_REGISTRY", 5 + ) + + +def test_call_obbject_display_no_args(mock_session): + mock_session.settings.N_TO_DISPLAY_OBBJECT_REGISTRY = 10 + controller = SettingsController() + controller.call_obbject_display([]) + mock_session.console.print.assert_called_with( + "Current number of results to display from the OBBject registry: 10" + ) + + +@pytest.mark.parametrize( + "args, expected", + [ + (["--rows", "50"], 50), + (["--rows", "100"], 100), + ([], 20), + ], +) +def test_call_n_rows_v2(args, expected, mock_session): + mock_session.settings.ALLOWED_NUMBER_OF_ROWS = 20 + controller = SettingsController() + controller.call_n_rows(args) + if args: + mock_session.settings.set_item.assert_called_with( + "ALLOWED_NUMBER_OF_ROWS", expected + ) + else: + mock_session.console.print.assert_called_with("Current number of rows: 20") diff --git a/cli/tests/test_controllers_utils.py b/cli/tests/test_controllers_utils.py new file mode 100644 index 000000000000..84c7548c9df8 --- /dev/null +++ b/cli/tests/test_controllers_utils.py @@ -0,0 +1,157 @@ +"""Test the Controller utils.""" + +import argparse +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.utils import ( + check_non_negative, + check_positive, + get_flair_and_username, + get_user_agent, + parse_and_split_input, + print_goodbye, + print_guest_block_msg, + remove_file, + reset, + welcome_message, +) + +# pylint: disable=redefined-outer-name, unused-argument + + +@pytest.fixture +def mock_session(): + """Mock the session and its dependencies.""" + with patch("openbb_cli.controllers.utils.Session", autospec=True) as mock: + mock.return_value.console.print = MagicMock() + mock.return_value.is_local = MagicMock(return_value=True) + mock.return_value.settings.VERSION = "1.0" + mock.return_value.user.profile.hub_session.username = "testuser" + mock.return_value.settings.FLAIR = "rocket" + yield mock + + +def test_remove_file_existing_file(): + """Test removing an existing file.""" + with patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove: + assert remove_file(Path("/path/to/file")) + mock_remove.assert_called_once() + + +def test_remove_file_directory(): + """Test removing a directory.""" + with patch("os.path.isfile", return_value=False), patch( + "os.path.isdir", return_value=True + ), patch("shutil.rmtree") as mock_rmtree: + assert remove_file(Path("/path/to/directory")) + mock_rmtree.assert_called_once() + + +def test_remove_file_failure(mock_session): + """Test removing a file that fails.""" + with patch("os.path.isfile", return_value=True), patch( + "os.remove", side_effect=Exception("Error") + ): + assert not remove_file(Path("/path/to/file")) + mock_session.return_value.console.print.assert_called() + + +def test_print_goodbye(mock_session): + """Test printing the goodbye message.""" + print_goodbye() + mock_session.return_value.console.print.assert_called() + + +def test_reset(mock_session): + """Test resetting the CLI.""" + with patch("openbb_cli.controllers.utils.remove_file"), patch( + "sys.modules", new_callable=dict + ): + reset() + mock_session.return_value.console.print.assert_called() + + +def test_parse_and_split_input(): + """Test parsing and splitting user input.""" + user_input = "ls -f /home/user/docs/document.xlsx" + result = parse_and_split_input(user_input, []) + assert "ls" in result[0] + + +@pytest.mark.parametrize( + "input_command, expected_output", + [ + ("/", ["home"]), + ("ls -f /path/to/file.txt", ["ls -f ", "path", "to", "file.txt"]), + ("rm -f /home/user/docs", ["rm -f ", "home", "user", "docs"]), + ], +) +def test_parse_and_split_input_special_cases(input_command, expected_output): + """Test parsing and splitting user input with special cases.""" + result = parse_and_split_input(input_command, []) + assert result == expected_output + + +def test_print_guest_block_msg(mock_session): + """Test printing the guest block message.""" + print_guest_block_msg() + mock_session.return_value.console.print.assert_called() + + +def test_welcome_message(mock_session): + """Test printing the welcome message.""" + welcome_message() + mock_session.return_value.console.print.assert_called_with( + "\nWelcome to OpenBB Platform CLI v1.0" + ) + + +def test_get_flair_and_username(mock_session): + """Test getting the flair and username.""" + result = get_flair_and_username() + assert "testuser" in result + assert "rocket" in result # + + +@pytest.mark.parametrize( + "value, expected", + [ + ("10", 10), + ("0", 0), + ("-1", pytest.raises(argparse.ArgumentTypeError)), + ("text", pytest.raises(ValueError)), + ], +) +def test_check_non_negative(value, expected): + """Test checking for a non-negative value.""" + if isinstance(expected, int): + assert check_non_negative(value) == expected + else: + with expected: + check_non_negative(value) + + +@pytest.mark.parametrize( + "value, expected", + [ + ("1", 1), + ("0", pytest.raises(argparse.ArgumentTypeError)), + ("-1", pytest.raises(argparse.ArgumentTypeError)), + ("text", pytest.raises(ValueError)), + ], +) +def test_check_positive(value, expected): + """Test checking for a positive value.""" + if isinstance(expected, int): + assert check_positive(value) == expected + else: + with expected: + check_positive(value) + + +def test_get_user_agent(): + """Test getting the user agent.""" + result = get_user_agent() + assert result.startswith("Mozilla/5.0") diff --git a/cli/tests/test_models_settings.py b/cli/tests/test_models_settings.py new file mode 100644 index 000000000000..0834355516f4 --- /dev/null +++ b/cli/tests/test_models_settings.py @@ -0,0 +1,72 @@ +"""Test the Models Settings module.""" + +from unittest.mock import mock_open, patch + +from openbb_cli.models.settings import Settings + +# pylint: disable=unused-argument + + +def test_default_values(): + """Test the default values of the settings model.""" + settings = Settings() + assert settings.VERSION == "1.0.0" + assert settings.TEST_MODE is False + assert settings.DEBUG_MODE is False + assert settings.DEV_BACKEND is False + assert settings.FILE_OVERWRITE is False + assert settings.SHOW_VERSION is True + assert settings.USE_INTERACTIVE_DF is True + assert settings.USE_CLEAR_AFTER_CMD is False + assert settings.USE_DATETIME is True + assert settings.USE_PROMPT_TOOLKIT is True + assert settings.ENABLE_EXIT_AUTO_HELP is True + assert settings.REMEMBER_CONTEXTS is True + assert settings.ENABLE_RICH_PANEL is True + assert settings.TOOLBAR_HINT is True + assert settings.SHOW_MSG_OBBJECT_REGISTRY is False + assert settings.TIMEZONE == "America/New_York" + assert settings.FLAIR == ":openbb" + assert settings.PREVIOUS_USE is False + assert settings.N_TO_KEEP_OBBJECT_REGISTRY == 10 + assert settings.N_TO_DISPLAY_OBBJECT_REGISTRY == 5 + assert settings.RICH_STYLE == "dark" + assert settings.ALLOWED_NUMBER_OF_ROWS == 20 + assert settings.ALLOWED_NUMBER_OF_COLUMNS == 5 + assert settings.HUB_URL == "https://my.openbb.co" + assert settings.BASE_URL == "https://payments.openbb.co" + + +# Test __repr__ output +def test_repr(): + """Test the __repr__ method of the settings model.""" + settings = Settings() + repr_str = settings.__repr__() # pylint: disable=C2801 + assert "Settings\n\n" in repr_str + assert "VERSION: 1.0.0" in repr_str + + +# Test loading from environment variables +@patch( + "openbb_cli.models.settings.dotenv_values", + return_value={"OPENBB_TEST_MODE": "True", "OPENBB_VERSION": "2.0.0"}, +) +def test_from_env(mock_dotenv_values): + """Test loading settings from environment variables.""" + settings = Settings.from_env({}) # type: ignore + assert settings["TEST_MODE"] == "True" + assert settings["VERSION"] == "2.0.0" + + +# Test setting an item and updating .env +@patch("openbb_cli.models.settings.set_key") +@patch( + "openbb_cli.models.settings.open", + new_callable=mock_open, + read_data="TEST_MODE=False\n", +) +def test_set_item(mock_file, mock_set_key): + """Test setting an item and updating the .env file.""" + settings = Settings() + settings.set_item("TEST_MODE", True) + assert settings.TEST_MODE is True diff --git a/cli/tests/test_session.py b/cli/tests/test_session.py new file mode 100644 index 000000000000..66ca9fccab8b --- /dev/null +++ b/cli/tests/test_session.py @@ -0,0 +1,44 @@ +"Test the Session class." +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.models.settings import Settings +from openbb_cli.session import Session, sys + +# pylint: disable=redefined-outer-name, unused-argument, protected-access + + +def mock_isatty(return_value): + """Mock the isatty method.""" + original_isatty = sys.stdin.isatty + sys.stdin.isatty = MagicMock(return_value=return_value) # type: ignore + return original_isatty + + +@pytest.fixture +def session(): + """Session fixture.""" + return Session() + + +def test_session_initialization(session): + """Test the initialization of the Session class.""" + assert session.settings is not None + assert session.style is not None + assert session.console is not None + assert session.obbject_registry is not None + assert isinstance(session.settings, Settings) + + +@patch("sys.stdin.isatty", return_value=True) +def test_get_prompt_session_true(mock_isatty, session): + "Test get_prompt_session method." + prompt_session = session._get_prompt_session() + assert prompt_session is not None + + +@patch("sys.stdin.isatty", return_value=False) +def test_get_prompt_session_false(mock_isatty, session): + "Test get_prompt_session method." + prompt_session = session._get_prompt_session() + assert prompt_session is None diff --git a/openbb_platform/dev_install.py b/openbb_platform/dev_install.py index 391aa08fa280..0b8695366fd0 100644 --- a/openbb_platform/dev_install.py +++ b/openbb_platform/dev_install.py @@ -129,7 +129,7 @@ def install_local(_extras: bool = False): print("Restoring pyproject.toml and poetry.lock") # noqa: T201 finally: - # Revert pyproject.toml and poetry.lock to their original state + # Revert pyproject.toml and poetry.lock to their original state. with open(PYPROJECT, "w", encoding="utf-8", newline="\n") as f: f.write(original_pyproject) From 8d060dc0717e2ce9cde06ffc7523ea465c920a0e Mon Sep 17 00:00:00 2001 From: Henrique Joaquim Date: Tue, 14 May 2024 16:44:07 +0100 Subject: [PATCH 2/3] [Feature] CLI docs (#6362) * cli docs website * changes * remove unused feat flags * typo * yeet stuff that can't be used * some progress * Add home screenshot * screenshots.md * fix links * new cards for cli pages * start config page * more updates * Add screenshots * flatten some things. * fix link * some more updates * some routine stuff * codespell * cli color * results in the global commands * increase codeBlock line-height * remove platform warning, obb is a class * minor change, danger warning * typo? * Data processing commands --------- Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com> Co-authored-by: Diogo Sousa --- .../controllers/settings_controller.py | 3 - website/content/cli/_category_.json | 4 + website/content/cli/commands-and-arguments.md | 76 ++++++ website/content/cli/configuration.md | 66 +++++ website/content/cli/data-sources.md | 113 ++++++++ website/content/cli/hub.md | 111 ++++++++ website/content/cli/index.md | 78 ++++++ website/content/cli/installation.md | 76 ++++++ website/content/cli/interactive-charts.md | 109 ++++++++ website/content/cli/interactive-tables.md | 75 ++++++ website/content/cli/openbbuserdata.md | 59 +++++ website/content/cli/quickstart.md | 243 ++++++++++++++++++ website/content/cli/routines/_category_.json | 4 + .../content/cli/routines/advanced-routines.md | 119 +++++++++ .../cli/routines/community-routines.md | 52 ++++ website/content/cli/routines/index.mdx | 31 +++ .../cli/routines/introduction-to-routines.md | 130 ++++++++++ .../cli/routines/routine-macro-recorder.md | 46 ++++ .../content/cli/structure-and-navigation.md | 42 +++ website/content/platform/installation.md | 9 - website/package-lock.json | 7 +- website/sidebars.js | 5 + .../components/General/NewReferenceCard.tsx | 5 +- .../theme/CodeBlock/Content/styles.module.css | 1 + .../theme/DocSidebarItem/Category/index.js | 1 + website/src/theme/Navbar/Layout/index.js | 12 + 26 files changed, 1458 insertions(+), 19 deletions(-) create mode 100644 website/content/cli/_category_.json create mode 100644 website/content/cli/commands-and-arguments.md create mode 100644 website/content/cli/configuration.md create mode 100644 website/content/cli/data-sources.md create mode 100644 website/content/cli/hub.md create mode 100644 website/content/cli/index.md create mode 100644 website/content/cli/installation.md create mode 100644 website/content/cli/interactive-charts.md create mode 100644 website/content/cli/interactive-tables.md create mode 100644 website/content/cli/openbbuserdata.md create mode 100644 website/content/cli/quickstart.md create mode 100644 website/content/cli/routines/_category_.json create mode 100644 website/content/cli/routines/advanced-routines.md create mode 100644 website/content/cli/routines/community-routines.md create mode 100644 website/content/cli/routines/index.mdx create mode 100644 website/content/cli/routines/introduction-to-routines.md create mode 100644 website/content/cli/routines/routine-macro-recorder.md create mode 100644 website/content/cli/structure-and-navigation.md diff --git a/cli/openbb_cli/controllers/settings_controller.py b/cli/openbb_cli/controllers/settings_controller.py index 8783ef47e1b7..f93501a5063f 100644 --- a/cli/openbb_cli/controllers/settings_controller.py +++ b/cli/openbb_cli/controllers/settings_controller.py @@ -21,10 +21,7 @@ class SettingsController(BaseController): CHOICES_COMMANDS: List[str] = [ "interactive", "cls", - "watermark", "promptkit", - "thoughts", - "reporthtml", "exithelp", "rcontext", "richpanel", diff --git a/website/content/cli/_category_.json b/website/content/cli/_category_.json new file mode 100644 index 000000000000..a68e9f9ff3b4 --- /dev/null +++ b/website/content/cli/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "OpenBB CLI", + "position": 2 +} diff --git a/website/content/cli/commands-and-arguments.md b/website/content/cli/commands-and-arguments.md new file mode 100644 index 000000000000..709b73f3bc95 --- /dev/null +++ b/website/content/cli/commands-and-arguments.md @@ -0,0 +1,76 @@ +--- +title: Commands And Arguments +sidebar_position: 4 +description: This page explains how to enter commands and arguments into the OpenBB CLI. +keywords: +- help arguments +- auto-complete +- global commands +- support command +- reset command +- command line interface +- metadata +- cli +- parameters +- functions +- commands +- options +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +Commands are displayed on-screen in a lighter colour, compared with menu items, and they will not be prefaced with, `>`. + +Functions have a variety of parameters that differ by endpoint and provider. The --help dialogue will provide guidance on nuances of any particular command. + +The available data sources for each command are displayed on the right side of the screen, and are selected with the `--provider` argument. + +## Help arguments + +The `help` command shows the current menu. The screen will display all the commands and menus that exist, including a short description for each of these. + +Adding `--help`, or `-h`, to any command will display the description and parameters for that particular function. + +## Entering Parameters + +Parameters are all defined through the same pattern, --argument, followed by a space, and then the value. + +If the parameter is a boolean (true/false), there is no value to enter. Adding the --argument flags the parameter to be the opposite of its default state. + +Use the auto-complete to prompt choices and reduce the amount of keystrokes required to run a complex function. + +## Auto-complete + +The OpenBB CLI is equipped with an auto completion engine that presents choices based on the current menu and command. Whenever you start typing, suggestion prompts will appear for existing commands and menus. When the command contains arguments, pressing the `space bar` after typing the command will present the list of available arguments. + +This functionality dramatically reduces the number of key strokes required to perform tasks and, in many cases, eliminates the need to consult the help dialogue for reminders. Choices - where they are bound by a defined list - are scrollable with the up and down arrow keys. + +## Global commands + +These are commands that can be used throughout the CLI and will work regardless of the menu where they belong. + +### Help + +`--help`, or `-h` can be attached to any command, as described above. + +### CLS + +The `cls` command clears the entire CLI screen. + +### Quit + +The `quit` command (can also use `q` or `..`) allows to leave the current menu to go one menu above. If the user is on the root, that will mean leaving the CLI. + +### Exit + +The `exit` command allows the user to exit the CLI. + +### Reset + +The `reset` command will reset the CLI to its default state. This is specially useful for development so you can refresh the CLI without having to close and open it again. + +### Results + +The `results` command will display the stack of `OBBjects` that have been generated during the session, which can be later injected on the data processing commands. diff --git a/website/content/cli/configuration.md b/website/content/cli/configuration.md new file mode 100644 index 000000000000..360aebae253f --- /dev/null +++ b/website/content/cli/configuration.md @@ -0,0 +1,66 @@ +--- +title: Configuration & Settings +sidebar_position: 5 +description: This documentation page details the various settings and feature flags used to customize the OpenBB CLI. +keywords: +- Settings Menu +- Feature Flags Menu +- customize CLI +- alter CLI behaviour +- environment variables +- Documentation +- OpenBB Platform CLI +- preferences +- user +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +In addition to the OpenBB Platform's `user_settings.json` file, described [here](/platform/usage/settings_and_environment_variables), there are settings and environment variables affecting the CLI only. + +:::important +API credentials are defined in the `user_settings.json` file. + +Find all data providers [here](/platform/extensions/data_extensions), and manage all your credentials directly on the [OpenBB Hub](https://my.openbb.co/app/platform/credentials). + +Define default data sources by following the pattern outlined [here](data-sources) +::: + +## Settings Menu + +The `/settings` menu provides methods for customizing the look and feel of the CLI. The menu is divided into two sections: + +- Feature Flags + - On/Off status is reflected by red/green text. + - Status is toggled by entering the item as a command. +- Preferences + - Choices and options will be presented as a typical function. + +### Feature Flags + +| Feature Flags | Description | +| :----------- | :--------------------------------------------------------------- | +| `interactive` | Enable/disable interactive tables. Disabling prints the table directly on the CLI screen. | +| `cls` | Clear the screen after each command. Default state is off. | +| `promptkit` | Enable auto complete and history. Default state is on. | +| `richpanel` | Displays a border around menus. Default state is on. | +| `tbhint` | Display usage hints in the bottom toolbar. Default state is on. | +| `exithelp` | Automatically print the screen after navigating back one menu. Default state is off. | +| `overwrite` | Automatically overwrite exported files with the same name. Default state is off. | +| `obbject_msg` | Displays a message whenever a new result is added to the registry. Default state is off. | +| `version` | Displays the currently installed version number in the bottom right corner. | + +### Preferences + +| Preferences | Description | +| :----------- | :--------------------------------------------------------------- | +| `console_style` | apply a custom rich style to the CLI | +| `flair` | choose flair icon | +| `timezone` | pick timezone | +| `language` | translation language | +| `n_rows` | number of rows to show on non interactive tables | +| `n_cols` | number of columns to show on non interactive tables | +| `obbject_res` | define the maximum number of obbjects allowed in the registry | +| `obbject_display` | define the maximum number of cached results to display on the help menu | diff --git a/website/content/cli/data-sources.md b/website/content/cli/data-sources.md new file mode 100644 index 000000000000..f6bd3a0d7626 --- /dev/null +++ b/website/content/cli/data-sources.md @@ -0,0 +1,113 @@ +--- +title: Data Sources +sidebar_position: 7 +description: This page explains how to select a provider for any specific command, and set a default source for a route. +keywords: +- Terminal +- CLI +- provider +- API keys +- FinancialModelingPrep +- Polygon +- AlphaVantage +- Intrinio +- YahooFinance +- source +- data +- default +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +Many commands have multiple data sources associated with it. This page describes how to select from multiple providers. + +:::important +API credentials are defined in the `user_settings.json` file. + +Find all data providers [here](/platform/extensions/data_extensions), and manage all your credentials directly on the [OpenBB Hub](https://my.openbb.co/app/platform/credentials). +::: + +## Data Source In-Command + +To specify the data vendor for that particular command, use the `--provider` argument. + +Parameter choices can be viewed from the help dialogue, `-h` or `--help`. + +```console +/equity/price/historical -h +``` + +```console +usage: historical --symbol SYMBOL [SYMBOL ...] [--interval INTERVAL] [--start_date START_DATE] [--end_date END_DATE] [--chart] + [--provider {fmp,intrinio,polygon,tiingo,yfinance}] [--start_time START_TIME] [--end_time END_TIME] [--timezone TIMEZONE] + [--source {realtime,delayed,nasdaq_basic}] [--sort {asc,desc}] [--limit LIMIT] [--extended_hours] [--include_actions] + [--adjustment {splits_and_dividends,unadjusted,splits_only}] [--adjusted] [--prepost] [-h] [--export EXPORT] + [--sheet-name SHEET_NAME [SHEET_NAME ...]] + +Get historical price data for a given stock. This includes open, high, low, close, and volume. + +options: + --interval INTERVAL Time interval of the data to return. + --start_date START_DATE + Start date of the data, in YYYY-MM-DD format. + --end_date END_DATE End date of the data, in YYYY-MM-DD format. + --chart Whether to create a chart or not, by default False. + --provider {fmp,intrinio,polygon,tiingo,yfinance} + The provider to use for the query, by default None. + If None, the provider specified in defaults is selected or 'fmp' if there is + no default. + --extended_hours Include Pre and Post market data. (provider: polygon, yfinance) + --adjustment {splits_and_dividends,unadjusted,splits_only} + The adjustment factor to apply. Default is splits only. (provider: polygon, yfinance) + -h, --help show this help message + --export EXPORT Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg + --sheet-name SHEET_NAME [SHEET_NAME ...] + Name of excel sheet to save data to. Only valid for .xlsx files. + +required arguments: + --symbol SYMBOL [SYMBOL ...] + Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, polygon, tiingo, yfinance. + +intrinio: + --start_time START_TIME + Return intervals starting at the specified time on the `start_date` formatted as 'HH:MM:SS'. + --end_time END_TIME Return intervals stopping at the specified time on the `end_date` formatted as 'HH:MM:SS'. + --timezone TIMEZONE Timezone of the data, in the IANA format (Continent/City). + --source {realtime,delayed,nasdaq_basic} + The source of the data. + +polygon: + --sort {asc,desc} Sort order of the data. This impacts the results in combination with the 'limit' parameter. The results are always returned in ascending order by date. + --limit LIMIT The number of data entries to return. + +yfinance: + --include_actions Include dividends and stock splits in results. +``` + +:::note +Provider-specific parameters are listed at the bottom of the print out. They are ignored when entered, if it is not supported by the selected provider. +::: + +## Setting The Default Source + +The default data source for each command (where multiple sources are available) can be defined within the user configuration file: `/home/your-user/.openbb_platform/user_settings.json`. + +Set the default data provider for the `/equity/price/historical` command by adding the following line to your `user_settings.json` file: + +```json +{ + "defaults": { + "routes": { + "/equity/price/historical": { + "provider": "fmp" + }, + "/equity/fundamental/balance": { + "provider": "polygon" + }, + ... + } + } +} +``` diff --git a/website/content/cli/hub.md b/website/content/cli/hub.md new file mode 100644 index 000000000000..d7e77fd9e63c --- /dev/null +++ b/website/content/cli/hub.md @@ -0,0 +1,111 @@ +--- +title: Hub Synchronization +sidebar_position: 6 +description: This page outlines the `/account` menu within the OpenBB CLI, and integrations with the OpenBB Hub. +keywords: +- OpenBB Platform CLI +- OpenBB Hub +- Registration +- Login process +- API Keys management +- Theme +- Style +- Dark +- Light +- Script Routines +- Personal Access Tokens +- PAT +- Credentials +- Customization +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +This page outlines the `/account` menu within the OpenBB CLI and integrations with the OpenBB Hub. + +## Registration + +To get started, you'll need to create an account on the OpenBB Hub by visiting [https://my.openbb.co](https://my.openbb.co) + +By registering with the OpenBB Hub, you can easily access our products on multiple devices and maintain consistent settings for an improved user experience. + +## Login + +Once you're successfully registered on the OpenBB Hub, you can log in to access all the benefits it has to offer. + +:::tip +OpenBB recommends logging in via the Personal Access Token (PAT) method. This revokable token allows users to login without transmitting any personally identifiable information, like an email address, which makes it an ideal solution for shared machines and public network connections. +::: + +To login, enter the `/account` menu and then use the `login` command with your choice of login method. + +### PAT + +```console +/account +login --pat REPLACE_WITH_PAT +``` + +### Email & Password + +```console +/account +login --email my@emailaddress.com --password totallysecurepassword +``` + +## API Keys + +The OpenBB Platform acts as a mediator between users and data providers. + +With an OpenBB Hub account, you can manage your API keys on [this page](https://my.openbb.co/app/platform/credentials). + +Upon logging in, the CLI will automatically retrieve the API keys associated with your account. + +If you have not saved them on the OpenBB Hub, they will be loaded from your local environment by default. + +:::danger +If an API key is saved on the OpenBB Hub, it will take precedence over the local environment key. +::: + +The CLI will need to be restarted, or refreshed, when changes are made on the Hub. + +## Theme Styles + +Theme styles correspond to the ability to change the terminal "skin" (i.e. coloring of the `menu`, `commands`, `data source`, `parameters`, `information` and `help`), as well as the chart and table styles. + +In the OpenBB Hub, you can select the text colours for the CLI. After customizing: +- Download the theme to your styles directory (`/home/your-user/OpenBBUserData/styles/user`). +- Apply it by selecting the style from the `/settings` menu. + +```console +/settings +console_style -s openbb_config +``` + +Replace `openbb_config` with the name of the downloaded (JSON) file. + +## Script Routines + +The OpenBB Hub allows users to create, edit, manage, and share their script routines that can be run in the OpenBB Platform CLI. + +The "Download" button will save the file locally. Add it to `/home/your-user/OpenBBUserData/routines`, for the script to populate as a choice for the `exe` command on next launch. + +## Refresh + +The `refresh` command will update any changes without the need to logout and login. + +```console +/account +refresh +``` + +## Logout + +Logging out will restore any local credentials and preferences defined in the `user_settings.json` file. + +```console +/account +logout +``` diff --git a/website/content/cli/index.md b/website/content/cli/index.md new file mode 100644 index 000000000000..8097697a0884 --- /dev/null +++ b/website/content/cli/index.md @@ -0,0 +1,78 @@ +--- +title: Introduction +sidebar_position: 0 +description: The OpenBB CLI is a command line interface wrapping the OpenBB Platform. It offers a convenient way to interact with the Platform and its extensions, as well as automate data collection via OpenBB Routine Scripts. No experience with Python, or other programming languages, is required. +keywords: +- OpenBB +- CLI +- Platform +- data connectors +- data access +- data processing +- third-party data providers +- introduction +--- + + + +import NewReferenceCard from "@site/src/components/General/NewReferenceCard"; +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +## Overview + +The OpenBB CLI is a command line interface wrapping the OpenBB Platform. It offers a convenient way to interact with the Platform and its extensions, as well as automate data collection via OpenBB Routine Scripts. + +The CLI is the next iteration of the [OpenBB Terminal](/terminal), and leverages the extendability of the OpenBB Platform architecture in an easy-to-consume and script format. + +![CLI Home](https://github.com/OpenBB-finance/OpenBBTerminal/assets/85772166/d1617c3b-c83d-4491-a7bc-986321fd7230) + +## Guides & Documentation + +
    + + + + + + + + +
+ +--- + +Want to contribute? Check out our [Development section](/platform/development). diff --git a/website/content/cli/installation.md b/website/content/cli/installation.md new file mode 100644 index 000000000000..51c1819538ab --- /dev/null +++ b/website/content/cli/installation.md @@ -0,0 +1,76 @@ +--- +title: Installation +sidebar_position: 2 +description: This page provides installation instructions for the OpenBB CLI. +keywords: +- OpenBB Platform +- Python +- CLI +- installation +- pip +- pypi + +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +## Pre-Requisites + +The OpenBB CLI is a wrapper around the [Platform](/platform), and should be installed along side an existing OpenBB installation. + +- A Python virtual environment with a version between 3.9 and 3.11, inclusive, is required. + +Please refer to the [OpenBB Platform install documentation](/platform/installation) for instructions and more information. + +:::info +If the OpenBB Platform is not already installed, the `openbb-cli` package will install the default components. +::: + +## PyPI + +Within your existing OpenBB environment, install `openbb-cli` with: + +```console +pip install openbb-cli +``` + + +The installation script adds `openbb` to the PATH within your Python environment. The application can be launched from any path, as long as the environment is active. + +```console +openbb + +Welcome to OpenBB Platform CLI v1.0.0 +``` + +## Source + +Follow the instructions [here](/platform/installation#source) to clone the GitHub repo and install the OpenBB Platform from the source code. + +Next, navigate into the folder: `~/OpenBBTerminal/cli` + +:::tip +The Python environment should have `toml` and `poetry` installed. + +```bash +pip install toml poetry +``` +::: + +Finally, enter: + +```console +poetry install +``` + +Alternatively, install locally with `pip`: + +```bash +pip install -e . +``` + +## Installing New Modules + +New extensions, or removals, are automatically added (removed) to the CLI on the next launch. diff --git a/website/content/cli/interactive-charts.md b/website/content/cli/interactive-charts.md new file mode 100644 index 000000000000..efd7438808e1 --- /dev/null +++ b/website/content/cli/interactive-charts.md @@ -0,0 +1,109 @@ +--- +title: Interactive Charts +sidebar_position: 9 +description: This page provides a detailed explanation of the OpenBB Interactive Charts. Understand various capabilities including annotation, color modification, drawing tools, data export, and supplementary data overlay. +keywords: +- interactive charts +- PyWry +- annotation +- drawing +- lines +- modebar +- plotly +- data export +- data overlay +- editing chart title +- Toolbar +- Text Tools +- Draw Tools +- Export Tools +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +Interactive charts open in a separate window ([PyWry](https://github.com/OpenBB-finance/pywry)). The OpenBB charting library provides interactive and highly customizable charts. + +:::tip +Not all commands have a charting output, the ones that do, will display a chart argument (`--chart`), which will trigger the charting output instead of the default table output. + +Example: `equity/price/historical --symbol AAPL --chart` +::: + +
+Charting cheat sheet + +![Group 653](https://user-images.githubusercontent.com/85772166/234313541-3d725e1c-ce48-4413-9267-b03571e0eccd.png) + +
+ +## Toolbar + +![Chart Tools](https://user-images.githubusercontent.com/85772166/233247997-55c03cbd-9ca9-4f5e-b3fb-3e5a9c63b6eb.png) + +The toolbar is located at the bottom of the window, and provides methods for: + +- Panning and zooming. +- Modifying the title and axis labels. +- Adjusting the hover read out. +- Toggling light/dark mode. +- Annotating and drawing. +- Exporting raw data. +- Saving the chart as an image. +- Adding supplementary external data as an overlay. + +The label for each tool is displayed by holding the mouse over it. + +The toolbar's visibility can be toggled utilizing the `ctrl + h` shortcut. + +## Text Tools + +Annotate a chart by clicking on the `Add Text` button, or with the keyboard, `ctrl + t`. + +![Annotate Charts](https://user-images.githubusercontent.com/85772166/233248056-d459d7a0-ba2d-4533-896a-79406ded859e.png) + +Enter some text, make any adjustments to the options, then `submit`. Place the crosshairs over the desired data point and click to place the text. + +![Place Text](https://user-images.githubusercontent.com/85772166/233728645-74734241-4da2-4cff-af17-b68a62e95113.png) + +After placement, the text can be updated or deleted by clicking on it again. + +![Delete Annotation](https://user-images.githubusercontent.com/85772166/233728428-55d2a8e5-a68a-4cd1-9dbf-4c1cd697187e.png) + +## Change Titles + +The title of the chart is edited by clicking the button, `Change Titles`, near the middle center of the toolbar, immediately to the right of the `Add Text` button. + +## Draw Tools + +![Edit Colors](https://user-images.githubusercontent.com/85772166/233729318-8af947fa-ce2a-43e2-85ab-657e583ac8b1.png) + +The fourth group of icons on the toolbar are for drawing lines and shapes. + +- Edit the colors. +- Draw a straight line. +- Draw a freeform line. +- Draw a circle. +- Draw a rectangle. +- Erase a shape. + +To draw on the chart, select one of the four drawing buttons and drag the mouse over the desired area. Click on any existing shape to modify it by dragging with the mouse and editing the color, or remove it by clicking the toolbar button, `Erase Active Shape`. The edit colors button will pop up as a floating icon, and clicking on that will display the color palette. + +## Export Tools + +The two buttons at the far-right of the toolbar are for saving the raw data or, to save an image file of the chart at the current panned and zoomed view. + +![Export Tools](https://user-images.githubusercontent.com/85772166/233248436-08a2a463-403b-4b1b-b7d8-80cd5af7bee3.png) + +## Overlay + +The button, `Overlay chart from CSV`, provides an easy import method for supplementing a chart with additional data. Clicking on the button opens a pop-up dialogue to select the file, column, and whether the overlay should be a bar, candlestick, or line chart. As a candlestick, the CSV file must contain OHLC data. The import window can also be opened with the keyboard, `ctrl-o`. + +![Overlay CSV](https://user-images.githubusercontent.com/85772166/233248522-16b539f4-b0ae-4c30-8c72-dfa59d0c0cfb.png) + +After choosing the file to overlay, select what to show and then click on `Submit`. + +![Overlay Options](https://user-images.githubusercontent.com/85772166/233250634-44864da0-0936-4d3c-8de2-c8374d26c1d2.png) + +![Overlay Chart](https://user-images.githubusercontent.com/85772166/233248639-6d12b16d-471f-4550-a8ab-8d8c18eeabb3.png) diff --git a/website/content/cli/interactive-tables.md b/website/content/cli/interactive-tables.md new file mode 100644 index 000000000000..1399576b3606 --- /dev/null +++ b/website/content/cli/interactive-tables.md @@ -0,0 +1,75 @@ +--- +title: Interactive Tables +sidebar_position: 10 +description: This page explains how to navigate and utilize OpenBB's interactive tables. Understand how to sort and filter columns, hide or remove columns, select number of rows per page, freeze index and column headers, and export the data. +keywords: +- interactive tables +- PyWry technology +- sorting columns +- filtering columns +- hiding columns +- rows per page +- freeze index +- freeze column headers +- exporting data +- data visualization +- customizing tables +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + + +Interactive tables open in a separate window ([PyWry](https://github.com/OpenBB-finance/pywry)). These provide methods for searching, sorting, filtering, exporting and even adapting settings directly on the table. + +:::tip +All OpenBB CLI results are displayed in interactive tables by default, unless the interactive model is disabled from the `/settings` menu. +::: + +
+Table cheat sheet + +![Chart Intro (5)](https://user-images.githubusercontent.com/85772166/234315026-de098953-111b-4b69-9124-31530c01407a.png) + +
+ +## Sorting + +Columns can be sorted ascending/descending/unsorted, by clicking the controls to the right of each header title. The status of the filtering is shown as a blue indicator. + +![Sort Columns](https://user-images.githubusercontent.com/85772166/233248754-20c18390-a7af-490c-9571-876447b1b0ae.png) + +## Filtering + +The settings button, at the lower-left corner, displays choices for customizing the table. By selecting the `Type` to be `Advanced`, columns become filterable. + +![Table Settings](https://user-images.githubusercontent.com/85772166/233248876-0d788ff4-974d-4d92-8186-56864469870a.png) + +The columns can be filtered with min/max values or by letters, depending on the content of each column. + +![Filtered Tables](https://user-images.githubusercontent.com/85772166/233248923-45873bf1-de6b-40f8-a4aa-05e7c3d21ab0.png) + +## Hiding columns + +The table will scroll to the right as far as there are columns. Columns can be removed from the table by clicking the icon to the right of the settings button and unchecking it from the list. + +![Select Columns](https://user-images.githubusercontent.com/85772166/233248976-849791a6-c126-437c-bb54-454ba6ea4fa2.png) + +## Select rows per page + +The number of rows per page is defined in the drop down selection near the center, at the bottom. + +![Rows per Page](https://user-images.githubusercontent.com/85772166/233249018-8269896d-72f7-4e72-a4d4-2715d1f11b96.png) + +## Freeze the Index and Column Headers + +Right-click on the index name to enable/disable freezing when scrolling to the right. Column headers are frozen by default. + +![Index Freeze](https://user-images.githubusercontent.com/85772166/234103702-0965dfbd-24ca-4a66-8c76-9fac28abcff8.png) + +## Exporting Data + +At the bottom-right corner of the table window, there is a button for exporting the data. To the left, the drop down selection for `Type` can be defined as a CSV, XLSX, or PNG file. Exporting the table as a PNG file will create a screenshot of the table at its current view, and data that is not visible will not be captured. + +![Export Data](https://user-images.githubusercontent.com/85772166/233249065-60728dd1-612e-4684-b196-892f3604c0f4.png) diff --git a/website/content/cli/openbbuserdata.md b/website/content/cli/openbbuserdata.md new file mode 100644 index 000000000000..036731b19365 --- /dev/null +++ b/website/content/cli/openbbuserdata.md @@ -0,0 +1,59 @@ +--- +title: OpenBBUserData Folder +sidebar_position: 8 +description: The OpenBBUserData folder is where exports, routines, and other user-related content is saved and stored. Its default location is the home of the system user account. +keywords: +- OpenBBUserData folder +- settings +- data +- preferences +- exports +- CLI +- save +- routines +- xlsx +- csv +- user_settings.json +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +The OpenBBUserData folder is where exports, routines, and other user-related content is saved and stored. + +:::info +If a new file is placed in the folder (like a Routine) the CLI will need to be reset before auto complete will recognize it. +::: + +## Default Location + +Its default location is the home of the system user account, similar to the following paths: +- macOS: `Macintosh HD/Users//OpenBBUserData` +- Windows: `C:/Users//OpenBBUserData` + +This folder contains all things user-created. For example: + +- Exported files +- Styles and themes +- Routines +- Logs + +:::note +**Note:** With a WSL-enabled Windows installation, this folder will be under the Linux partition +::: + +## Update Folder Location + +The location of this folder can be set by the user by changing the user configuration file: `/home/your-user/.openbb_platform/user_settings.json`. + +```json +{ +... +"preferences": { + "data_directory": "/path/to/NewOpenBBUserData", + "export_directory": "/path/to/NewOpenBBUserData" +}, +... +} +``` diff --git a/website/content/cli/quickstart.md b/website/content/cli/quickstart.md new file mode 100644 index 000000000000..ef9841087311 --- /dev/null +++ b/website/content/cli/quickstart.md @@ -0,0 +1,243 @@ +--- +title: Quick Start +sidebar_position: 2 +description: This page is a quick start guide for the OpenBB CLI. +keywords: +- quickstart +- quick start +- tutorial +- getting started +- cli +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +## Launch + +- Open a Terminal and activate the environment where the `openbb-cli` package was installed. +- On the command line, enter: `openbb` + +![CLI Home](https://github.com/OpenBB-finance/OpenBBTerminal/assets/85772166/d1617c3b-c83d-4491-a7bc-986321fd7230) + +## Login + +Login to your [OpenBB Hub account](https://my.openbb.co) to add stored keys to the session. + +```console +/account/login --pat REPLACE_WITH_YOUR_PAT +``` + +:::tip +Add `--remember-me` to the command to persist the login until actively logging out. +::: + +Login by email & password is also possible. + +```console +/account/login --email my@emailaddress.com --password n0Ts3CuR3L!kEPAT +``` + +Find all data providers [here](https://docs.openbb.co/platform/extensions/data_extensions), and manage all your credentials directly on the [OpenBB Hub](https://my.openbb.co/app/platform/credentials). + +## Menus + +:::info +Menus are distinguishable from commands by the character, `>`, on the left of the screen. +::: + +Enter a menu by typing it out and pressing return. + +```console +economy +``` + +![Economy Menu](https://github.com/OpenBB-finance/OpenBBTerminal/assets/85772166/b68491fc-d6c3-42a7-80db-bfe2aa848a5a) + +### Go Back One Level + +Return to the parent menu by entering either: + +- `..` +- `q` + +### Go Back To Home + +Return to the base menu by entering either: + +- `/` +- `home` + +### Jump Between Menus + +Use absolute paths to navigate from anywhere, to anywhere. + +From: + +```console +/equity/calendar/earnings +``` + +To: + +```console +/economy/calendar +``` + +## Commands + +Commands are displayed on-screen in a lighter colour, compared with menu items, and they will not have, `>`. + +Functions have a variety of parameters that differ by endpoint and provider. Use the `--help` dialogue to understand the nuances of any particular command. + +### How To Enter Parameters + +Parameters are all defined through the same pattern, `--argument`, followed by a space, and then the value. + +If the parameter is a boolean (true/false), there is no value to enter. Adding the `--argument` flags the parameter to be the opposite of its default state. + +:::danger +The use of positional arguments is not supported. + +❌ `historical AAPL --start_date 2024-01-01` + +✅ `historical --symbol AAPL --start_date 2024-01-01` +::: + +### Use Auto Complete + +The auto completion engine is triggered when the spacebar is pressed following any command, or parameter with a defined set of choices. + +After the first parameter has been set, remaining parameters will be triggered by entering `--`. + +```console +historical --symbol AAPL --start_date 2024-01-01 -- +``` + +![Auto Complete](https://github.com/OpenBB-finance/OpenBBTerminal/assets/85772166/78e68bbd-094e-4558-bce0-92b8d556fcaf) + +### Data Processing Commands + +Data processing extensions, like `openbb-technical` accept `--data` as an input. + +:::info +Command outputs are cached. These can be check using the `results` command and are selected with the `--data` parameter. +::: + +```console +# Store the command output +/equity/price/historical --symbol SPY --start_date 2024-01-01 --provider yfinance + +# Check results content +results + +# Use the results +/technical/rsi --data OBB0 --chart +``` + +![SPY RSI](https://github.com/OpenBB-finance/OpenBBTerminal/assets/85772166/b480da04-92e6-48e2-bccf-cebc16fb083a) + +## Help Dialogues + +Display the help dialogue by attaching, `--help` or `-h`, to any command. + +:::info +Use this to identify the providers compatible with each parameter, if applicable. +::: + +```console +calendar --help +``` + +```console +usage: calendar [--start_date START_DATE] [--end_date END_DATE] [--provider {fmp,nasdaq,tradingeconomics}] [--country COUNTRY] [--importance {Low,Medium,High}] + [--group {interest rate,inflation,bonds,consumer,gdp,government,housing,labour,markets,money,prices,trade,business}] [-h] [--export EXPORT] + [--sheet-name SHEET_NAME [SHEET_NAME ...]] + +Get the upcoming, or historical, economic calendar of global events. + +options: + --start_date START_DATE + Start date of the data, in YYYY-MM-DD format. + --end_date END_DATE End date of the data, in YYYY-MM-DD format. + --provider {fmp,nasdaq,tradingeconomics} + The provider to use for the query, by default None. + If None, the provider specified in defaults is selected or 'fmp' if there is + no default. + --country COUNTRY Country of the event. (provider: nasdaq, tradingeconomics) + -h, --help show this help message + --export EXPORT Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg + --sheet-name SHEET_NAME [SHEET_NAME ...] + Name of excel sheet to save data to. Only valid for .xlsx files. + +tradingeconomics: + --importance {Low,Medium,High} + Importance of the event. + --group {interest rate,inflation,bonds,consumer,gdp,government,housing,labour,markets,money,prices,trade,business} + Grouping of events + +``` + +If the source selected was Nasdaq, `--provider nasdaq`, the `--importance` and `--group` parameters will be ignored. + +```console +/economy/calendar --provider nasdaq --country united_states +``` + +| date | country | event | actual | previous | consensus | description | +|:--------------------|:--------------|:-------------------------|:---------|:-----------|:------------|:--------------| +| 2024-05-08 13:30:00 | United States | Fed Governor Cook Speaks | - | - | - | | +| cont... | | | | | | | + +## Export Data + +Data can be exported as a CSV, JSON, or XLSX file, and can also be exported directly from the interactive tables and charts. + +### Named File + +This command exports the Nasdaq directory as a specific CSV file. The path to the file is displayed on-screen. + +```console +/equity/search --provider nasdaq --export nasdaq_directory.csv +``` + +```console +Saved file: /Users/myusername/OpenBBUserData/nasdaq_directory.csv +``` + +### Unnamed File + +If only supplied with the file type, the export will be given a generic name beginning with the date and time. + +```console +/equity/search --provider nasdaq --export csv +``` + +``` +Saved file: /Users/myusername/OpenBBUserData/20240508_145308_controllers_search.csv +``` + +### Specify Sheet Name + +Exports can share the same `.xlsx` file by providing a `--sheet-name`. + +```console +/equity/search --provider nasdaq --export directory.xlsx --sheet-name nasdaq +``` + +## Run Multiple Commands + +A chain of commands can be run from a single line, separate each process with `/`. The example below will draw two charts and can be pasted as a single line. + +```console +/equity/price/historical --symbol AAPL,MSFT,GOOGL,AMZN,META,NVDA,NFLX,TSLA,QQQ --start_date 2022-01-01 --provider yfinance --chart/performance --symbol AAPL,MSFT,GOOGL,AMZN,META,NVDA,NFLX,TSLA,QQQ --provider finviz --chart +``` + +## Example Routine + +To demonstrate how multiple commands are sequenced as a script, try running the example Routine. + +```console +/exe --example +``` diff --git a/website/content/cli/routines/_category_.json b/website/content/cli/routines/_category_.json new file mode 100644 index 000000000000..baa94ff89038 --- /dev/null +++ b/website/content/cli/routines/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Routines", + "position": 11 +} diff --git a/website/content/cli/routines/advanced-routines.md b/website/content/cli/routines/advanced-routines.md new file mode 100644 index 000000000000..deb5988eb0ef --- /dev/null +++ b/website/content/cli/routines/advanced-routines.md @@ -0,0 +1,119 @@ +--- +title: Advanced Routines +sidebar_position: 5 +description: This page provides guidance on creating and running advanced workflows in the OpenBB CLI by + introducing variables and arguments for routines. It explains input variables, + relative time keyword variables, internal script variables and creating loops for + batch execution. +keywords: +- automated workflows +- routines +- arguments +- variables +- relative time keywords +- internal script variables +- loops +- batch execution +- Tutorial +- Running Scripts +- Executing Commands +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +## Input Variables + +Arguments are variables referenced within the `.openbb` script as `$ARGV` or `$ARGV[0]`, `$ARGV[1]`, and so on. They are provided in the CLI when running `exe` by adding the `--input` flag, followed by the variables separated by commas. + +### Example + +```text +# This script requires you to use arguments. This can be done with the following: +# exe --file routines_template_with_input.openbb -i TSLA +# Replace the name of the file with your file. + +# Navigate to the menu +/equity/price + +# Load the data and display a chart +historical --symbol $ARGV --chart +``` + +## Set Variables + +In addition to external variables using the keyword, `ARGV`, internal variables can be defined with the, `$`, character. + +Note that the variable can have a single element or can be constituted by an array where elements are separated using a comma “,”. + +### Internal Variables Example + +```text +# Example routine with internal variables. + +$TICKERS = XLE,XOP,XLB,XLI,XLP,XLY,XHE,XLV,XLF,KRE,XLK,XLC,XLU,XLRE + +/equity + +price + +historical --symbol $TICKERS --provider yfinance --start_date 2024-01-01 --chart + +home +``` + +## Relative Time Keyword Variables + +In addition to the powerful variables discussed earlier, OpenBB also supports the usage of relative keywords, particularly for working with dates. These relative keywords provide flexibility when specifying dates about the current day. There are four types of relative keywords: + +1. **AGO**: Denotes a time in the past relative to the present day. Valid examples include `$365DAYSAGO`, `$12MONTHSAGO`, `$1YEARSAGO`. + +2. **FROMNOW**: Denotes a time in the future relative to the present day. Valid examples include `$365DAYSFROMNOW`, `$12MONTHSFROMNOW`, `$1YEARSFROMNOW`. + +3. **LAST**: Refers to the last specific day of the week or month that has occurred. Valid examples include `$LASTMONDAY`, `$LASTJUNE`. + +4. **NEXT**: Refers to the next specific day of the week or month that will occur. Valid examples include `$NEXTFRIDAY`, `$NEXTNOVEMBER`. + +The result will be a date with the conventional date associated with OpenBB, i.e. `YYYY-MM-DD`. + +### Relative Time Example + +```text +$TICKERS = XLE,XOP,XLB,XLI,XLP,XLY,XHE,XLV,XLF,KRE,XLK,XLC,XLU,XLRE + +/equity + +price + +historical --symbol $TICKERS --provider yfinance --start_date $3MONTHSAGO --chart + +.. + +calendar + +earnings --start_date $NEXTMONDAY --end_date $NEXTFRIDAY --provider nasdaq + +home +``` + +## Foreach Loop + +Finally, what scripting language would this be if there were no loops? For this, we were inspired by MatLab. The loops in OpenBB utilize the foreach and end convention, allowing for iteration through a list of variables or arguments to execute a sequence of commands. + +To create a foreach loop, you need to follow these steps: + +1. Create the loop header using the syntax: `foreach $$VAR in X` where `X` represents either an argument or a list of variables. It's worth noting that you can choose alternative names for the `$$VAR` variable, as long as the `$$` convention is maintained. + +2. Insert the commands you wish to repeat on the subsequent lines. + +3. Conclude the loop with the keyword `end`. + +### Loop Example + +```text +# Iterates through ARGV elements. +foreach $$VAR in $ARGV[1:] + /equity/fundamental/filings --symbol $$VAR --provider sec +end +``` diff --git a/website/content/cli/routines/community-routines.md b/website/content/cli/routines/community-routines.md new file mode 100644 index 000000000000..1fa10fcdd048 --- /dev/null +++ b/website/content/cli/routines/community-routines.md @@ -0,0 +1,52 @@ +--- +title: Community Routines +sidebar_position: 3 +description: Page provides a detailed overview of OpenBB's Community + Routines. It explains how users can create, modify, share, vote, download, and search for investment + research scripts. +keywords: +- Community Routines +- Investment Research +- Investment Scripts +- Upvotes +- Share Scripts +- Advanced Search +- CLI +- automation +- Hub +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +## Overview + +Community Routines are a feature of the [OpenBB Hub](https://my.openbb.co) that provides methods for creating, editing and sharing OpenBB CLI scripts in the cloud. + +Users can create routines that are private or public, with public routines able to run directly from a URL. + +## Create Routines + +- From the sidebar, click on "My Routines". +- Scroll down and click the button, "Create New". +- Enter a name for the routine. +- Select the "public" check box to make it runnable via URL. +- Add tags (optional). +- Add a short description. +- Enter your workflow into the Script box. +- Click the "Create" button. + +## Run From URL + +To run a routine with a URL, it must be made public. Existing routines can be modified by clicking on the item under, "My Routines". Check the "Public" box to activate. + +The URL will follow the pattern, `https://my.openbb.co/u/{username}/routine/{routine-name}`, which can be executed from the CLI with: + +```console +/exe --url {URL} +``` + +## Download + +Alternatively, click the "Download" button at the bottom of the routine editor to manually download the file to the machine. Place the file in the `OpenBBUserData/routines` folder and open the CLI. The script will be presented as a choice by auto-complete. diff --git a/website/content/cli/routines/index.mdx b/website/content/cli/routines/index.mdx new file mode 100644 index 000000000000..cf5947ef80a0 --- /dev/null +++ b/website/content/cli/routines/index.mdx @@ -0,0 +1,31 @@ +--- +title: Routines +--- + +import NewReferenceCard from "@site/src/components/General/NewReferenceCard"; +import HeadTitle from "@site/src/components/General/HeadTitle.tsx"; + + + +
    + + + + +
\ No newline at end of file diff --git a/website/content/cli/routines/introduction-to-routines.md b/website/content/cli/routines/introduction-to-routines.md new file mode 100644 index 000000000000..7eaf8ecd9734 --- /dev/null +++ b/website/content/cli/routines/introduction-to-routines.md @@ -0,0 +1,130 @@ +--- +title: Introduction to Routines +sidebar_position: 1 +description: The page provides a detailed introduction to OpenBB Routines, which allow + users to automate processes and repetitive tasks in financial analysis and data + collection. It explains conventions, basic scripts, routine execution, and guides users on getting + started with an example. +keywords: +- OpenBB Routines +- automated processes +- repetitive tasks +- data collection +- basic script +- routine execution +- automation +- routines +- cli +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +## Overview + +OpenBB Routines allows users to capture and write simple scripts for automating processes and repetitive tasks. In essence, these are text plain-text files that can be created or modified in any basic text editor with the only difference being the `.openbb` extension. + +Other software like STATA, SPSS, and R-Studio share similar functionality in the area of Econometrics and the OpenBB routine scripts venture into the area of financial analysis and data collection to speed up the process. + +Routines make it easy to automate a series of processes, and this includes mining and dumping large amounts of data to organized spreadsheets. Making use of `--export` and `--sheet-name`, data collection is more efficient and reliable, with results that are easily replicable. + +A pipeline of commands is difficult to share, so to encourage users to share ideas and processes, we created [Community Routines](community-routines.md) for the [OpenBB Hub](https://my.openbb.co/). Routines can be created, stored, and shared - executable in any OpenBB CLI installation, by URL. + +## Pipeline of Commands + +One of the main objectives of the OpenBB Platform CLI is to automate a user's investment research workflow - not just a single command, but the complete process. This is where the pipeline of commands comes in, running a sequence of commands. + +An example of a pipeline of commands is: + +```console +/equity/price/historical --symbol AAPL/../../technical/ema --data OBB0 --length 50 +``` + +Which will perform a exponential moving average (`ema`) on the historical price of Apple (`AAPL`). + +### Step-by-Step Explanation + +This will do the following: + +1. `equity` - Go into `equity` menu. + +2. `price` - Go into `price` sub-menu. + +3. `historical --symbol AAPL` - Load historical price data for Apple. + +4. `..` (X2) will walk back to the root menu. + +5. `technical` - Go into Technical Analysis (`technical`) menu. + +6. `ema --data OBB0 --length 50` - Run the exponential moving average indicator with windows of length 50 (`--length 50`) on the last cached result (`--data OBB0`) + +## Routine execution + +Run a routine file from the main menu, with the `exe` command. Try, `exe --example`, to get a sense of what this functionality does. Below, the `--help` dialogue is displayed. + +```console +/exe -h +``` + +```console +usage: exe [--file FILE [FILE ...]] [-i ROUTINE_ARGS] [-e] [--url URL] [-h] + +Execute automated routine script. For an example, please use `exe --example` and for documentation and to learn how create your own script type `about exe`. + +optional arguments: + --file FILE [FILE ...], -f FILE [FILE ...] + The path or .openbb file to run. (default: None) + -i ROUTINE_ARGS, --input ROUTINE_ARGS + Select multiple inputs to be replaced in the routine and separated by commas. E.g. GME,AMC,BTC-USD (default: None) + -e, --example Run an example script to understand how routines can be used. (default: False) + --url URL URL to run openbb script from. (default: None) + -h, --help show this help message (default: False) + +For more information and examples, use 'about exe' to access the related guide. +``` + +## Basic Script + +The most basic script style contains 2 main elements: + +- **Comments**: any text after a hashtag (`#`) is referred to as a comment. This is used to explain what is happening within the line below and is ignored when the file is executed. +- **Commands**: any text *without* a hashtag is being run inside the CLI as if the user had prompted that line in the terminal. Note that this means that you are able to create a pipeline of commands in a single line, i.e. `equity/price/historical --symbol AAPL --provider fmp` is a valid line for the script. + +For instance, the text below corresponds to the example file that OpenBB provides. + +```console +# Navigate into the price sub-menu of the equity module. +equity/price + +# Load a company ticker, e.g. Apple +historical --symbol AAPL --provider yfinance + +# Show a candle chart with a 20 day Moving Average +/technical/ema --data OBB0 --length 20 + +# Switch over to the Fundamental Analysis menu +/equity/fundamental + +# Show balance sheet +balance --symbol aapl --provider yfinance + +# Show cash flow statement +cash --symbol aapl --provider yfinance + +# Show income statement +income --symbol aapl --provider yfinance + +# Return to home +home +``` + +## Getting started + +As a starting point, let's use the example above. + +1. Create a new text file with the name `routines_template.openbb` and copy and paste the routine above. + +2. Move the file inside the `routines` folder within the [OpenBBUserData](openbbuserdata) folder and, optionally, adjust the name to your liking. + +3. Open up the CLI, and type `exe --file routines_template`. If you changed the name of the file, then replace, `routines_template`, with that. As long as the file remains in the `~/OpenBBUserData/routines` folder, the CLI's auto-completer will provide it as a choice. diff --git a/website/content/cli/routines/routine-macro-recorder.md b/website/content/cli/routines/routine-macro-recorder.md new file mode 100644 index 000000000000..2221b7d8d342 --- /dev/null +++ b/website/content/cli/routines/routine-macro-recorder.md @@ -0,0 +1,46 @@ +--- +title: Routine Macro Recorder +sidebar_position: 4 +description: Learn how to use the macro recorder in OpenBB to start saving commands + and automate common tasks with scripts. This page guides you through the process + of recording, saving, and accessing your recorded routines. +keywords: +- macro recorder +- script routines +- global commands +- command recording +- routine script +- terminal main menu +- exe --file +- OpenBBUserData +- routines folder +- cli +- record +- stop +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +OpenBB script routines can be captured with the macro recorder, controlled with global commands. Enter, `record`, to start saving commands, and then, `stop`, terminates the recording. This means that any command you run will be captured in the script; and on `stop`, it will be saved to the `~/OpenBBUserData/routines/` folder. + +For example: + +```console +record -n sample + +/equity/price/historical --symbol SPY --provider cboe --interval 1m/home/derivatives/options/chains --symbol SPY --provider cboe/home/stop/r +``` + +The final command after `stop`, `r`, resets the CLI so that the routine is presented as a choice in the `exe` command. + +It can now be played back by entering: + +```console +/exe --file sample.openbb +``` + +:::tip +The routine can be edited to replace parameter values with input variables - e.g, `$ARGV[0]`, `$ARGV[1]`, etc. +::: diff --git a/website/content/cli/structure-and-navigation.md b/website/content/cli/structure-and-navigation.md new file mode 100644 index 000000000000..bcb6f6d840e0 --- /dev/null +++ b/website/content/cli/structure-and-navigation.md @@ -0,0 +1,42 @@ +--- +title: Structure and Navigation +sidebar_position: 3 +description: This page describes the layout and structure of the OpenBB CLI, as well as how to navigate it. +keywords: +- CLI application +- OpenBB Platform CLI +- structure +- navigation +- Command Line Interface +- navigating +- Home +- commands +- menus +- OpenBB Hub Theme Style +- Absolute paths +--- + +import HeadTitle from '@site/src/components/General/HeadTitle.tsx'; + + + +## Structure + +The OpenBB Platform CLI is a Command Line Interface (CLI) application. Functions (commands) are called through the keyboard with results returned as charts, tables, or text. Charts and tables (if enabled) are displayed in a new window, and are fully interactive, while text prints directly to the Terminal screen. + +A menu is a collection of commands (and sub-menus). A menu can be distinguished from a command because the former has a `>` on the left. The color of a command and a menu also differ, but these can be changed in OpenBB Hub's theme style. + +## Navigation + +Navigating through the CLI menus is similar to traversing folders from any operating system's command line prompt. The `/home` screen is the main directory where everything begins, and the menus are paths branched from the main. Instead of `C:\Users\OpenBB\Documents`, you'll have something like `/equity/price`. Instead of `cd ..`, you can do `..` to return the menu right above. To go back to the root menu you can do `/`. + +### Absolute Paths + +Absolute paths are also valid to-and-from any point. From the `/equity/price` menu, you can go directly to `crypto` menu with: `/crypto`. Note the forward slash at the start to denote the "absolute" path. + +### Home + +Return to the Home screen from anywhere by entering any of the following: + +- `/` +- `home` diff --git a/website/content/platform/installation.md b/website/content/platform/installation.md index 2db5639e2f68..9ef0a060ce44 100644 --- a/website/content/platform/installation.md +++ b/website/content/platform/installation.md @@ -123,15 +123,6 @@ From your python interpreter, import the OpenBB Platform: from openbb import obb ``` -:::warning -This import statement is required due to the statefulness of the obb package. There is currently no support for imports such as - -```console -from openbb.obb.equity import * -``` - -::: - When the package is imported, any installed extensions will be discovered, imported and available for use. :::note diff --git a/website/package-lock.json b/website/package-lock.json index ddcb2300e626..d4355a0b3d57 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -13952,12 +13952,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", - "peer": true - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -15149,6 +15143,7 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/website/sidebars.js b/website/sidebars.js index 17d5b5dccbc3..f2ebf8ea6ea5 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -36,6 +36,11 @@ export default { label: "OpenBB Platform", items: [{ type: "autogenerated", dirName: "platform" }], }, + { + type: "category", + label: "OpenBB CLI", + items: [{ type: "autogenerated", dirName: "cli" }], + }, { type: "category", label: "OpenBB Bot", diff --git a/website/src/components/General/NewReferenceCard.tsx b/website/src/components/General/NewReferenceCard.tsx index f40993a99090..d75bb780f193 100644 --- a/website/src/components/General/NewReferenceCard.tsx +++ b/website/src/components/General/NewReferenceCard.tsx @@ -40,13 +40,16 @@ export default function NewReferenceCard({ cleanedPath.startsWith("/platform"), "hover:bg-[#16A34A] border-[#16A34A] dark:hover:bg-[#14532D] dark:border-[#14532D]": cleanedPath.startsWith("/excel"), + "hover:bg-[#D3D3D3] border-[#D3D3D3] dark:hover:bg-[#5c5c5c] dark:border-[#5c5c5c]": + cleanedPath.startsWith("/cli"), header_docs: !cleanedPath.startsWith("/terminal") && !cleanedPath.startsWith("/pro") && !cleanedPath.startsWith("/excel") && !cleanedPath.startsWith("/sdk") && !cleanedPath.startsWith("/platform") && - !cleanedPath.startsWith("/bot"), + !cleanedPath.startsWith("/bot") && + !cleanedPath.startsWith("/cli"), }, )} to={url} diff --git a/website/src/theme/CodeBlock/Content/styles.module.css b/website/src/theme/CodeBlock/Content/styles.module.css index c1b10cde049a..1cc776c2fbfa 100644 --- a/website/src/theme/CodeBlock/Content/styles.module.css +++ b/website/src/theme/CodeBlock/Content/styles.module.css @@ -35,6 +35,7 @@ float: left; min-width: 100%; padding: var(--ifm-pre-padding); + line-height: 18px; } .codeBlockLinesWithNumbering { diff --git a/website/src/theme/DocSidebarItem/Category/index.js b/website/src/theme/DocSidebarItem/Category/index.js index 4ddec60bf68c..a374570c15c4 100644 --- a/website/src/theme/DocSidebarItem/Category/index.js +++ b/website/src/theme/DocSidebarItem/Category/index.js @@ -85,6 +85,7 @@ export default function DocSidebarItemCategory({ "OpenBB Bot": "/bot", "OpenBB Terminal Pro": "/pro", "OpenBB Add-in for Excel": "/excel", + "OpenBB CLI": "/cli", }; const newHref = labelToHrefMap[label] || href; const { diff --git a/website/src/theme/Navbar/Layout/index.js b/website/src/theme/Navbar/Layout/index.js index 8a767c3f6200..65e2a425e6e4 100644 --- a/website/src/theme/Navbar/Layout/index.js +++ b/website/src/theme/Navbar/Layout/index.js @@ -80,6 +80,18 @@ export default function NavbarLayout({ children }) { "#3a204f" ); } + } else if (cleanedPath.startsWith("/cli")) { + if (document.documentElement.getAttribute("data-theme") === "dark") { + document.documentElement.style.setProperty( + "--ifm-color-primary", + "#d3d3d3" + ); + } else { + document.documentElement.style.setProperty( + "--ifm-color-primary", + "#d3d3d3" + ); + } } else { } }, [pathname]); From 05322de3b70aff8c7a2b4a93085b64d47d7bec3f Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Tue, 14 May 2024 09:08:18 -0700 Subject: [PATCH 3/3] add linux stuff to pre-requisites (#6411) --- website/content/cli/installation.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/website/content/cli/installation.md b/website/content/cli/installation.md index 51c1819538ab..67056410d796 100644 --- a/website/content/cli/installation.md +++ b/website/content/cli/installation.md @@ -28,6 +28,29 @@ Please refer to the [OpenBB Platform install documentation](/platform/installati If the OpenBB Platform is not already installed, the `openbb-cli` package will install the default components. ::: +### Linux Requirements + +Linux users will need to take additional steps prior to installation. + +#### Rust + +Rust and Cargo must be installed, system-level, and in the PATH. Follow the instructions on-screen to install and add to PATH in the shell profile. + +```bash +curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh +``` + +#### Webkit + +Next, install webkit. + +- Debian-based / Ubuntu / Mint: `sudo apt install libwebkit2gtk-4.0-dev` + +- Arch Linux / Manjaro: `sudo pacman -S webkit2gtk` + +- Fedora: `sudo dnf install gtk3-devel webkit2gtk3-devel` + + ## PyPI Within your existing OpenBB environment, install `openbb-cli` with: @@ -36,7 +59,6 @@ Within your existing OpenBB environment, install `openbb-cli` with: pip install openbb-cli ``` - The installation script adds `openbb` to the PATH within your Python environment. The application can be launched from any path, as long as the environment is active. ```console