diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..217ed8e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +docs/syntax-highlighting.css linguist-generated +docs/theme.css linguist-generated diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..f72beb8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: cssnr diff --git a/.github/disabled/publish.yaml b/.github/disabled/publish.yaml deleted file mode 100644 index 0eba812..0000000 --- a/.github/disabled/publish.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: "GitHub Pages" - -on: - release: - types: [published] -# workflow_dispatch: -# environment: -# type: choice -# description: Choose Environment -# options: -# - testpypi -# - pypi - push: - -env: - ENVIRONMENT: "testpypi" - REPOSITORY: "https://test.pypi.org/legacy/" - -jobs: - publish: - name: "Publish" - runs-on: ubuntu-latest - timeout-minutes: 5 - if: github.event_name == 'release' && !github.event.release.prerelease - - permissions: - id-token: write - - environment: - name: ${{ env.ENVIRONMENT }} - url: https://pypi.org/project/vultr-python/ - - steps: - - name: "PyPi Publish" - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: ${{ env.REPOSITORY }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e61c27d..92a7c85 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,11 +1,16 @@ -name: "Build" +name: "Build Package" on: - workflow_dispatch: - release: - types: [published] - pull_request: - branches: ["master"] + workflow_call: + inputs: + name: + description: "Artifact Name" + type: string + required: true + path: + description: "Build Path" + type: string + default: dist jobs: build: @@ -15,21 +20,60 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v5 - - name: "Setup Python" - uses: actions/setup-python@v4 + - name: "Debug event.json" + continue-on-error: true + run: cat "${GITHUB_EVENT_PATH}" + + - name: "Debug CTX github" + continue-on-error: true + env: + GITHUB_CTX: ${{ toJSON(github) }} + run: echo "$GITHUB_CTX" + + #- name: "Debug Environment" + # continue-on-error: true + # run: env + + - name: "Setup Python 3.13" + uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: "3.13" + cache: "pip" - - name: "Build" + - name: "Install" run: | python -m pip install -U pip - python -m pip install -U build + python -m pip install --group dev + + - name: "Build" + run: | python -m build - - name: "Upload Artifact" - uses: actions/upload-artifact@v3 + - name: "List Artifacts" + env: + path: ${{ inputs.path }} + run: | + echo "::group::ls" + ls -lAhR "${path}" + echo "::endgroup::" + echo "::group::tree" + tree "${path}" + echo "::endgroup::" + results="$(tree "${path}")" + markdown='Artifacts:\n```text\n'"${results}"'\n```' + echo -e "${markdown}" >> $GITHUB_STEP_SUMMARY + + - name: "Upload to Actions" + uses: actions/upload-artifact@v5 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} + + - name: "Send Failure Notification" + if: ${{ failure() }} + uses: sarisia/actions-status-discord@v1 with: - name: vultr-python - path: dist/ + webhook: ${{ secrets.DISCORD_WEBHOOK }} + description: ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 13916ff..21272c5 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,12 +1,20 @@ -name: "GitHub Pages" +name: "Build Docs" on: - push: - branches: ["master"] - workflow_dispatch: - repository_dispatch: - types: - - webhook + workflow_call: + inputs: + name: + description: "Artifact Name" + type: string + default: github-pages + path: + description: "Build Path" + type: string + default: site + +env: + logo: "https://raw.githubusercontent.com/smashedr/repo-images/refs/heads/master/vultr-python/logo128.png" + link: ${{ github.event.repository.html_url }} jobs: build: @@ -16,51 +24,75 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v5 + + - name: "Debug CTX github" + continue-on-error: true + env: + GITHUB_CTX: ${{ toJSON(github) }} + run: echo "$GITHUB_CTX" + + - name: "Debug" + continue-on-error: true + run: | + echo "logo: ${{ env.logo }}" + echo "link: ${{ env.link }}" - - name: "Setup Python" - uses: actions/setup-python@v4 + - name: "Setup Python 3.13" + uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: "3.13" + cache: "pip" - name: "Build Docs" run: | python -m pip install -U pip python -m pip install -U pdoc - python -m pip install -Ur requirements.txt - python -m pdoc -o _site vultr.py + python -m pip install -e . + python -m pdoc -t docs -o "${{ inputs.path }}" \ + --favicon "${{ env.logo }}" \ + --logo "${{ env.logo }}" \ + --logo-link "${{ env.link }}" \ + vultr - name: "Update Permissions" run: | - chmod -c -R +rX "_site/" | while read line; do - echo "::warning title=Invalid file permissions automatically fixed::$line" + chmod -c -R +rX "${{ inputs.path }}" | while read name; do + echo "::notice::Fixed invalid file permissions: ${name}" done + - name: "List Artifacts" + env: + path: ${{ inputs.path }} + run: | + echo "::group::ls" + ls -lAhR "${path}" + echo "::endgroup::" + echo "::group::tree" + tree "${path}" + echo "::endgroup::" + results="$(tree "${path}")" + markdown='Artifacts:\n```text\n'"${results}"'\n```' + echo -e "${markdown}" >> $GITHUB_STEP_SUMMARY + - name: "Upload Pages Artifact" - uses: actions/upload-pages-artifact@v2 + if: ${{ inputs.name == 'github-pages' }} + uses: actions/upload-pages-artifact@v4 with: - path: _site/ + path: ${{ inputs.path }} - deploy: - name: "Deploy" - runs-on: ubuntu-latest - timeout-minutes: 5 - needs: build - - permissions: - pages: write - id-token: write - - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - name: "Configure Pages" - uses: actions/configure-pages@v3 + - name: "Upload Artifact" + if: ${{ inputs.name != 'github-pages' }} + uses: actions/upload-artifact@v5 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} + if-no-files-found: "error" + include-hidden-files: true - - name: "Deploy Pages" - id: deployment - uses: actions/deploy-pages@v2 + - name: "Send Failure Notification" + if: ${{ failure() }} + uses: sarisia/actions-status-discord@v1 with: - artifact_name: github-pages + webhook: ${{ secrets.DISCORD_WEBHOOK }} + description: ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..7dfc8cd --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,167 @@ +name: "Lint" + +on: + workflow_dispatch: + push: + branches: [master] + paths-ignore: + - ".gitignore" + - ".prettierignore" + - "MANIFEST.in" + - "pyproject.toml" + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: "Lint" + runs-on: ubuntu-latest + timeout-minutes: 5 + + permissions: + contents: read + + steps: + - name: "Checkout" + uses: actions/checkout@v5 + + - name: "Debug event.json" + continue-on-error: true + run: cat "${GITHUB_EVENT_PATH}" + + - name: "Debug CTX github" + continue-on-error: true + env: + GITHUB_CTX: ${{ toJSON(github) }} + run: echo "$GITHUB_CTX" + + - name: "Debug Environment" + continue-on-error: true + run: env + + #- name: "Check Changed Files" + # id: changed + # uses: tj-actions/changed-files@v47 + # with: + # files: | + # src/** + # tests/** + # pyproject.toml + + - name: "Setup Python 3.13" + uses: actions/setup-python@v6 + with: + python-version: "3.13" + cache: "pip" + + - name: "Install" + id: install + run: | + python -m pip install -U pip + python -m pip install --group dev + python -m pip install -e . + python -m pip list + + - name: "Debug" + continue-on-error: true + run: | + python -V + which python + echo "::group::pip list" + python -m pip list + echo "::endgroup::" + + - name: "astral-sh/ruff" + if: ${{ !cancelled() }} + uses: astral-sh/ruff-action@v3 + with: + version: latest + + - name: "astral-sh/ty" + if: ${{ !cancelled() }} + run: | + ty check --python "$(which python)" -v + + - name: "psf/black" + if: ${{ !cancelled() }} + uses: psf/black@stable + + - name: "isort" + if: ${{ !cancelled() }} + uses: isort/isort-action@v1 + + - name: "mypy" + if: ${{ !cancelled() }} + run: | + mypy src + + - name: "bandit" + if: ${{ !cancelled() }} + run: | + bandit -c pyproject.toml -r src + + - name: "prettier" + if: ${{ !cancelled() }} + run: | + echo "::group::Install" + npm install prettier + echo "::endgroup::" + npx prettier --check . + + - name: "yamllint" + if: ${{ !cancelled() }} + env: + CONFIG: "{extends: relaxed, ignore: [node_modules/], rules: {line-length: {max: 119}}}" + run: | + echo "::group::List Files" + yamllint -d '${{ env.CONFIG }}' --list-files . + echo "::endgroup::" + yamllint -d '${{ env.CONFIG }}' . + + - name: "actionlint" + if: ${{ !cancelled() }} + run: | + echo "::group::Download" + loc=$(curl -sI https://github.com/rhysd/actionlint/releases/latest | grep -i '^location:') + echo "loc: ${loc}" + tag=$(echo "${loc}" | sed -E 's|.*/tag/v?(.*)|\1|' | tr -d '\t\r\n') + echo "tag: ${tag}" + url="https://github.com/rhysd/actionlint/releases/latest/download/actionlint_${tag}_linux_amd64.tar.gz" + echo "url: ${url}" + curl -sL "${url}" | tar xz -C "${RUNNER_TEMP}" actionlint + file "${RUNNER_TEMP}/actionlint" + "${RUNNER_TEMP}/actionlint" --version + echo "::endgroup::" + "${RUNNER_TEMP}/actionlint" -color -verbose -shellcheck= -pyflakes= + + #- name: "pytest" + # if: ${{ !cancelled() }} + # id: coverage + # run: | + # coverage run -m pytest + # coverage xml + # coverage report -m + + #- name: "codecov" + # if: ${{ !cancelled() && steps.coverage.outcome == 'success' }} + # uses: codecov/codecov-action@v5 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + + #- name: "hadolint" + # if: ${{ !cancelled() }} + # uses: hadolint/hadolint-action@v3.3.0 + # with: + # dockerfile: Dockerfile + + #- name: "Vale" + # if: ${{ !cancelled() }} + # uses: errata-ai/vale-action@v2.1.1 + + #- name: "SonarQube" + # uses: SonarSource/sonarqube-scan-action@v4 + # env: + # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml new file mode 100644 index 0000000..be6a19f --- /dev/null +++ b/.github/workflows/pages.yaml @@ -0,0 +1,46 @@ +name: "GitHub Pages" + +on: + workflow_dispatch: + push: + branches: [master] + paths: + - ".github/workflows/docs.yaml" + - ".github/workflows/pages.yaml" + - "docs/**" + - "src/**" + +jobs: + build: + name: "Build" + uses: ./.github/workflows/docs.yaml + secrets: inherit + permissions: + contents: read + + deploy: + name: "Deploy" + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: build + + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: "Deploy Pages" + id: deployment + uses: actions/deploy-pages@v4 + + - name: "Send Notification" + if: ${{ !cancelled() }} + continue-on-error: true + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + description: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml new file mode 100644 index 0000000..465f3f1 --- /dev/null +++ b/.github/workflows/preview.yaml @@ -0,0 +1,43 @@ +name: "Preview" + +on: + workflow_dispatch: + push: + branches-ignore: [master] + paths: + - ".github/workflows/docs.yaml" + - ".github/workflows/preview.yaml" + - "docs/**" + - "src/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: "Build" + if: ${{ !contains(github.event.head_commit.message, '#nopreview') }} + uses: ./.github/workflows/docs.yaml + secrets: inherit + permissions: + contents: read + with: + name: artifact + + deploy: + name: "Deploy" + uses: cssnr/workflows/.github/workflows/deploy-static.yaml@master + needs: build + permissions: + contents: read + with: + name: "preview" + url: "https://dev-static.cssnr.com/" + robots: true + secrets: + host: ${{ secrets.DEV_DEPLOY_HOST }} + port: ${{ secrets.DEV_DEPLOY_PORT }} + user: ${{ secrets.DEV_DEPLOY_USER }} + pass: ${{ secrets.DEV_DEPLOY_PASS }} + webhook: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..c575493 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,168 @@ +name: "Release" + +on: + release: + types: [published] + +env: + url: "https://${{ github.event.release.prerelease && 'test.' || '' }}pypi.org/p/vultr-python" + repository-url: "https://${{ github.event.release.prerelease && 'test' || 'upload' }}.pypi.org/legacy/" + +jobs: + build: + name: "Build" + uses: ./.github/workflows/build.yaml + secrets: inherit + with: + name: "release" + + publish: + name: "Publish" + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: [build] + + permissions: + contents: write + id-token: write + + environment: + name: ${{ github.event.release.prerelease && 'test' || 'pypi' }} + url: ${{ env.url }} + + steps: + - name: "Debug event.json" + continue-on-error: true + run: cat "${GITHUB_EVENT_PATH}" + + - name: "Debug CTX github" + continue-on-error: true + env: + GITHUB_CTX: ${{ toJSON(github) }} + run: echo "$GITHUB_CTX" + + #- name: "Debug Environment" + # continue-on-error: true + # run: env + + - name: "Debug" + run: | + echo "github.ref_name: ${{ github.ref_name }}" + echo "name: ${{ github.event.release.prerelease && 'test' || 'pypi' }}" + echo "env.url: ${{ env.url }}" + echo "env.repository-url: ${{ env.repository-url }}" + + - name: "Download Artifact" + uses: actions/download-artifact@v6 + with: + name: "release" + path: "dist" + + - name: "Verify Artifacts" + env: + path: "dist" + tag: ${{ github.ref_name }} + run: | + ls -lAh ${path}/* + results="$(ls -lAh ${path}/* | awk '{print $9" - "$5}')" + markdown='Artifacts:\n```text\n'"${results}"'\n```' + echo -e "${markdown}" >> $GITHUB_STEP_SUMMARY + ls ${path} | grep -- "-${tag}-" > /dev/null + + - name: "Publish to PyPI" + id: publish + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + repository-url: ${{ env.repository-url }} + #attestations: false + + - name: "Summary" + if: ${{ always() }} + env: + tag: ${{ github.ref_name }} + url: ${{ env.url }} + repository-url: ${{ env.repository-url }} + run: | + if [ "${{ steps.publish.outcome }}" == "success" ];then + markdown="🎉 Published Version: \`${tag}\`\n\n[${url}](${url})\n\n" + else + markdown="⛔ Error Publishing Version: \`${tag}\`\n\n" + fi + markdown+="Repository URL: \`${repository-url}\`" + echo -e "${markdown}" >> $GITHUB_STEP_SUMMARY + + - name: "Send Notification" + if: ${{ !cancelled() }} + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + description: | + ${{ env.url }} + ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }} + + release: + name: "Release" + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: [publish] + + steps: + - name: "Debug" + continue-on-error: true + run: | + echo "github.ref_name: ${{ github.ref_name }}" + echo "github.event.release.prerelease: ${{ github.event.release.prerelease }}" + + - name: "Debug event.json" + continue-on-error: true + run: cat "${GITHUB_EVENT_PATH}" + + - name: "Debug CTX github" + continue-on-error: true + env: + GITHUB_CTX: ${{ toJSON(github) }} + run: echo "$GITHUB_CTX" + + #- name: "Debug Environment" + # continue-on-error: true + # run: env + + #- name: "Update Latest Tag" + # if: ${{ !github.event.release.prerelease }} + # uses: cssnr/update-version-tags-action@latest + # continue-on-error: true + # with: + # major: false + # minor: false + # tags: latest + # #tag: ${{ github.ref_name }} + + - name: "Download Artifact" + uses: actions/download-artifact@v6 + with: + name: "release" + path: "dist" + + - name: "List Artifacts" + run: | + ls -lAh dist + ls dist | grep -- "-${{ github.ref_name }}-" > /dev/null + + - name: "Upload Release" + uses: cssnr/upload-release-action@latest + with: + globs: dist/* + + - name: "Update Release Notes Action" + continue-on-error: true + uses: smashedr/update-release-notes-action@master + with: + location: tail + + - name: "Send Failure Notification" + if: ${{ failure() }} + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + description: ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }} diff --git a/.gitignore b/.gitignore index 19197fa..08b9fdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,18 @@ +# Generic .idea/ *.iml .vscode/ -dist/ -_site/ +.venv/ venv/ -**/__pycache__/ +__pycache__/ *.egg-info/ +build/ +dist/ +*.log +*.pyc .coverage +coverage.xml +# Zensical +site/ +.cache/ +# App diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..5b4267c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,16 @@ +# IDE +.idea/ +.vscode/ + +# Build +dist/ +node_modules/ + +# Tools +.ruff_cache/ +.mypy_cache/ +.pytest_cache/ + +# Files +package-lock.json +*.html diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4541eb0 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,20 @@ +{ + "trailingComma": "es5", + "semi": false, + "singleQuote": true, + "printWidth": 90, + "overrides": [ + { + "files": ["**/*.html", "**/*.yaml", "**/*.yml"], + "options": { + "singleQuote": false + } + }, + { + "files": ["**/*.js", "**/*.mjs", "**/*.css", "**/*.scss"], + "options": { + "tabWidth": 4 + } + } + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ffe8abf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 CSSNR + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2901fc9..198deaf 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,61 @@ -[![Discord](https://img.shields.io/discord/899171661457293343?color=7289da&label=discord&logo=discord&logoColor=white&style=flat)](https://discord.gg/wXy6m2X8wY) +[![PyPI Version](https://img.shields.io/pypi/v/vultr-python?logo=pypi&logoColor=white&label=pypi)](https://pypi.org/project/vultr-python/) +[![TOML Python Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fcssnr%2Fvultr-python%2Frefs%2Fheads%2Fmaster%2Fpyproject.toml&query=%24.project.requires-python&logo=python&logoColor=white&label=python)](https://github.com/cssnr/vultr-python?tab=readme-ov-file#readme) +[![PyPI Downloads](https://img.shields.io/pypi/dm/vultr-python?logo=pypi&logoColor=white)](https://pypistats.org/packages/vultr-python) +[![Pepy Total Downloads](https://img.shields.io/pepy/dt/vultr-python?logo=pypi&logoColor=white&label=total)](https://clickpy.clickhouse.com/dashboard/vultr-python) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9b356c4327df41e395c81de1c717ce11)](https://app.codacy.com/gh/cssnr/vultr-python/dashboard) -[![PyPI](https://img.shields.io/pypi/v/vultr-python)](https://pypi.org/project/vultr-python/) -[![](https://repository-images.githubusercontent.com/441314848/513fb2f4-39cb-4bbc-8d47-a2cde9ccbd65)](https://www.vultr.com/?ref=6905748) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cssnr_vultr-python&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=cssnr_vultr-python) +[![Workflow Lint](https://img.shields.io/github/actions/workflow/status/cssnr/vultr-python/lint.yaml?logo=cachet&label=lint)](https://github.com/cssnr/vultr-python/actions/workflows/lint.yaml) +[![GitHub Deployments](https://img.shields.io/github/deployments/cssnr/vultr-python/github-pages?logo=materialformkdocs&logoColor=white&label=github-pages)](https://cssnr.github.io/vultr-python) +[![GitHub Last Commit](https://img.shields.io/github/last-commit/cssnr/vultr-python?logo=github&label=updated)](https://github.com/cssnr/vultr-python/graphs/commit-activity) +[![GitHub Repo Size](https://img.shields.io/github/repo-size/cssnr/vultr-python?logo=bookstack&logoColor=white&label=repo%20size)](https://github.com/cssnr/vultr-python) +[![GitHub Top Language](https://img.shields.io/github/languages/top/cssnr/vultr-python?logo=htmx&logoColor=white)](https://github.com/cssnr/vultr-python?tab=readme-ov-file#readme) +[![GitHub Contributors](https://img.shields.io/github/contributors-anon/cssnr/vultr-python?logo=github)](https://github.com/cssnr/vultr-python/graphs/contributors) +[![GitHub Discussions](https://img.shields.io/github/discussions/cssnr/vultr-python?logo=github)](https://github.com/cssnr/vultr-python/discussions) +[![GitHub Forks](https://img.shields.io/github/forks/cssnr/vultr-python?style=flat&logo=github)](https://github.com/cssnr/vultr-python/forks) +[![GitHub Repo Stars](https://img.shields.io/github/stars/cssnr/vultr-python?style=flat&logo=github)](https://github.com/cssnr/vultr-python/stargazers) +[![GitHub Org Stars](https://img.shields.io/github/stars/cssnr?style=flat&logo=github&label=org%20stars)](https://cssnr.github.io/) +[![Discord](https://img.shields.io/discord/899171661457293343?logo=discord&logoColor=white&label=discord&color=7289da)](https://discord.gg/wXy6m2X8wY) +[![Ko-fi](https://img.shields.io/badge/Ko--fi-72a5f2?logo=kofi&label=support)](https://ko-fi.com/cssnr) + # Vultr Python + +Vultr Python + +- [Install](#Install) +- [Usage](#Usage) +- [Support](#Support) +- [Contributing](#Contributing) + Python 3 wrapper for the Vultr API v2. -* Vultr: [https://www.vultr.com](https://www.vultr.com/?ref=6905748) -* Vultr API: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) -* Vultr Python Docs: [https://cssnr.github.io/vultr-python](https://cssnr.github.io/vultr-python) +[![GitHub](https://img.shields.io/badge/github-232925?style=for-the-badge&logo=github)](https://github.com/cssnr/vultr-python?tab=readme-ov-file#readme) +[![PyPi](https://img.shields.io/badge/pypi-006dad?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/vultr-python) +[![Docs](https://img.shields.io/badge/docs-198754?style=for-the-badge&logo=mdbook)](https://cssnr.github.io/vultr-python) +[![Vultr](https://img.shields.io/badge/vultr-007bfc?style=for-the-badge&logo=vultr)](https://www.vultr.com/api/?ref=6905748) + +Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) -This is currently a WIP and not complete, but has some useful functions. -Feel free to request additional functions and more on [Discord](https://discord.gg/wXy6m2X8wY). +> [!TIP] +> This project is not complete, but has many useful functions. +> Please submit a [Feature Request](https://github.com/cssnr/vultr-python/discussions/categories/feature-requests) +> or report any [Issues](https://github.com/cssnr/vultr-python/issues). + +For more details visit [www.vultr.com](https://www.vultr.com/?ref=6905748). ## Install -From PyPi using pip: +From PyPi: + ```text python -m pip install vultr-python ``` -From Source using setuptools: +From Source: + ```text git clone https://github.com/cssnr/vultr-python.git -cd vultr-python -python setup.py install +python -m pip install vultr-python ``` ## Usage @@ -32,31 +63,40 @@ python setup.py install You will need to create an api key and whitelist your IP address. Most functions do not work without an API Key. -* [https://my.vultr.com/settings/#settingsapi](https://my.vultr.com/settings/#settingsapi) +- [https://my.vultr.com/settings/#settingsapi](https://my.vultr.com/settings/#settingsapi) Initialize the class with your API Key or with the `VULTR_API_KEY` environment variable. + ```python from vultr import Vultr vultr = Vultr('VULTR_API_KEY') ``` + List plans and get available regions for that plan + ```python plans = vultr.list_plans() plan = plans[0] # 0 seems to be the basic 5 dollar plan regions = vultr.list_regions() available = vultr.filter_regions(regions, plan['locations']) ``` + Get the OS list and filter by name + ```python os_list = vultr.list_os() ubuntu_lts = vultr.filter_os(os_list, 'Ubuntu 20.04 x64') ``` + Create a new ssh key from key string + ```python sshkey = vultr.create_key('key-name', 'ssh-rsa AAAA...') ``` + Create a new instance + ```python hostname = 'my-new-host' data = { @@ -70,8 +110,31 @@ data = { instance = vultr.create_instance(**data) ``` -View all functions at the Doc site: [https://cssnr.github.io/vultr-python](https://cssnr.github.io/vultr-python) +Full Documentation: [https://cssnr.github.io/vultr-python](https://cssnr.github.io/vultr-python) + +Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) + +# Support + +For general help or to request a feature, see: + +- Q&A Discussion: +- Request a Feature: +- Chat with us on Discord: + +If you are experiencing an issue/bug or getting unexpected results, you can: + +- Report an Issue: +- Provide General Feedback: [https://cssnr.github.io/feedback/](https://cssnr.github.io/feedback/?app=vultr-python) +- Chat with us on Discord: + +# Contributing + +If you would like to submit a PR, please review the [CONTRIBUTING.md](#contributing-ov-file). + +Please consider making a donation to support the development of this project +and [additional](https://cssnr.com/) open source projects. -View the full API documentation at Vultr: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) +[![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/cssnr) -If you have more questions, concerns, or comments? Join our [Discord](https://discord.gg/wXy6m2X8wY) for more information... +For a full list of current projects visit: [https://cssnr.github.io/](https://cssnr.github.io/) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..c27277d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,93 @@ +[![PyPI Version](https://img.shields.io/pypi/v/vultr-python?logo=pypi&logoColor=white&label=pypi)](https://pypi.org/project/vultr-python/) +[![PyPI Downloads](https://img.shields.io/pypi/dm/vultr-python?logo=pypi&logoColor=white)](https://pypistats.org/packages/vultr-python) +[![Pepy Total Downloads](https://img.shields.io/pepy/dt/vultr-python?logo=pypi&logoColor=white&label=total)](https://clickpy.clickhouse.com/dashboard/vultr-python) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9b356c4327df41e395c81de1c717ce11)](https://app.codacy.com/gh/cssnr/vultr-python/dashboard) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cssnr_vultr-python&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=cssnr_vultr-python) +[![GitHub Contributors](https://img.shields.io/github/contributors-anon/cssnr/vultr-python?logo=github)](https://github.com/cssnr/vultr-python/graphs/contributors) +[![GitHub Discussions](https://img.shields.io/github/discussions/cssnr/vultr-python?logo=github)](https://github.com/cssnr/vultr-python/discussions) +[![GitHub Forks](https://img.shields.io/github/forks/cssnr/vultr-python?style=flat&logo=github)](https://github.com/cssnr/vultr-python/forks) +[![GitHub Repo Stars](https://img.shields.io/github/stars/cssnr/vultr-python?style=flat&logo=github)](https://github.com/cssnr/vultr-python/stargazers) +[![GitHub Org Stars](https://img.shields.io/github/stars/cssnr?style=flat&logo=github&label=org%20stars)](https://cssnr.github.io/) +[![Discord](https://img.shields.io/discord/899171661457293343?logo=discord&logoColor=white&label=discord&color=7289da)](https://discord.gg/wXy6m2X8wY) +[![Ko-fi](https://img.shields.io/badge/Ko--fi-72a5f2?logo=kofi&label=support)](https://ko-fi.com/cssnr) + +Python 3 wrapper for the [Vultr](https://www.vultr.com/?ref=6905748) API v2. + +[![GitHub](https://img.shields.io/badge/github-232925?style=for-the-badge&logo=github)](https://github.com/cssnr/vultr-python?tab=readme-ov-file#readme) +[![PyPi](https://img.shields.io/badge/pypi-006dad?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/vultr-python) +[![Docs](https://img.shields.io/badge/docs-198754?style=for-the-badge&logo=mdbook)](#Vultr) +[![Vultr](https://img.shields.io/badge/vultr-007bfc?style=for-the-badge&logo=vultr)](https://www.vultr.com/api/?ref=6905748) + +[⬇️ Jump to the API Documentation](#Vultr) + +## Install + +From PyPi: + +```text +python -m pip install vultr-python +``` + +From Source: + +```text +git clone https://github.com/cssnr/vultr-python.git +python -m pip install vultr-python +``` + +## Usage + +You will need to create an api key and whitelist your IP address. Most functions do not work without an API Key. + +- [https://my.vultr.com/settings/#settingsapi](https://my.vultr.com/settings/#settingsapi) + +Initialize the class with your API Key or with the `VULTR_API_KEY` environment variable. + +```python +from vultr import Vultr + +vultr = Vultr('VULTR_API_KEY') +``` + +List plans and get available regions for that plan + +```python +plans = vultr.list_plans() +plan = plans[0] # 0 seems to be the basic 5 dollar plan +regions = vultr.list_regions() +available = vultr.filter_regions(regions, plan['locations']) +``` + +Get the OS list and filter by name + +```python +os_list = vultr.list_os() +ubuntu_lts = vultr.filter_os(os_list, 'Ubuntu 20.04 x64') +``` + +Create a new ssh key from key string + +```python +sshkey = vultr.create_key('key-name', 'ssh-rsa AAAA...') +``` + +Create a new instance + +```python +hostname = 'my-new-host' +data = { + 'region': available[0]['id'], + 'plan': plan['id'], + 'os_id': ubuntu_lts['id'], + 'sshkey_id': [sshkey['id']], + 'hostname': hostname, + 'label': hostname, +} +instance = vultr.create_instance(**data) +``` + +  + +Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) + +--- diff --git a/docs/module.html.jinja2 b/docs/module.html.jinja2 new file mode 100644 index 0000000..229f2a8 --- /dev/null +++ b/docs/module.html.jinja2 @@ -0,0 +1,2 @@ +{% extends "default/module.html.jinja2" %} +{% block title %}Vultr Python{% endblock %} diff --git a/docs/syntax-highlighting.css b/docs/syntax-highlighting.css new file mode 100644 index 0000000..e5ec354 --- /dev/null +++ b/docs/syntax-highlighting.css @@ -0,0 +1,246 @@ +/* monokai color scheme, see pdoc/template/README.md */ +pre { + line-height: 125%; +} +span.linenos { + color: inherit; + background-color: transparent; + padding-left: 5px; + padding-right: 20px; +} +.pdoc-code .hll { + background-color: #49483e; +} +.pdoc-code { + background: #272822; + color: #f8f8f2; +} +.pdoc-code .c { + color: #75715e; +} /* Comment */ +.pdoc-code .err { + color: #960050; + background-color: #1e0010; +} /* Error */ +.pdoc-code .esc { + color: #f8f8f2; +} /* Escape */ +.pdoc-code .g { + color: #f8f8f2; +} /* Generic */ +.pdoc-code .k { + color: #66d9ef; +} /* Keyword */ +.pdoc-code .l { + color: #ae81ff; +} /* Literal */ +.pdoc-code .n { + color: #f8f8f2; +} /* Name */ +.pdoc-code .o { + color: #f92672; +} /* Operator */ +.pdoc-code .x { + color: #f8f8f2; +} /* Other */ +.pdoc-code .p { + color: #f8f8f2; +} /* Punctuation */ +.pdoc-code .ch { + color: #75715e; +} /* Comment.Hashbang */ +.pdoc-code .cm { + color: #75715e; +} /* Comment.Multiline */ +.pdoc-code .cp { + color: #75715e; +} /* Comment.Preproc */ +.pdoc-code .cpf { + color: #75715e; +} /* Comment.PreprocFile */ +.pdoc-code .c1 { + color: #75715e; +} /* Comment.Single */ +.pdoc-code .cs { + color: #75715e; +} /* Comment.Special */ +.pdoc-code .gd { + color: #f92672; +} /* Generic.Deleted */ +.pdoc-code .ge { + color: #f8f8f2; + font-style: italic; +} /* Generic.Emph */ +.pdoc-code .gr { + color: #f8f8f2; +} /* Generic.Error */ +.pdoc-code .gh { + color: #f8f8f2; +} /* Generic.Heading */ +.pdoc-code .gi { + color: #a6e22e; +} /* Generic.Inserted */ +.pdoc-code .go { + color: #66d9ef; +} /* Generic.Output */ +.pdoc-code .gp { + color: #f92672; + font-weight: bold; +} /* Generic.Prompt */ +.pdoc-code .gs { + color: #f8f8f2; + font-weight: bold; +} /* Generic.Strong */ +.pdoc-code .gu { + color: #75715e; +} /* Generic.Subheading */ +.pdoc-code .gt { + color: #f8f8f2; +} /* Generic.Traceback */ +.pdoc-code .kc { + color: #66d9ef; +} /* Keyword.Constant */ +.pdoc-code .kd { + color: #66d9ef; +} /* Keyword.Declaration */ +.pdoc-code .kn { + color: #f92672; +} /* Keyword.Namespace */ +.pdoc-code .kp { + color: #66d9ef; +} /* Keyword.Pseudo */ +.pdoc-code .kr { + color: #66d9ef; +} /* Keyword.Reserved */ +.pdoc-code .kt { + color: #66d9ef; +} /* Keyword.Type */ +.pdoc-code .ld { + color: #e6db74; +} /* Literal.Date */ +.pdoc-code .m { + color: #ae81ff; +} /* Literal.Number */ +.pdoc-code .s { + color: #e6db74; +} /* Literal.String */ +.pdoc-code .na { + color: #a6e22e; +} /* Name.Attribute */ +.pdoc-code .nb { + color: #f8f8f2; +} /* Name.Builtin */ +.pdoc-code .nc { + color: #a6e22e; +} /* Name.Class */ +.pdoc-code .no { + color: #66d9ef; +} /* Name.Constant */ +.pdoc-code .nd { + color: #a6e22e; +} /* Name.Decorator */ +.pdoc-code .ni { + color: #f8f8f2; +} /* Name.Entity */ +.pdoc-code .ne { + color: #a6e22e; +} /* Name.Exception */ +.pdoc-code .nf { + color: #a6e22e; +} /* Name.Function */ +.pdoc-code .nl { + color: #f8f8f2; +} /* Name.Label */ +.pdoc-code .nn { + color: #f8f8f2; +} /* Name.Namespace */ +.pdoc-code .nx { + color: #a6e22e; +} /* Name.Other */ +.pdoc-code .py { + color: #f8f8f2; +} /* Name.Property */ +.pdoc-code .nt { + color: #f92672; +} /* Name.Tag */ +.pdoc-code .nv { + color: #f8f8f2; +} /* Name.Variable */ +.pdoc-code .ow { + color: #f92672; +} /* Operator.Word */ +.pdoc-code .w { + color: #f8f8f2; +} /* Text.Whitespace */ +.pdoc-code .mb { + color: #ae81ff; +} /* Literal.Number.Bin */ +.pdoc-code .mf { + color: #ae81ff; +} /* Literal.Number.Float */ +.pdoc-code .mh { + color: #ae81ff; +} /* Literal.Number.Hex */ +.pdoc-code .mi { + color: #ae81ff; +} /* Literal.Number.Integer */ +.pdoc-code .mo { + color: #ae81ff; +} /* Literal.Number.Oct */ +.pdoc-code .sa { + color: #e6db74; +} /* Literal.String.Affix */ +.pdoc-code .sb { + color: #e6db74; +} /* Literal.String.Backtick */ +.pdoc-code .sc { + color: #e6db74; +} /* Literal.String.Char */ +.pdoc-code .dl { + color: #e6db74; +} /* Literal.String.Delimiter */ +.pdoc-code .sd { + color: #e6db74; +} /* Literal.String.Doc */ +.pdoc-code .s2 { + color: #e6db74; +} /* Literal.String.Double */ +.pdoc-code .se { + color: #ae81ff; +} /* Literal.String.Escape */ +.pdoc-code .sh { + color: #e6db74; +} /* Literal.String.Heredoc */ +.pdoc-code .si { + color: #e6db74; +} /* Literal.String.Interpol */ +.pdoc-code .sx { + color: #e6db74; +} /* Literal.String.Other */ +.pdoc-code .sr { + color: #e6db74; +} /* Literal.String.Regex */ +.pdoc-code .s1 { + color: #e6db74; +} /* Literal.String.Single */ +.pdoc-code .ss { + color: #e6db74; +} /* Literal.String.Symbol */ +.pdoc-code .bp { + color: #f8f8f2; +} /* Name.Builtin.Pseudo */ +.pdoc-code .fm { + color: #a6e22e; +} /* Name.Function.Magic */ +.pdoc-code .vc { + color: #f8f8f2; +} /* Name.Variable.Class */ +.pdoc-code .vg { + color: #f8f8f2; +} /* Name.Variable.Global */ +.pdoc-code .vi { + color: #f8f8f2; +} /* Name.Variable.Instance */ +.pdoc-code .vm { + color: #f8f8f2; +} /* Name.Variable.Magic */ diff --git a/docs/theme.css b/docs/theme.css new file mode 100644 index 0000000..60b447a --- /dev/null +++ b/docs/theme.css @@ -0,0 +1,20 @@ +:root { + --pdoc-background: #212529; +} + +.pdoc { + --text: #f7f7f7; + --muted: #9d9d9d; + --link: #58a6ff; + --link-hover: #3989ff; + --code: #333; + --active: #555; + + --accent: #343434; + --accent2: #555; + + --nav-hover: rgba(0, 0, 0, 0.1); + --name: #77c1ff; + --def: #0cdd0c; + --annotation: #00c037; +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..052de90 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,104 @@ +# Project +[project] +name = "vultr-python" +description = "Python 3 wrapper for the Vultr API v2.0" +authors = [{ name="Shane" }] +readme = "README.md" +dynamic = ["version"] +requires-python = ">=3.6" +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.06", + "Programming Language :: Python :: 3.07", + "Programming Language :: Python :: 3.08", + "Programming Language :: Python :: 3.09", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Operating System :: OS Independent", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Natural Language :: English", +] +license = { text = "MIT" } +#license = { file = "LICENSE" } + +dependencies = ["requests"] + +[dependency-groups] +dev = [ + "bandit", + "black", + "build", + "coverage", + "isort", + "mypy", + "requests", + "ruff", + "setuptools", + "ty", + "types-requests", +] + +[project.urls] +Homepage = "https://cssnr.com/" +Documentation = "https://cssnr.github.io/vultr-python" +Source = "https://github.com/cssnr/vultr-python" +Issues = "https://github.com/cssnr/vultr-python/issues" +Funding = "https://ko-fi.com/cssnr" + +# Setup Tools +#[tool.setuptools.package-data] +#vultr = ["py.typed"] +[tool.setuptools.dynamic] +version = { attr = "vultr.version.__version__" } + +# Build System +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +# Bandit +# https://bandit.readthedocs.io/en/latest/config.html +[tool.bandit] +exclude_dirs = ["venv"] +skips = ["B105", "B107"] + +# Black +# https://black.readthedocs.io/en/stable/usage_and_configuration/ +[tool.black] +line-length = 119 +extend-exclude = '(\.github)' + +# Coverage +# https://coverage.readthedocs.io/en/latest/ +[tool.coverage.run] +omit = ["*.egg-info/*", ".github/*"] +source = ["src"] + +# Isort +# https://pycqa.github.io/isort/docs/configuration/options.html +[tool.isort] +profile = "black" +lines_after_imports = 2 +src_paths = ["src", "test"] +skip = ["venv", ".github"] + +# Mypy +# https://mypy.readthedocs.io/en/stable/config_file.html +[tool.mypy] +#ignore_missing_imports = true +exclude = ["venv"] + +# Ruff +# https://docs.astral.sh/ruff/configuration/ +[tool.ruff] +line-length = 119 +target-version = "py313" +extend-exclude = [".github"] + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F", "B", "Q"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f229360..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8fbaec7..0000000 --- a/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[flake8] -exclude = venv,*migrations* -max-line-length = 119 - -[coverage:run] -omit = *venv*,*migrations* diff --git a/setup.py b/setup.py deleted file mode 100644 index 2d26b5b..0000000 --- a/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -from setuptools import setup - - -with open(os.path.join(os.path.dirname(__file__), 'README.md'), 'r') as f: - long_description = f.read() - -setup( - name='vultr-python', - version='0.1.5', - description='Python 3 wrapper for the Vultr API v2.0', - long_description=long_description, - long_description_content_type="text/markdown", - url='https://github.com/cssnr/vultr-python', - author='Shane', - author_email='shane@sapps.me', - py_modules=['vultr'], - install_requires=['requests'], - python_requires='>=3.5', - include_package_data=True, - zip_safe=False, - platforms='any', - project_urls={ - 'Documentation': 'https://cssnr.github.io/vultr-python', - 'Source': 'https://github.com/cssnr/vultr-python', - }, - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3 :: Only', - ], -) diff --git a/src/vultr/__init__.py b/src/vultr/__init__.py new file mode 100644 index 0000000..6cc4cfb --- /dev/null +++ b/src/vultr/__init__.py @@ -0,0 +1,8 @@ +""" +.. include:: ../../docs/index.md +""" + +from .vultr import Vultr + + +__all__ = ["Vultr"] diff --git a/src/vultr/version.py b/src/vultr/version.py new file mode 100644 index 0000000..bf9d384 --- /dev/null +++ b/src/vultr/version.py @@ -0,0 +1,12 @@ +import os +import re + + +def get_version() -> str: + version = os.environ.get("GITHUB_REF_NAME", "0.0.1") + pattern = r"^\d+\.\d+\.\d+(?:[abc]\d*)?$" + match = re.match(pattern, version) + return version if match else "0.0.1" + + +__version__ = get_version() diff --git a/vultr.py b/src/vultr/vultr.py similarity index 59% rename from vultr.py rename to src/vultr/vultr.py index 108a745..96f0b50 100644 --- a/vultr.py +++ b/src/vultr/vultr.py @@ -1,146 +1,147 @@ import os -import requests from typing import Optional, Union +import requests + class Vultr(object): - url = 'https://api.vultr.com/v2' + url = "https://api.vultr.com/v2" def __init__(self, api_key: Optional[str] = None): """ :param str api_key: Vultr API Key or VULTR_API_KEY environment variable """ - self.api_key = api_key or os.getenv('VULTR_API_KEY') + self.api_key = api_key or os.getenv("VULTR_API_KEY") self.s = requests.session() if self.api_key: - self.s.headers.update({'Authorization': f'Bearer {self.api_key}'}) + self.s.headers.update({"Authorization": f"Bearer {self.api_key}"}) def list_os(self): - url = f'{self.url}/os' - return self._get(url)['os'] + url = f"{self.url}/os" + return self._get(url)["os"] def list_plans(self): - url = f'{self.url}/plans' - return self._get(url)['plans'] + url = f"{self.url}/plans" + return self._get(url)["plans"] def list_regions(self): - url = f'{self.url}/regions' - return self._get(url)['regions'] + url = f"{self.url}/regions" + return self._get(url)["regions"] def list_instances(self): - url = f'{self.url}/instances' - return self._get(url)['instances'] + url = f"{self.url}/instances" + return self._get(url)["instances"] def get_instance(self, instance: Union[str, dict]): instance_id = self._get_obj_key(instance) - url = f'{self.url}/instances/{instance_id}' - return self._get(url)['instance'] + url = f"{self.url}/instances/{instance_id}" + return self._get(url)["instance"] def create_instance(self, region: str, plan: str, **kwargs): - data = {'region': region, 'plan': plan} + data = {"region": region, "plan": plan} data.update(kwargs) - url = f'{self.url}/instances' - return self._post(url, data)['instance'] + url = f"{self.url}/instances" + return self._post(url, data)["instance"] def update_instance(self, instance: Union[str, dict], **kwargs): instance_id = self._get_obj_key(instance) - url = f'{self.url}/instances/{instance_id}' - return self._patch(url, kwargs)['instance'] + url = f"{self.url}/instances/{instance_id}" + return self._patch(url, kwargs)["instance"] def delete_instance(self, instance: Union[str, dict]): instance_id = self._get_obj_key(instance) - url = f'{self.url}/instances/{instance_id}' + url = f"{self.url}/instances/{instance_id}" return self._delete(url) def list_keys(self): - url = f'{self.url}/ssh-keys' - return self._get(url)['ssh_keys'] + url = f"{self.url}/ssh-keys" + return self._get(url)["ssh_keys"] def get_key(self, key: Union[str, dict]): key_id = self._get_obj_key(key) - url = f'{self.url}/ssh-keys/{key_id}' - return self._get(url)['ssh_key'] + url = f"{self.url}/ssh-keys/{key_id}" + return self._get(url)["ssh_key"] def create_key(self, name: str, key: str, **kwargs): - data = {'name': name, 'ssh_key': key} + data = {"name": name, "ssh_key": key} data.update(kwargs) - url = f'{self.url}/ssh-keys' - return self._post(url, data)['ssh_key'] + url = f"{self.url}/ssh-keys" + return self._post(url, data)["ssh_key"] def update_key(self, key: Union[str, dict], **kwargs): key_id = self._get_obj_key(key) - url = f'{self.url}/ssh-keys/{key_id}' - return self._patch(url, kwargs)['ssh_key'] + url = f"{self.url}/ssh-keys/{key_id}" + return self._patch(url, kwargs)["ssh_key"] def delete_key(self, key: Union[str, dict]): key_id = self._get_obj_key(key) - url = f'{self.url}/ssh-keys/{key_id}' + url = f"{self.url}/ssh-keys/{key_id}" return self._delete(url) def list_scripts(self): - url = f'{self.url}/startup-scripts' - return self._get(url)['startup_scripts'] + url = f"{self.url}/startup-scripts" + return self._get(url)["startup_scripts"] def get_script(self, script: Union[str, dict]): script_id = self._get_obj_key(script) - url = f'{self.url}/startup-scripts/{script_id}' - return self._get(url)['startup_script'] + url = f"{self.url}/startup-scripts/{script_id}" + return self._get(url)["startup_script"] def create_script(self, name: str, script: str, **kwargs): - data = {'name': name, 'script': script} + data = {"name": name, "script": script} data.update(kwargs) - url = f'{self.url}/startup-scripts' - return self._post(url, data)['startup_script'] + url = f"{self.url}/startup-scripts" + return self._post(url, data)["startup_script"] def update_script(self, script: Union[str, dict], **kwargs): script_id = self._get_obj_key(script) - url = f'{self.url}/startup-scripts/{script_id}' - return self._patch(url, kwargs)['startup_script'] + url = f"{self.url}/startup-scripts/{script_id}" + return self._patch(url, kwargs)["startup_script"] def delete_script(self, script: Union[str, dict]): script_id = self._get_obj_key(script) - url = f'{self.url}/startup-scripts/{script_id}' + url = f"{self.url}/startup-scripts/{script_id}" return self._delete(url) def list_ipv4(self, instance: Union[str, dict]): instance_id = self._get_obj_key(instance) - url = f'{self.url}/instances/{instance_id}/ipv4' - return self._get(url)['ipv4s'] + url = f"{self.url}/instances/{instance_id}/ipv4" + return self._get(url)["ipv4s"] def create_ipv4(self, instance: Union[str, dict], **kwargs): instance_id = self._get_obj_key(instance) - url = f'{self.url}/instances/{instance_id}/ipv4' - return self._post(url, kwargs)['ipv4'] + url = f"{self.url}/instances/{instance_id}/ipv4" + return self._post(url, kwargs)["ipv4"] def delete_ipv4(self, instance: Union[str, dict]): instance_id = self._get_obj_key(instance) - url = f'{self.url}/instances/{instance_id}/ipv4' + url = f"{self.url}/instances/{instance_id}/ipv4" return self._delete(url) @staticmethod def filter_keys(keys: list, name: str) -> dict: try: - return next(d for d in keys if d['name'].lower() == name.lower()) + return next(d for d in keys if d["name"].lower() == name.lower()) except StopIteration: return {} @staticmethod def filter_os(os_list: list, name: str) -> dict: try: - return next(d for d in os_list if d['name'].lower() == name.lower()) + return next(d for d in os_list if d["name"].lower() == name.lower()) except StopIteration: return {} @staticmethod def filter_scripts(scripts: list, name: str) -> dict: try: - return next(d for d in scripts if d['name'].lower() == name.lower()) + return next(d for d in scripts if d["name"].lower() == name.lower()) except StopIteration: return {} @staticmethod def filter_regions(regions: list, locations: list) -> list: - return [d for d in regions if d['id'] in locations] + return [d for d in regions if d["id"] in locations] def _get(self, url): r = self.s.get(url, timeout=10) @@ -167,7 +168,7 @@ def _delete(self, url): return None @staticmethod - def _get_obj_key(obj, key='id'): + def _get_obj_key(obj, key="id"): if isinstance(obj, str): return obj elif isinstance(obj, int): @@ -176,4 +177,4 @@ def _get_obj_key(obj, key='id'): if key in obj: return obj[key] else: - raise ValueError(f'Unable to parse object: {key}') + raise ValueError(f"Unable to parse object: {key}")