From 9c135812682ba9683bbdf8f9c9f60dd70325c4ee Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 13:57:48 +0530 Subject: [PATCH 01/20] added .github directory & run_ruff & linter --- .github/CODEOWNERS | 3 + .github/workflows/lint.yml | 28 +++++++ .github/workflows/test.yml | 21 +++++ .gitignore | 162 +++++++++++++++++++++++++++++++++++++ install_hooks.sh | 3 + ruff.toml | 7 ++ run_ruff.sh | 41 ++++++++++ 7 files changed, 265 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 install_hooks.sh create mode 100644 ruff.toml create mode 100644 run_ruff.sh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..26b200b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +* @Rippling/apps +/flux-sdk/ @Rippling/flux @Rippling/apps +/flux_sdk/ @Rippling/flux \ No newline at end of file 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..439882a --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +develop-eggs/ +dist/ +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 100644 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/ruff.toml b/ruff.toml new file mode 100644 index 0000000..e26becf --- /dev/null +++ b/ruff.toml @@ -0,0 +1,7 @@ +select = ['E', 'F', 'I'] + +target-version = "py310" + +line-length = 120 + +fixable = ["ALL"] diff --git a/run_ruff.sh b/run_ruff.sh new file mode 100644 index 0000000..090c510 --- /dev/null +++ b/run_ruff.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e + +RUFF_LINT="${RUFF_LINT:-true}" +REQUIRED_RUFF_VERSION="0.0.286" +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" + else + ruff_cmd="$ruff_cmd $1" + 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 From 87c86b163edb0e097095a509388df2e49a313f27 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:02:22 +0530 Subject: [PATCH 02/20] fixed run_ruff.sh & added dev dependencies --- pyproject.toml | 4 ++++ ruff.toml | 6 ++++-- run_ruff.sh | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c3eed4..1d6a153 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,5 +18,9 @@ urllib3 ="^2.2.1" requests = "^2.26.0" setuptools = "^69.2.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/ruff.toml b/ruff.toml index e26becf..8de1d64 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,7 +1,9 @@ -select = ['E', 'F', 'I'] + target-version = "py310" line-length = 120 -fixable = ["ALL"] +[lint] +select = ['E', 'F', 'I'] +fixable = ["ALL"] \ No newline at end of file diff --git a/run_ruff.sh b/run_ruff.sh index 090c510..5532e55 100644 --- a/run_ruff.sh +++ b/run_ruff.sh @@ -3,7 +3,7 @@ set -e RUFF_LINT="${RUFF_LINT:-true}" -REQUIRED_RUFF_VERSION="0.0.286" +REQUIRED_RUFF_VERSION="0.3.3" PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE:-"python3"} ensure_ruff_installed() { @@ -21,9 +21,9 @@ ensure_ruff_installed() { run_ruff() { ruff_cmd="ruff check" if [[ "$2" != "" ]]; then - ruff_cmd="$ruff_cmd $2 $1" + ruff_cmd="$ruff_cmd $2 $1 --config ruff.toml" else - ruff_cmd="$ruff_cmd $1" + ruff_cmd="$ruff_cmd $1 --config ruff.toml" fi # shellcheck disable=SC2086 "${PYTHON_EXECUTABLE}" -m $ruff_cmd From 68659f49c819616fe72d225019bd6f5a0113fbed Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:04:28 +0530 Subject: [PATCH 03/20] updated poetry.lock --- poetry.lock | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 1908997..2b2fe04 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" @@ -194,6 +325,28 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments 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 = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[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" version = "2.2.1" @@ -214,4 +367,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10.5" -content-hash = "68569a4d219fce9ff7772bb8a51ff69825e61ede3b1c1dd10721e6c7f134e7d8" +content-hash = "42b4604f06a10d6cb2558d4c52dd17f936d223fa4fe4ef1a3e74303a79080775" From f253695a7876cb087e075dbe9f51dbaa872b3955 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:15:20 +0530 Subject: [PATCH 04/20] updated poetry.lock --- poetry.lock | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2b2fe04..02665fb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -366,5 +366,5 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" -python-versions = "^3.10.5" -content-hash = "42b4604f06a10d6cb2558d4c52dd17f936d223fa4fe4ef1a3e74303a79080775" +python-versions = "^3.10" +content-hash = "91dceb537432c99fa06458949b5ebdbe131700884d40dfc1b6ebc81b0a494a0c" diff --git a/pyproject.toml b/pyproject.toml index 1d6a153..19beaab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ 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" From 4b155fd075cf65244eb98a6997777e864b49ed13 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:19:46 +0530 Subject: [PATCH 05/20] updated poetry.lock --- poetry.lock | 18 +----------------- pyproject.toml | 5 ++--- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 02665fb..729ad56 100644 --- a/poetry.lock +++ b/poetry.lock @@ -309,22 +309,6 @@ urllib3 = ">=1.21.1,<3" 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" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, -] - -[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 = "tomli" version = "2.0.1" @@ -367,4 +351,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "91dceb537432c99fa06458949b5ebdbe131700884d40dfc1b6ebc81b0a494a0c" +content-hash = "caea3819cb7a63416ae4ef3034cdfece57ba17d21c00bd5054b4d64a0cca6e2e" diff --git a/pyproject.toml b/pyproject.toml index 19beaab..b5188a7 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" @@ -16,7 +16,6 @@ click = "^8.1.3" pkce = "^1.0.3" urllib3 ="^2.2.1" requests = "^2.26.0" -setuptools = "^69.2.0" [tool.poetry.group.dev.dependencies] pytest = "^7.2.1" From e02729d030b2eeed4d54bb7194df3cdd92caa223 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:24:26 +0530 Subject: [PATCH 06/20] changed git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 439882a..b126c53 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ .Python develop-eggs/ dist/ +build/ downloads/ eggs/ .eggs/ From 41fd1a3ea2b67db86cbe4cf7e379f6e89be6b286 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:31:43 +0530 Subject: [PATCH 07/20] updated lint.yml --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 77db2be..1be7109 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,4 +25,4 @@ jobs: - run: poetry install - name: mypy - run: poetry run mypy ./rippling_cli \ No newline at end of file + run: poetry run mypy ./rippling_cli --ignore-missing-imports \ No newline at end of file From fca7d705d470f2a0a902cf8eda4d76c3908d3366 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:34:36 +0530 Subject: [PATCH 08/20] updated permissions --- run_ruff.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 run_ruff.sh diff --git a/run_ruff.sh b/run_ruff.sh old mode 100644 new mode 100755 From 9211ebb5037876909df945ae0e8e03e8b22f0020 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:36:38 +0530 Subject: [PATCH 09/20] linting fix --- rippling_cli/cli/commands/login.py | 4 +--- rippling_cli/cli/main.py | 8 ++++---- rippling_cli/config/config.py | 2 +- rippling_cli/core/oauth_token.py | 10 +++++----- 4 files changed, 11 insertions(+), 13 deletions(-) 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..d4f42a6 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 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: From 68be9573edf049f6e186a1174a3f9d151231cd6e Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:41:06 +0530 Subject: [PATCH 10/20] linting fix --- rippling_cli/core/oauth_token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rippling_cli/core/oauth_token.py b/rippling_cli/core/oauth_token.py index d4f42a6..ac41482 100644 --- a/rippling_cli/core/oauth_token.py +++ b/rippling_cli/core/oauth_token.py @@ -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) From 9297105937cc43e7d8b44c6fb62a1c5e17db96ae Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:42:43 +0530 Subject: [PATCH 11/20] permission fix --- install_hooks.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 install_hooks.sh diff --git a/install_hooks.sh b/install_hooks.sh old mode 100644 new mode 100755 From 54577c2b302bbc3b3e5b59f65c76fe1d27deca23 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:49:11 +0530 Subject: [PATCH 12/20] updated pyproject.toml and poetry.lock --- poetry.lock | 17 ++++++++++++++++- pyproject.toml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 729ad56..0e69c62 100644 --- a/poetry.lock +++ b/poetry.lock @@ -309,6 +309,21 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruamel-yaml" +version = "0.17.10" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3" +files = [ + {file = "ruamel.yaml-0.17.10-py3-none-any.whl", hash = "sha256:ffb9b703853e9e8b7861606dfdab1026cf02505bade0653d1880f4b2db47f815"}, + {file = "ruamel.yaml-0.17.10.tar.gz", hash = "sha256:106bc8d6dc6a0ff7c9196a47570432036f41d556b779c6b4e618085f57e39e67"}, +] + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + [[package]] name = "tomli" version = "2.0.1" @@ -351,4 +366,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "caea3819cb7a63416ae4ef3034cdfece57ba17d21c00bd5054b4d64a0cca6e2e" +content-hash = "abd8b972698f241ace62c0c4117ed10e4e09df3be904b7682365f95a36d18207" diff --git a/pyproject.toml b/pyproject.toml index b5188a7..d78e249 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ authors = ["Rippling Apps "] [tool.poetry.dependencies] python = "^3.10" click = "^8.1.3" +"ruamel.yaml" = "0.17.10" pkce = "^1.0.3" urllib3 ="^2.2.1" requests = "^2.26.0" From 2777dcbb571650b58420c1bcdac440ee42d31ee6 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 14:50:22 +0530 Subject: [PATCH 13/20] removed import --- poetry.lock | 17 +---------------- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0e69c62..729ad56 100644 --- a/poetry.lock +++ b/poetry.lock @@ -309,21 +309,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "ruamel-yaml" -version = "0.17.10" -description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -optional = false -python-versions = ">=3" -files = [ - {file = "ruamel.yaml-0.17.10-py3-none-any.whl", hash = "sha256:ffb9b703853e9e8b7861606dfdab1026cf02505bade0653d1880f4b2db47f815"}, - {file = "ruamel.yaml-0.17.10.tar.gz", hash = "sha256:106bc8d6dc6a0ff7c9196a47570432036f41d556b779c6b4e618085f57e39e67"}, -] - -[package.extras] -docs = ["ryd"] -jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] - [[package]] name = "tomli" version = "2.0.1" @@ -366,4 +351,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "abd8b972698f241ace62c0c4117ed10e4e09df3be904b7682365f95a36d18207" +content-hash = "caea3819cb7a63416ae4ef3034cdfece57ba17d21c00bd5054b4d64a0cca6e2e" diff --git a/pyproject.toml b/pyproject.toml index d78e249..b5188a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ authors = ["Rippling Apps "] [tool.poetry.dependencies] python = "^3.10" click = "^8.1.3" -"ruamel.yaml" = "0.17.10" pkce = "^1.0.3" urllib3 ="^2.2.1" requests = "^2.26.0" From 143859e7f322e0944ce8fc64511c20350c591da5 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 15:29:13 +0530 Subject: [PATCH 14/20] fixed import --- rippling_cli/test/test_rippling.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 rippling_cli/test/test_rippling.py 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 From 7f0680e5ae05b48520e1feb61f2d0e6e937edb53 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Wed, 17 Apr 2024 15:47:14 +0530 Subject: [PATCH 15/20] added #type :ignore --- .github/workflows/lint.yml | 2 +- poetry.lock | 2 +- pyproject.toml | 2 +- rippling_cli/core/oauth_token.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1be7109..77db2be 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,4 +25,4 @@ jobs: - run: poetry install - name: mypy - run: poetry run mypy ./rippling_cli --ignore-missing-imports \ No newline at end of file + run: poetry run mypy ./rippling_cli \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 729ad56..124c4b1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -351,4 +351,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "caea3819cb7a63416ae4ef3034cdfece57ba17d21c00bd5054b4d64a0cca6e2e" +content-hash = "3ab0c01a982abcf9de3666f6b3c084298b25686a866c30ef2669238b9ef2464c" diff --git a/pyproject.toml b/pyproject.toml index b5188a7..602ecf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ python = "^3.10" click = "^8.1.3" pkce = "^1.0.3" urllib3 ="^2.2.1" -requests = "^2.26.0" +requests = "^2.31.0" [tool.poetry.group.dev.dependencies] pytest = "^7.2.1" diff --git a/rippling_cli/core/oauth_token.py b/rippling_cli/core/oauth_token.py index ac41482..911fbda 100644 --- a/rippling_cli/core/oauth_token.py +++ b/rippling_cli/core/oauth_token.py @@ -5,7 +5,7 @@ from urllib.parse import parse_qs import click -import requests +import requests # type: ignore from rippling_cli.config.config import get_oauth_token_data from rippling_cli.constants import RIPPLING_API, RIPPLING_BASE_URL From 057ab1a007bfb3a807b0ca66cab34bfa5f40aa0e Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Thu, 18 Apr 2024 19:24:46 +0530 Subject: [PATCH 16/20] Fixed imports --- rippling_cli/cli/commands/app.py | 67 ++++++++++++++++++++++++++++ rippling_cli/cli/main.py | 5 ++- rippling_cli/config/config.py | 46 +++++++++++++++++--- rippling_cli/constants.py | 1 + rippling_cli/core/api_client.py | 72 +++++++++++++++++++++++++++++++ rippling_cli/utils/login_utils.py | 10 +++++ 6 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 rippling_cli/cli/commands/app.py create mode 100644 rippling_cli/core/api_client.py create mode 100644 rippling_cli/utils/login_utils.py diff --git a/rippling_cli/cli/commands/app.py b/rippling_cli/cli/commands/app.py new file mode 100644 index 0000000..235205d --- /dev/null +++ b/rippling_cli/cli/commands/app.py @@ -0,0 +1,67 @@ +import os + +import click + +from rippling_cli.config.config import get_app_config, save_app_config +from rippling_cli.constants import RIPPLING_API +from rippling_cli.core.api_client import APIClient +from rippling_cli.utils.login_utils import ensure_logged_in + + +@click.group() +@click.pass_context +def app(ctx: click.Context) -> None: + """Manage flux apps""" + ensure_logged_in(ctx) + + +@app.command() +def list() -> None: + """This command displays a list of all apps owned by the developer.""" + ctx: click.Context = click.get_current_context() + api_client = APIClient(base_url=RIPPLING_API, headers={"Authorization": f"Bearer {ctx.obj.oauth_token}"}) + endpoint = "/apps/api/integrations" + + for page in api_client.find_paginated(endpoint): + click.echo(f"Page: {len(page)} apps") + + for app in page: + click.echo(f"- {app.get('displayName')} ({app.get('id')})") + + if not click.confirm("Continue"): + break + + click.echo("End of apps list.") + + +@app.command() +@click.option("--app_id", required=True, type=str, help="The app id to set for the current directory.") +def set(app_id: str) -> None: + """This command sets the current app within the app_config.json file located in the .rippling directory.""" + ctx: click.Context = click.get_current_context() + api_client = APIClient(base_url=RIPPLING_API, headers={"Authorization": f"Bearer {ctx.obj.oauth_token}"}) + + endpoint = "/apps/api/apps/?large_get_query=true" + response = api_client.post(endpoint, data={"query": f"id={app_id}&limit=1"}) + app_list = response.json() if response.status_code == 200 else [] + + if response.status_code != 200 or len(app_list) == 0: + click.echo(f"Invalid app id: {app_id}") + return + + app_name = app_list[0].get("displayName") + + save_app_config(app_id, app_name) + click.echo(f"Current app set to {app_name} ({app_id})") + + +@app.command() +def current() -> None: + """This command indicates the current app selected by the developer within the directory.""" + app_config_json = get_app_config() + app_config = app_config_json.get(os.getcwd()) + if not app_config: + click.echo("No app selected.") + return + + click.echo(f"{app_config.get('displayName')} ({app_config.get('id')})") \ No newline at end of file diff --git a/rippling_cli/cli/main.py b/rippling_cli/cli/main.py index a690a74..4f7997c 100644 --- a/rippling_cli/cli/main.py +++ b/rippling_cli/cli/main.py @@ -1,8 +1,8 @@ -import sys from typing import Union import click +from rippling_cli.cli.commands.app import app 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 @@ -41,7 +41,8 @@ def cli(ctx): COMMANDS_LIST: list[Union[click.Command, click.Group]] = [ - login + login, + app ] diff --git a/rippling_cli/config/config.py b/rippling_cli/config/config.py index 55d98d2..dfb6269 100644 --- a/rippling_cli/config/config.py +++ b/rippling_cli/config/config.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta from pathlib import Path -from rippling_cli.constants import OAUTH_TOKEN_FILE_NAME, RIPPLING_DIRECTORY_NAME +from rippling_cli.constants import APP_CONFIG_FILE, OAUTH_TOKEN_FILE_NAME, RIPPLING_DIRECTORY_NAME CLIENT_ID = "AgvGDwoBRb0BJAnL2CQ8dNbE6J2fgCFIchEOyr5S" config_dir = Path.home() / f".{RIPPLING_DIRECTORY_NAME}" @@ -14,12 +14,15 @@ def get_client_id(): return CLIENT_ID - -def get_oauth_token_data(): +def create_base_directory_if_not_exists(): # Create the directory if it doesn't exist if not os.path.exists(config_dir): os.makedirs(config_dir) +def get_oauth_token_data(): + + create_base_directory_if_not_exists() + token_file = config_dir / OAUTH_TOKEN_FILE_NAME if token_file.exists(): with token_file.open("r") as f: @@ -29,9 +32,7 @@ def get_oauth_token_data(): def save_oauth_token(token, expires_in=3600): - # Create the directory if it doesn't exist - if not os.path.exists(config_dir): - os.makedirs(config_dir) + create_base_directory_if_not_exists() data = { "token": str(token), @@ -40,3 +41,36 @@ def save_oauth_token(token, expires_in=3600): token_file = config_dir / OAUTH_TOKEN_FILE_NAME with token_file.open("w") as f: json.dump(data, f) + + +def get_app_config(): + """ + Load the app configuration from the specified directory. + + Returns: + dict: The app configuration data. + """ + create_base_directory_if_not_exists() + + config_file = config_dir / APP_CONFIG_FILE + if config_file.exists(): + with config_file.open("r") as f: + return json.load(f) + return {} + + +def save_app_config(app_id: str, app_name: str): + """ + Save the app configuration to the specified directory. + Args: + :param app_id: + """ + create_base_directory_if_not_exists() + + app_config = get_app_config() + + app_config.update({f"{os.getcwd()}": {"id": app_id, "displayName": app_name}}) + + config_file = config_dir / APP_CONFIG_FILE + with config_file.open("w") as f: + json.dump(app_config, f) diff --git a/rippling_cli/constants.py b/rippling_cli/constants.py index edcb2db..80a0fd0 100644 --- a/rippling_cli/constants.py +++ b/rippling_cli/constants.py @@ -1,5 +1,6 @@ RIPPLING_DIRECTORY_NAME = "rippling_cli" OAUTH_TOKEN_FILE_NAME = "oauth_token.json" +APP_CONFIG_FILE = "app_config.json" CODE_CHALLENGE_METHOD = "S256" RIPPLING_BASE_URL = "https://app.rippling.com" RIPPLING_API = "https://app.rippling.com/api" diff --git a/rippling_cli/core/api_client.py b/rippling_cli/core/api_client.py new file mode 100644 index 0000000..b2b630f --- /dev/null +++ b/rippling_cli/core/api_client.py @@ -0,0 +1,72 @@ +import requests + + +class APIClient: + def __init__(self, base_url, headers=None): + self.base_url = base_url + self.headers = headers or {} + + def make_request(self, method, endpoint, params=None, data=None): + url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}" + response = requests.request(method, url, params=params, json=data, headers=self.headers) + return response + + def get(self, endpoint, params=None): + return self.make_request("GET", endpoint, params=params) + + def post(self, endpoint, data): + return self.make_request("POST", endpoint, data=data) + + def put(self, endpoint, data): + return self.make_request("PUT", endpoint, data=data) + + def delete(self, endpoint, params=None): + return self.make_request("DELETE", endpoint, params=params) + + def find_paginated(self, endpoint, page=1, page_size=10, read_preference="SECONDARY_PREFERRED"): + """ + Fetch paginated data from the API. + + Args: + endpoint (str): The API endpoint. + headers (dict): The headers for the API request. + page (int): The page number to fetch. + per_page (int): The number of items to fetch per page. + + Yields: + dict: The data from the API response. + :param endpoint: + :param page: + :param page_size: + :param read_preference: + """ + has_more = True + cursor = None + while has_more: + payload = { + "paginationParams": { + "page": page, + "cursor": cursor, + "sortingMetadata": { + "order": "DESC", + "column": { + "sortKey": "createdAt" + } + } + }, + "pageSize": page_size, + "readPreference": read_preference + } + response = self.make_request("POST", f"{endpoint}/find_paginated", data=payload) + + if response.status_code == 200: + data = response.json() + cursor = data.get("cursor") + has_more = False if not cursor else True + items = data["data"] + page += 1 + print(page) + yield items + else: + response.raise_for_status() + break diff --git a/rippling_cli/utils/login_utils.py b/rippling_cli/utils/login_utils.py new file mode 100644 index 0000000..7169611 --- /dev/null +++ b/rippling_cli/utils/login_utils.py @@ -0,0 +1,10 @@ +import click + +from rippling_cli.cli.commands.login import login +from rippling_cli.core.oauth_token import OAuthToken + + +def ensure_logged_in(ctx: click.Context): + if OAuthToken.is_token_expired(): + click.echo("You are not logged in. Please log in first.") + ctx.invoke(login) From 9fed5543cbece117dd967cc5fe685989cc96e83b Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Thu, 18 Apr 2024 19:45:27 +0530 Subject: [PATCH 17/20] fix imports --- rippling_cli/cli/commands/flux/__init__.py | 0 rippling_cli/cli/commands/{ => flux}/app.py | 0 rippling_cli/cli/commands/flux/flux.py | 14 ++++++++++++++ rippling_cli/cli/main.py | 4 ++-- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 rippling_cli/cli/commands/flux/__init__.py rename rippling_cli/cli/commands/{ => flux}/app.py (100%) create mode 100644 rippling_cli/cli/commands/flux/flux.py diff --git a/rippling_cli/cli/commands/flux/__init__.py b/rippling_cli/cli/commands/flux/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rippling_cli/cli/commands/app.py b/rippling_cli/cli/commands/flux/app.py similarity index 100% rename from rippling_cli/cli/commands/app.py rename to rippling_cli/cli/commands/flux/app.py diff --git a/rippling_cli/cli/commands/flux/flux.py b/rippling_cli/cli/commands/flux/flux.py new file mode 100644 index 0000000..0055862 --- /dev/null +++ b/rippling_cli/cli/commands/flux/flux.py @@ -0,0 +1,14 @@ +import click + +from rippling_cli.cli.commands.flux.app import app +from rippling_cli.utils.login_utils import ensure_logged_in + + +@click.group() +@click.pass_context +def flux(ctx: click.Context) -> None: + """Manage flux apps""" + ensure_logged_in(ctx) + + +flux.add_command(app) # type: ignore diff --git a/rippling_cli/cli/main.py b/rippling_cli/cli/main.py index 51d5550..e514653 100644 --- a/rippling_cli/cli/main.py +++ b/rippling_cli/cli/main.py @@ -3,7 +3,7 @@ import click -from rippling_cli.cli.commands.app import app +from rippling_cli.cli.commands.flux.flux import flux 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 @@ -43,7 +43,7 @@ def cli(ctx): COMMANDS_LIST: list[Union[click.Command, click.Group]] = [ login, - app + flux ] From 3aad3e92d0a441c9e5e10db5e7579cf4f7745e20 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Thu, 18 Apr 2024 19:49:07 +0530 Subject: [PATCH 18/20] fixed lint typing --- rippling_cli/core/api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rippling_cli/core/api_client.py b/rippling_cli/core/api_client.py index b2b630f..ac14fd8 100644 --- a/rippling_cli/core/api_client.py +++ b/rippling_cli/core/api_client.py @@ -1,4 +1,4 @@ -import requests +import requests # type: ignore class APIClient: From b03f83bf26cdd49b9905520683f1cd4035a8d4a7 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Thu, 18 Apr 2024 21:49:02 +0530 Subject: [PATCH 19/20] Update api_client.py --- rippling_cli/core/api_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rippling_cli/core/api_client.py b/rippling_cli/core/api_client.py index ac14fd8..f6c72d0 100644 --- a/rippling_cli/core/api_client.py +++ b/rippling_cli/core/api_client.py @@ -65,7 +65,6 @@ def find_paginated(self, endpoint, page=1, page_size=10, read_preference="SECOND has_more = False if not cursor else True items = data["data"] page += 1 - print(page) yield items else: response.raise_for_status() From d83b0c817b37801360ec905fdea54c37fe9b7975 Mon Sep 17 00:00:00 2001 From: vguptarippling Date: Fri, 19 Apr 2024 18:25:20 +0530 Subject: [PATCH 20/20] fixed import --- rippling_cli/cli/commands/flux/app.py | 8 ++-- rippling_cli/config/config.py | 55 ++++++++++++++++++++------- rippling_cli/constants.py | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/rippling_cli/cli/commands/flux/app.py b/rippling_cli/cli/commands/flux/app.py index 235205d..a9727b0 100644 --- a/rippling_cli/cli/commands/flux/app.py +++ b/rippling_cli/cli/commands/flux/app.py @@ -1,4 +1,3 @@ -import os import click @@ -58,10 +57,9 @@ def set(app_id: str) -> None: @app.command() def current() -> None: """This command indicates the current app selected by the developer within the directory.""" - app_config_json = get_app_config() - app_config = app_config_json.get(os.getcwd()) - if not app_config: + app_config = get_app_config() + if not app_config or len(app_config.keys()) == 0: click.echo("No app selected.") return - click.echo(f"{app_config.get('displayName')} ({app_config.get('id')})") \ No newline at end of file + click.echo(f"{app_config.get('displayName')} ({app_config.get('id')})") diff --git a/rippling_cli/config/config.py b/rippling_cli/config/config.py index dfb6269..8734df7 100644 --- a/rippling_cli/config/config.py +++ b/rippling_cli/config/config.py @@ -8,22 +8,23 @@ from rippling_cli.constants import APP_CONFIG_FILE, OAUTH_TOKEN_FILE_NAME, RIPPLING_DIRECTORY_NAME CLIENT_ID = "AgvGDwoBRb0BJAnL2CQ8dNbE6J2fgCFIchEOyr5S" -config_dir = Path.home() / f".{RIPPLING_DIRECTORY_NAME}" +global_config_dir = Path.home() / RIPPLING_DIRECTORY_NAME def get_client_id(): return CLIENT_ID -def create_base_directory_if_not_exists(): + +def create_base_directory_if_not_exists(config_dir=global_config_dir): # Create the directory if it doesn't exist if not os.path.exists(config_dir): os.makedirs(config_dir) -def get_oauth_token_data(): +def get_oauth_token_data(): create_base_directory_if_not_exists() - token_file = config_dir / OAUTH_TOKEN_FILE_NAME + token_file = global_config_dir / OAUTH_TOKEN_FILE_NAME if token_file.exists(): with token_file.open("r") as f: return json.load(f) @@ -31,18 +32,39 @@ def get_oauth_token_data(): def save_oauth_token(token, expires_in=3600): - create_base_directory_if_not_exists() data = { "token": str(token), "expiration_timestamp": (datetime.now() + timedelta(seconds=expires_in)).timestamp() } - token_file = config_dir / OAUTH_TOKEN_FILE_NAME + token_file = global_config_dir / OAUTH_TOKEN_FILE_NAME with token_file.open("w") as f: json.dump(data, f) +def get_app_config_dir(start_dir): + """ + Find the nearest directory containing the app configuration. + + Args: + start_dir (str): The starting directory to begin the search. + + Returns: + str: The path to the directory containing the app configuration, or None if not found. + """ + current_dir = start_dir + while True: + config_dir = os.path.join(current_dir, RIPPLING_DIRECTORY_NAME) + config_file = os.path.join(config_dir, APP_CONFIG_FILE) + if os.path.isdir(config_dir) and os.path.isfile(config_file): + return config_dir + parent_dir = os.path.dirname(current_dir) + if parent_dir == current_dir: + return None + current_dir = parent_dir + + def get_app_config(): """ Load the app configuration from the specified directory. @@ -50,11 +72,13 @@ def get_app_config(): Returns: dict: The app configuration data. """ - create_base_directory_if_not_exists() + config_dir = get_app_config_dir(os.getcwd()) + if not config_dir: + return {} - config_file = config_dir / APP_CONFIG_FILE - if config_file.exists(): - with config_file.open("r") as f: + config_file = os.path.join(config_dir, APP_CONFIG_FILE) + if os.path.exists(config_file): + with open(config_file, "r") as f: return json.load(f) return {} @@ -63,13 +87,16 @@ def save_app_config(app_id: str, app_name: str): """ Save the app configuration to the specified directory. Args: + :param app_name: :param app_id: """ - create_base_directory_if_not_exists() + config_dir = Path.cwd() / RIPPLING_DIRECTORY_NAME + create_base_directory_if_not_exists(config_dir) - app_config = get_app_config() - - app_config.update({f"{os.getcwd()}": {"id": app_id, "displayName": app_name}}) + app_config = { + "id": app_id, + "displayName": app_name + } config_file = config_dir / APP_CONFIG_FILE with config_file.open("w") as f: diff --git a/rippling_cli/constants.py b/rippling_cli/constants.py index 80a0fd0..0b7d6bb 100644 --- a/rippling_cli/constants.py +++ b/rippling_cli/constants.py @@ -1,4 +1,4 @@ -RIPPLING_DIRECTORY_NAME = "rippling_cli" +RIPPLING_DIRECTORY_NAME = ".rippling_cli" OAUTH_TOKEN_FILE_NAME = "oauth_token.json" APP_CONFIG_FILE = "app_config.json" CODE_CHALLENGE_METHOD = "S256"