diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c1740f0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +* @Rippling/apps +/rippling_cli/ @Rippling/flux diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..77db2be --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +name: Lint +on: + pull_request: + workflow_dispatch: +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run Linter + run: ./run_ruff.sh + + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install poetry + run: pip install poetry + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - run: poetry install + + - name: mypy + run: poetry run mypy ./rippling_cli \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..12ace68 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,21 @@ +name: Test +on: + pull_request: + workflow_dispatch: +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install poetry + run: pip install poetry + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - run: poetry install + + - name: pytest + run: poetry run pytest \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b126c53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,163 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +develop-eggs/ +dist/ +build/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Formula ruby file +*.rb + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ diff --git a/install_hooks.sh b/install_hooks.sh new file mode 100755 index 0000000..b8f5ff0 --- /dev/null +++ b/install_hooks.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cp -f run_ruff.sh .git/hooks/pre-commit \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 1908997..124c4b1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -135,6 +135,20 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "idna" version = "3.6" @@ -146,6 +160,86 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "mypy" +version = "1.9.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + [[package]] name = "pkce" version = "1.0.3" @@ -157,6 +251,43 @@ files = [ {file = "pkce-1.0.3.tar.gz", hash = "sha256:9775fd76d8a743d39b87df38af1cd04a58c9b5a5242d5a6350ef343d06814ab6"}, ] +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "requests" version = "2.31.0" @@ -179,20 +310,26 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] -name = "setuptools" -version = "69.2.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] [[package]] name = "urllib3" @@ -213,5 +350,5 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" -python-versions = "^3.10.5" -content-hash = "68569a4d219fce9ff7772bb8a51ff69825e61ede3b1c1dd10721e6c7f134e7d8" +python-versions = "^3.10" +content-hash = "3ab0c01a982abcf9de3666f6b3c084298b25686a866c30ef2669238b9ef2464c" diff --git a/pyproject.toml b/pyproject.toml index 9c3eed4..602ecf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools>=65.5.0", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" [tool.poetry] name = "rippling_cli" @@ -11,12 +11,15 @@ authors = ["Rippling Apps "] [tool.poetry.dependencies] -python = "^3.10.5" +python = "^3.10" click = "^8.1.3" pkce = "^1.0.3" urllib3 ="^2.2.1" -requests = "^2.26.0" -setuptools = "^69.2.0" +requests = "^2.31.0" + +[tool.poetry.group.dev.dependencies] +pytest = "^7.2.1" +mypy = "^1.7.1" [tool.poetry.scripts] rippling = "rippling_cli.cli.main:cli" diff --git a/rippling_cli/cli/commands/login.py b/rippling_cli/cli/commands/login.py index 1f8b448..a507629 100644 --- a/rippling_cli/cli/commands/login.py +++ b/rippling_cli/cli/commands/login.py @@ -1,10 +1,8 @@ -from pathlib import Path import click from rippling_cli.config.config import save_oauth_token from rippling_cli.constants import CODE_CHALLENGE_METHOD, DEFAULT_CODE_VERIFIER_LENGTH -from rippling_cli.core.oauth_client import OAuthClient from rippling_cli.core.oauth_pkce import PKCE from rippling_cli.core.oauth_token import OAuthToken @@ -28,6 +26,6 @@ def login(ctx) -> None: ctx.obj.oauth_token = access_token save_oauth_token(access_token, token.expires_in) - click.echo(f"Login successful!") + click.echo("Login successful!") else: click.echo("OAuth credentials not configured") diff --git a/rippling_cli/cli/main.py b/rippling_cli/cli/main.py index 333aaee..a690a74 100644 --- a/rippling_cli/cli/main.py +++ b/rippling_cli/cli/main.py @@ -1,13 +1,13 @@ +import sys from typing import Union import click -import sys +from rippling_cli.cli.commands.login import login +from rippling_cli.config.config import get_client_id, get_oauth_token_data from rippling_cli.constants import EXIT_UNKNOWN_EXCEPTION from rippling_cli.core.oauth_token import OAuthToken from rippling_cli.core.rippling_context import RipplingContext -from rippling_cli.cli.commands.login import login -from rippling_cli.config.config import get_client_id, get_oauth_token_data @click.group(context_settings=dict(help_option_names=["-h", "--help"])) @@ -57,7 +57,7 @@ def initialize_cli() -> None: try: initialize_cli() cli() - except Exception as e: + except Exception: exit_code = EXIT_UNKNOWN_EXCEPTION finally: sys.exit(exit_code) diff --git a/rippling_cli/config/config.py b/rippling_cli/config/config.py index 7bc7b35..55d98d2 100644 --- a/rippling_cli/config/config.py +++ b/rippling_cli/config/config.py @@ -1,5 +1,5 @@ -import os import json +import os # Store the OAuth credentials in environment variables or a config file from datetime import datetime, timedelta diff --git a/rippling_cli/core/oauth_token.py b/rippling_cli/core/oauth_token.py index 5acb977..911fbda 100644 --- a/rippling_cli/core/oauth_token.py +++ b/rippling_cli/core/oauth_token.py @@ -1,14 +1,14 @@ +import http.server +import socketserver +import threading +from datetime import datetime from urllib.parse import parse_qs import click -import http.server -from datetime import datetime -import threading -import socketserver -import requests +import requests # type: ignore from rippling_cli.config.config import get_oauth_token_data -from rippling_cli.constants import RIPPLING_BASE_URL, RIPPLING_API +from rippling_cli.constants import RIPPLING_API, RIPPLING_BASE_URL class OAuthToken: @@ -49,7 +49,8 @@ def start_authorization_flow(self): if not self.client_id or not self.code_challenge or not self.code_challenge_method: raise ValueError("Missing required parameters") - url = f"{RIPPLING_BASE_URL}/oauth?clientId={self.client_id}&codeChallenge={self.code_challenge}&codeChallengeMethod={self.code_challenge_method}" + url = f"{RIPPLING_BASE_URL}/oauth?clientId={self.client_id}" \ + f"&codeChallenge={self.code_challenge}&codeChallengeMethod={self.code_challenge_method}" click.launch(url) self.server_thread = threading.Thread(target=self.run_server, daemon=True) diff --git a/rippling_cli/test/test_rippling.py b/rippling_cli/test/test_rippling.py new file mode 100644 index 0000000..56b4538 --- /dev/null +++ b/rippling_cli/test/test_rippling.py @@ -0,0 +1,12 @@ +from click.testing import CliRunner + +from rippling_cli.cli import main + + +class TestRipplingCommand: + + def test_help(self): + runner = CliRunner() + result = runner.invoke(main.cli, ['--help']) + assert result.exit_code == 0 + assert result.output diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..8de1d64 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,9 @@ + + +target-version = "py310" + +line-length = 120 + +[lint] +select = ['E', 'F', 'I'] +fixable = ["ALL"] \ No newline at end of file diff --git a/run_ruff.sh b/run_ruff.sh new file mode 100755 index 0000000..5532e55 --- /dev/null +++ b/run_ruff.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e + +RUFF_LINT="${RUFF_LINT:-true}" +REQUIRED_RUFF_VERSION="0.3.3" +PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE:-"python3"} + +ensure_ruff_installed() { + if ! "${PYTHON_EXECUTABLE}" -m ruff --version &>/dev/null; then + echo "Installing ruff ${REQUIRED_RUFF_VERSION}" + "${PYTHON_EXECUTABLE}" -m pip install ruff==${REQUIRED_RUFF_VERSION} + fi + RUFF_VERSION=$("${PYTHON_EXECUTABLE}" -m ruff --version) + if [[ $RUFF_VERSION != "ruff $REQUIRED_RUFF_VERSION" ]]; then + echo "Incorrect ruff version ${RUFF_VERSION}; Updating ruff version." + "${PYTHON_EXECUTABLE}" -m pip install ruff==${REQUIRED_RUFF_VERSION} + fi +} + +run_ruff() { + ruff_cmd="ruff check" + if [[ "$2" != "" ]]; then + ruff_cmd="$ruff_cmd $2 $1 --config ruff.toml" + else + ruff_cmd="$ruff_cmd $1 --config ruff.toml" + fi + # shellcheck disable=SC2086 + "${PYTHON_EXECUTABLE}" -m $ruff_cmd +} + +if [[ "$1" == "--fix" ]]; then + RUFF_ARGS="--fix --show-fixes" +else + RUFF_ARGS="" +fi + +if [[ $RUFF_LINT = true ]]; then + ensure_ruff_installed + run_ruff . "$RUFF_ARGS" +fi \ No newline at end of file