From b060028974cf9532e5647118bf9b11057f783b63 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:15:27 -0800 Subject: [PATCH 01/24] Add Arbitrary Methods --- README.md | 13 ++++++++++++- build.ps1 | 48 ++++++++++++++++++++++++++++++++++++++++++++++ docs.ps1 | 38 ++++++++++++++++++++++++++++++++++++ docs/index.md | 11 +++++++++++ src/vultr/py.typed | 0 src/vultr/vultr.py | 26 ++++++++++++++++++------- 6 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 build.ps1 create mode 100644 docs.ps1 create mode 100644 src/vultr/py.typed diff --git a/README.md b/README.md index 06c1747..4a7493d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ # Vultr Python -Vultr Python +Vultr Python - [Install](#Install) - [Usage](#Usage) @@ -110,6 +110,17 @@ data = { instance = vultr.create_instance(**data) ``` +Arbitrary Methods `get`, `post`, `patch`, `delete` + +```python +# vultr.list_instances() +instances = vultr.get('instances') +# vultr.create_key() +sshkey = vultr.post('ssh-keys', {"name": 'key-name', "ssh_key": 'ssh-rsa AAAA...'}) +# vultr.delete_instance() +vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") +``` + 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) diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..5db65bf --- /dev/null +++ b/build.ps1 @@ -0,0 +1,48 @@ +param ( + [switch]$c, + [switch]$i, + [switch]$u +) + +$ErrorActionPreference = "Stop" + +write-output "Clean: $c" +write-output "Install: $i" +write-output "Uninstall: $u" + +if ($u) { + Write-Host -ForegroundColor Red "Uninstalling..." + python -m pip uninstall -y vultr-python +} + +$egg_dir = ".\src\*.egg-info" +if (Test-Path $egg_dir) { + Write-Host -ForegroundColor Cyan "Removing: $egg_dir" + Remove-Item -Force -Recurse $egg_dir +} +$cache_dir = ".\src\*\__pycache__" +if (Test-Path $cache_dir) { + Write-Host -ForegroundColor Cyan "Removing: $cache_dir" + Remove-Item -Force -Recurse $cache_dir +} +if (Test-Path ".\dist") { + Write-Host -ForegroundColor Cyan "Removing: .\dist" + Remove-Item -Force -Recurse ".\dist" +} +if (Test-Path ".\build") { + Write-Host -ForegroundColor Cyan "Removing: .\build" + Remove-Item -Force -Recurse ".\build" +} +if ($c) { + Write-Host -ForegroundColor Yellow "Clean Only. Not Building or Installing!" + exit +} + +python -m build + +if ($args[0] -eq "i") { + Write-Host -ForegroundColor Green "Installing..." + python -m pip install .\dist\vultr_python-0.0.1-py3-none-any.whl +} + +Write-Output "Success." diff --git a/docs.ps1 b/docs.ps1 new file mode 100644 index 0000000..4d0d955 --- /dev/null +++ b/docs.ps1 @@ -0,0 +1,38 @@ +param ( + [switch]$c, + [switch]$b +) + +$ErrorActionPreference = "Stop" + +write-output "Clean: $c" +write-output "Build: $b" + +if ($c) { + Write-Host -ForegroundColor Yellow "Cleaning Docs..." + $site_dir = ".\site" + if (Test-Path $site_dir) { + Write-Host -ForegroundColor Cyan "Removing: $site_dir" + Remove-Item -Force -Recurse $site_dir + } + $cache_dir = ".\.cache" + if (Test-Path $cache_dir) { + Write-Host -ForegroundColor Cyan "Removing: $cache_dir" + Remove-Item -Force -Recurse $cache_dir + } +} + +python -m pdoc -t .\docs\ -p 8008 ` + --favicon "https://df.cssnr.com/raw/logo128.png" ` + --logo "https://df.cssnr.com/raw/logo128.png" ` + --logo-link "https://github.com/cssnr/vultr-python" ` + vultr + +#-e "vultr=https://github.com/cssnr/vultr-python/blob/updates/src/vultr/" ` + +#if ($b) { +# Write-Host -ForegroundColor Green "Building Docs..." +# zensical build +#} else { +# zensical serve +#} diff --git a/docs/index.md b/docs/index.md index 2cab29c..886ed4f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -88,6 +88,17 @@ data = { instance = vultr.create_instance(**data) ``` +Arbitrary Methods `get`, `post`, `patch`, `delete` + +```python +# vultr.list_instances() +instances = vultr.get('instances') +# vultr.create_key() +sshkey = vultr.post('ssh-keys', {"name": 'key-name', "ssh_key": 'ssh-rsa AAAA...'}) +# vultr.delete_instance() +vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") +``` +   Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) diff --git a/src/vultr/py.typed b/src/vultr/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/vultr/vultr.py b/src/vultr/vultr.py index 96f0b50..f62675a 100644 --- a/src/vultr/vultr.py +++ b/src/vultr/vultr.py @@ -9,12 +9,24 @@ class Vultr(object): def __init__(self, api_key: Optional[str] = None): """ - :param str api_key: Vultr API Key or VULTR_API_KEY environment variable + :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.s = requests.session() + self._session = requests.session() if self.api_key: - self.s.headers.update({"Authorization": f"Bearer {self.api_key}"}) + self._session.headers.update({"Authorization": f"Bearer {self.api_key}"}) + + def get(self, url: str): + return self._get(f"{self.url}/{url.lstrip('/')}") + + def post(self, url: str, **kwargs): + return self._post(f"{self.url}/{url.lstrip('/')}", kwargs) + + def patch(self, url: str, **kwargs): + return self._patch(f"{self.url}/{url.lstrip('/')}", kwargs) + + def delete(self, url: str): + return self._delete(f"{self.url}/{url.lstrip('/')}") def list_os(self): url = f"{self.url}/os" @@ -144,25 +156,25 @@ def filter_regions(regions: list, locations: list) -> list: return [d for d in regions if d["id"] in locations] def _get(self, url): - r = self.s.get(url, timeout=10) + r = self._session.get(url, timeout=10) if not r.ok: r.raise_for_status() return r.json() def _post(self, url, data): - r = self.s.post(url, json=data, timeout=10) + r = self._session.post(url, json=data, timeout=10) if not r.ok: r.raise_for_status() return r.json() def _patch(self, url, data): - r = self.s.patch(url, json=data, timeout=10) + r = self._session.patch(url, json=data, timeout=10) if not r.ok: r.raise_for_status() return r.json() def _delete(self, url): - r = self.s.delete(url, timeout=10) + r = self._session.delete(url, timeout=10) if not r.ok: r.raise_for_status() return None From 659abf3fed1bca3c4dd110c8250d6cf7a01a15f6 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:26:06 -0800 Subject: [PATCH 02/24] Add Workflow Permissions --- .github/workflows/build.yaml | 3 +++ .github/workflows/docs.yaml | 3 +++ .github/workflows/release.yaml | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0371331..0780dc7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,6 +18,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 + permissions: + contents: read + steps: - name: "Checkout" uses: actions/checkout@v6 diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 21272c5..3e5aa85 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -22,6 +22,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 + permissions: + contents: read + steps: - name: "Checkout" uses: actions/checkout@v5 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6c917d7..4b6e6b9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,6 +16,9 @@ jobs: with: name: "release" + permissions: + contents: read + publish: name: "Publish" runs-on: ubuntu-latest @@ -107,6 +110,9 @@ jobs: timeout-minutes: 5 needs: [publish] + permissions: + contents: write + steps: - name: "Debug" continue-on-error: true From 08371e82ccafd9dcd6945303be66585ce5a3e6f6 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:38:53 -0800 Subject: [PATCH 03/24] Update Docs --- README.md | 2 +- docs/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4a7493d..880e8cb 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Arbitrary Methods `get`, `post`, `patch`, `delete` # vultr.list_instances() instances = vultr.get('instances') # vultr.create_key() -sshkey = vultr.post('ssh-keys', {"name": 'key-name', "ssh_key": 'ssh-rsa AAAA...'}) +sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') # vultr.delete_instance() vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` diff --git a/docs/index.md b/docs/index.md index 886ed4f..0b12470 100644 --- a/docs/index.md +++ b/docs/index.md @@ -94,7 +94,7 @@ Arbitrary Methods `get`, `post`, `patch`, `delete` # vultr.list_instances() instances = vultr.get('instances') # vultr.create_key() -sshkey = vultr.post('ssh-keys', {"name": 'key-name', "ssh_key": 'ssh-rsa AAAA...'}) +sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') # vultr.delete_instance() vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` From a7779aff15a7a5fb46347319e8644a9488570e87 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:29:28 -0800 Subject: [PATCH 04/24] Update Docs --- README.md | 2 ++ docs/index.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 880e8cb..ea67536 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,8 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` +These methods take the URI as the first argument, and data kwargs as the second. + 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) diff --git a/docs/index.md b/docs/index.md index 0b12470..91b1dd9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -99,6 +99,8 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` +These methods take the URI as the first argument, and data kwargs as the second. +   Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) From 7b2b1a74ded3b650cb56110855fda03c0ec2db9e Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:08:29 -0800 Subject: [PATCH 05/24] Add Query Params and Basic Functionality Test --- .github/workflows/test.yaml | 86 +++++++++++++++++++++++++++++++++++++ README.md | 21 ++++----- docs/index.md | 21 ++++----- src/vultr/vultr.py | 54 ++++++++++++----------- tests/test_all.py | 10 +++++ 5 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/test.yaml create mode 100644 tests/test_all.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..26b4f8e --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,86 @@ +name: "Test" + +on: + workflow_dispatch: + push: + branches: [master] + #paths: &paths + paths: + - ".github/workflows/test.yaml" + - "src/**" + - "tests/**" + - "pyproject.toml" + #pull_request: + # paths: *paths + +jobs: + build: + name: "Build" + if: ${{ !contains(github.event.head_commit.message, '#notest') }} + uses: ./.github/workflows/build.yaml + with: + name: test + + permissions: + contents: read + + test: + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: [build] + strategy: + fail-fast: false + matrix: + version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + name: "Test ${{ matrix.version }}" + + permissions: + contents: read + + steps: + - name: "Checkout" + uses: actions/checkout@v6 + + - name: "Debug event.json" + if: ${{ !github.event.act }} + continue-on-error: true + run: cat "${GITHUB_EVENT_PATH}" + + - name: "Debug CTX github" + if: ${{ !github.event.act }} + continue-on-error: true + env: + GITHUB_CTX: ${{ toJSON(github) }} + run: echo "$GITHUB_CTX" + + - name: "Debug Environment" + if: ${{ !github.event.act }} + continue-on-error: true + run: env + + - name: "Download Artifact" + uses: actions/download-artifact@v6 + with: + name: test + path: dist + + - name: "Setup Python ${{ matrix.version }}" + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.version }} + cache: "pip" + + - name: "Install ${{ matrix.version }}" + run: | + python -V + python -m pip install -U pip pytest + python -m pip install dist/*.whl + + #- name: "Debug ${{ matrix.version }}" + # run: | + # ls -lAhR dist/ + + - name: "Test ${{ matrix.version }}" + #continue-on-error: ${{ contains('dev', matrix.version) }} + run: | + pytest -s diff --git a/README.md b/README.md index ea67536..945d0c8 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ 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 +plans = vultr.list_plans({'type': 'vc2'}) # vc2 - Cloud Compute +plan = plans[0] # 0 seems to be the base plan regions = vultr.list_regions() available = vultr.filter_regions(regions, plan['locations']) ``` @@ -98,26 +98,23 @@ 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, + 'hostname': 'my-new-host', + 'label': 'my-new-host', } -instance = vultr.create_instance(**data) +instance = vultr.create_instance(available[0], plan, **data) ``` Arbitrary Methods `get`, `post`, `patch`, `delete` ```python -# vultr.list_instances() -instances = vultr.get('instances') -# vultr.create_key() +# vultr.get('url', params) +instances = vultr.get('instances', {'type': 'vc2'}) +# vultr.post('url', **kwargs) sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') -# vultr.delete_instance() +# vultr.delete('url') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` diff --git a/docs/index.md b/docs/index.md index 91b1dd9..627a3c2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,8 +54,8 @@ 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 +plans = vultr.list_plans({'type': 'vc2'}) # vc2 - Cloud Compute +plan = plans[0] # 0 seems to be the base plan regions = vultr.list_regions() available = vultr.filter_regions(regions, plan['locations']) ``` @@ -76,26 +76,23 @@ 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, + 'hostname': 'my-new-host', + 'label': 'my-new-host', } -instance = vultr.create_instance(**data) +instance = vultr.create_instance(available[0], plan, **data) ``` Arbitrary Methods `get`, `post`, `patch`, `delete` ```python -# vultr.list_instances() -instances = vultr.get('instances') -# vultr.create_key() +# vultr.get('url', params) +instances = vultr.get('instances', {'type': 'vc2'}) +# vultr.post('url', **kwargs) sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') -# vultr.delete_instance() +# vultr.delete('url') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` diff --git a/src/vultr/vultr.py b/src/vultr/vultr.py index f62675a..76bc79a 100644 --- a/src/vultr/vultr.py +++ b/src/vultr/vultr.py @@ -16,8 +16,8 @@ def __init__(self, api_key: Optional[str] = None): if self.api_key: self._session.headers.update({"Authorization": f"Bearer {self.api_key}"}) - def get(self, url: str): - return self._get(f"{self.url}/{url.lstrip('/')}") + def get(self, url: str, params: Optional[dict] = None): + return self._get(f"{self.url}/{url.lstrip('/')}", params) def post(self, url: str, **kwargs): return self._post(f"{self.url}/{url.lstrip('/')}", kwargs) @@ -28,29 +28,31 @@ def patch(self, url: str, **kwargs): def delete(self, url: str): return self._delete(f"{self.url}/{url.lstrip('/')}") - def list_os(self): + def list_os(self, params: Optional[dict] = None): url = f"{self.url}/os" - return self._get(url)["os"] + return self._get(url, params)["os"] - def list_plans(self): + def list_plans(self, params: Optional[dict] = None): url = f"{self.url}/plans" - return self._get(url)["plans"] + return self._get(url, params)["plans"] - def list_regions(self): + def list_regions(self, params: Optional[dict] = None): url = f"{self.url}/regions" - return self._get(url)["regions"] + return self._get(url, params)["regions"] - def list_instances(self): + def list_instances(self, params: Optional[dict] = None): url = f"{self.url}/instances" - return self._get(url)["instances"] + return self._get(url, params)["instances"] - def get_instance(self, instance: Union[str, dict]): + def get_instance(self, instance: Union[str, dict], params: Optional[dict] = None): instance_id = self._get_obj_key(instance) url = f"{self.url}/instances/{instance_id}" - return self._get(url)["instance"] + return self._get(url, params)["instance"] - def create_instance(self, region: str, plan: str, **kwargs): - data = {"region": region, "plan": plan} + def create_instance( + self, region: Union[str, dict], plan: Union[str, dict], **kwargs + ): + data = {"region": self._get_obj_key(region), "plan": self._get_obj_key(plan)} data.update(kwargs) url = f"{self.url}/instances" return self._post(url, data)["instance"] @@ -65,14 +67,14 @@ def delete_instance(self, instance: Union[str, dict]): url = f"{self.url}/instances/{instance_id}" return self._delete(url) - def list_keys(self): + def list_keys(self, params: Optional[dict] = None): url = f"{self.url}/ssh-keys" - return self._get(url)["ssh_keys"] + return self._get(url, params)["ssh_keys"] - def get_key(self, key: Union[str, dict]): + def get_key(self, key: Union[str, dict], params: Optional[dict] = None): key_id = self._get_obj_key(key) url = f"{self.url}/ssh-keys/{key_id}" - return self._get(url)["ssh_key"] + return self._get(url, params)["ssh_key"] def create_key(self, name: str, key: str, **kwargs): data = {"name": name, "ssh_key": key} @@ -90,14 +92,14 @@ def delete_key(self, key: Union[str, dict]): url = f"{self.url}/ssh-keys/{key_id}" return self._delete(url) - def list_scripts(self): + def list_scripts(self, params: Optional[dict] = None): url = f"{self.url}/startup-scripts" - return self._get(url)["startup_scripts"] + return self._get(url, params)["startup_scripts"] - def get_script(self, script: Union[str, dict]): + def get_script(self, script: Union[str, dict], params: Optional[dict] = None): script_id = self._get_obj_key(script) url = f"{self.url}/startup-scripts/{script_id}" - return self._get(url)["startup_script"] + return self._get(url, params)["startup_script"] def create_script(self, name: str, script: str, **kwargs): data = {"name": name, "script": script} @@ -115,10 +117,10 @@ def delete_script(self, script: Union[str, dict]): url = f"{self.url}/startup-scripts/{script_id}" return self._delete(url) - def list_ipv4(self, instance: Union[str, dict]): + def list_ipv4(self, instance: Union[str, dict], params: Optional[dict] = None): instance_id = self._get_obj_key(instance) url = f"{self.url}/instances/{instance_id}/ipv4" - return self._get(url)["ipv4s"] + return self._get(url, params)["ipv4s"] def create_ipv4(self, instance: Union[str, dict], **kwargs): instance_id = self._get_obj_key(instance) @@ -155,8 +157,8 @@ def filter_scripts(scripts: list, name: str) -> dict: def filter_regions(regions: list, locations: list) -> list: return [d for d in regions if d["id"] in locations] - def _get(self, url): - r = self._session.get(url, timeout=10) + def _get(self, url, params: Optional[dict] = None): + r = self._session.get(url, params=params, timeout=10) if not r.ok: r.raise_for_status() return r.json() diff --git a/tests/test_all.py b/tests/test_all.py new file mode 100644 index 0000000..a4ec506 --- /dev/null +++ b/tests/test_all.py @@ -0,0 +1,10 @@ +from vultr import Vultr + + +vultr = Vultr() + + +def test_free(): + per_page = 100 + plans = vultr.get("plans", {"type": "vc2", "per_page": per_page}) + assert len(plans["plans"]) == min(plans["meta"]["total"], per_page) From 4d40e332c6ce7cc7b03ebb2d69b33ba9466edaaf Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:12:40 -0800 Subject: [PATCH 06/24] Test --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 26b4f8e..ac6ce3f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,7 +3,7 @@ name: "Test" on: workflow_dispatch: push: - branches: [master] + #branches: [master] #paths: &paths paths: - ".github/workflows/test.yaml" From ee11706db8dc759f2be8fe4d6ab951918300f5e3 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:43:27 -0800 Subject: [PATCH 07/24] Drop Support for 3.6 --- .github/workflows/test.yaml | 4 ++-- pyproject.toml | 2 +- src/vultr/vultr.py | 4 +--- tests/test_all.py | 9 +++++++++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ac6ce3f..afd74f8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,13 +25,13 @@ jobs: contents: read test: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 5 needs: [build] strategy: fail-fast: false matrix: - version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] name: "Test ${{ matrix.version }}" permissions: diff --git a/pyproject.toml b/pyproject.toml index 47d3620..f4ea224 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Python 3 wrapper for the Vultr API v2.0" authors = [{ name="Shane" }] readme = "README.md" dynamic = ["version"] -requires-python = ">=3.6" +requires-python = ">=3.7" classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", diff --git a/src/vultr/vultr.py b/src/vultr/vultr.py index 76bc79a..879ddc9 100644 --- a/src/vultr/vultr.py +++ b/src/vultr/vultr.py @@ -49,9 +49,7 @@ def get_instance(self, instance: Union[str, dict], params: Optional[dict] = None url = f"{self.url}/instances/{instance_id}" return self._get(url, params)["instance"] - def create_instance( - self, region: Union[str, dict], plan: Union[str, dict], **kwargs - ): + def create_instance(self, region: Union[str, dict], plan: Union[str, dict], **kwargs): data = {"region": self._get_obj_key(region), "plan": self._get_obj_key(plan)} data.update(kwargs) url = f"{self.url}/instances" diff --git a/tests/test_all.py b/tests/test_all.py index a4ec506..f8c4a0f 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -7,4 +7,13 @@ def test_free(): per_page = 100 plans = vultr.get("plans", {"type": "vc2", "per_page": per_page}) + print(f"plans: {len(plans['plans'])}") assert len(plans["plans"]) == min(plans["meta"]["total"], per_page) + + regions = vultr.list_regions({"per_page": per_page}) + print(f"regions: {len(regions)}") + assert 0 < len(regions) <= per_page + + available = vultr.filter_regions(regions, plans["plans"][0]["locations"]) + print(f"available: {len(available)}") + assert 0 < len(available) <= per_page From fe62469c59cf7b09b10fa1e408261be77a6b8594 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:07:55 -0800 Subject: [PATCH 08/24] Add coverage --- .github/workflows/lint.yaml | 26 +++++++++++++------------- README.md | 10 ++++++---- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 7dfc8cd..0e8b52f 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -137,19 +137,19 @@ jobs: 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: "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() }} diff --git a/README.md b/README.md index 945d0c8..33c73e4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ [![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) [![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) +[![Workflow Test](https://img.shields.io/github/actions/workflow/status/cssnr/vultr-python/test.yaml?logo=cachet&label=test)](https://github.com/cssnr/vultr-python/actions/workflows/test.yaml) +[![Deployments PyPi](https://img.shields.io/github/deployments/cssnr/vultr-python/pypi?logo=materialformkdocs&logoColor=white&label=pypi)](https://pypi.org/project/vultr-python/) +[![Deployments Pages](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) @@ -19,7 +21,7 @@ # Vultr Python - + Vultr Python - [Install](#Install) @@ -31,7 +33,7 @@ Python 3 wrapper for the Vultr 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)](https://cssnr.github.io/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) @@ -120,7 +122,7 @@ vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") These methods take the URI as the first argument, and data kwargs as the second. -Full Documentation: [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) From fd0bf2d118d1788b2a7f37385c5feec0f27ebf4d Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:10:28 -0800 Subject: [PATCH 09/24] Add pytest to dev --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index f4ea224..6f3b1a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dev = [ "coverage", "isort", "mypy", + "pytest", "requests", "ruff", "setuptools", From f1fccce0c1e0d755a282e318b013aef7c7a08080 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:18:01 -0800 Subject: [PATCH 10/24] Update test on --- .github/workflows/test.yaml | 9 ++++----- README.md | 2 -- docs/index.md | 2 -- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index afd74f8..e61026f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,15 +3,14 @@ name: "Test" on: workflow_dispatch: push: - #branches: [master] - #paths: &paths - paths: + branches: [master] + paths: &paths - ".github/workflows/test.yaml" - "src/**" - "tests/**" - "pyproject.toml" - #pull_request: - # paths: *paths + pull_request: + paths: *paths jobs: build: diff --git a/README.md b/README.md index 33c73e4..d68798f 100644 --- a/README.md +++ b/README.md @@ -120,8 +120,6 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` -These methods take the URI as the first argument, and data kwargs as the second. - 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) diff --git a/docs/index.md b/docs/index.md index 627a3c2..ba77e8a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -96,8 +96,6 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` -These methods take the URI as the first argument, and data kwargs as the second. -   Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) From e2172c3b5b475a736eca5edd759e72d56bf4b27c Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:19:54 -0800 Subject: [PATCH 11/24] Add actionlint.yaml #notest --- .github/actionlint.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/actionlint.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000..97d70ba --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,4 @@ +paths: + .github/workflows/**/*.{yml,yaml}: + ignore: + - '"paths" section must be sequence node but got alias node with "" tag' From 16193c35cc38c63ca1c7eaae080fb09a486c9827 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:15:15 -0800 Subject: [PATCH 12/24] Add VultrException --- README.md | 13 ++++++++++++- docs/index.md | 12 ++++++++++++ src/vultr/__init__.py | 4 ++-- src/vultr/vultr.py | 21 +++++++++++++++++---- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d68798f..a104a16 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![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) [![Workflow Test](https://img.shields.io/github/actions/workflow/status/cssnr/vultr-python/test.yaml?logo=cachet&label=test)](https://github.com/cssnr/vultr-python/actions/workflows/test.yaml) -[![Deployments PyPi](https://img.shields.io/github/deployments/cssnr/vultr-python/pypi?logo=materialformkdocs&logoColor=white&label=pypi)](https://pypi.org/project/vultr-python/) [![Deployments Pages](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) @@ -120,6 +119,18 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` +Errors Handling + +```python +from vultr import VultrException + +try: + instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", os_id=2284) +except VultrException as error: + print(error.error) # 'Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory.' + print(error.status) # 400 +``` + 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) diff --git a/docs/index.md b/docs/index.md index ba77e8a..bc10614 100644 --- a/docs/index.md +++ b/docs/index.md @@ -96,6 +96,18 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` +Errors Handling + +```python +from vultr import VultrException + +try: + instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", os_id=2284) +except VultrException as error: + print(error.error) # 'Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory.' + print(error.status) # 400 +``` +   Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) diff --git a/src/vultr/__init__.py b/src/vultr/__init__.py index 6cc4cfb..ab69aad 100644 --- a/src/vultr/__init__.py +++ b/src/vultr/__init__.py @@ -2,7 +2,7 @@ .. include:: ../../docs/index.md """ -from .vultr import Vultr +from .vultr import Vultr, VultrException -__all__ = ["Vultr"] +__all__ = ["Vultr", "VultrException"] diff --git a/src/vultr/vultr.py b/src/vultr/vultr.py index 879ddc9..655bc0a 100644 --- a/src/vultr/vultr.py +++ b/src/vultr/vultr.py @@ -158,25 +158,25 @@ def filter_regions(regions: list, locations: list) -> list: def _get(self, url, params: Optional[dict] = None): r = self._session.get(url, params=params, timeout=10) if not r.ok: - r.raise_for_status() + raise VultrException(r) return r.json() def _post(self, url, data): r = self._session.post(url, json=data, timeout=10) if not r.ok: - r.raise_for_status() + raise VultrException(r) return r.json() def _patch(self, url, data): r = self._session.patch(url, json=data, timeout=10) if not r.ok: - r.raise_for_status() + raise VultrException(r) return r.json() def _delete(self, url): r = self._session.delete(url, timeout=10) if not r.ok: - r.raise_for_status() + raise VultrException(r) return None @staticmethod @@ -190,3 +190,16 @@ def _get_obj_key(obj, key="id"): return obj[key] else: raise ValueError(f"Unable to parse object: {key}") + + +class VultrException(Exception): + def __init__(self, response: requests.Response): + try: + data = response.json() + error = data.get("error", response.text) + except ValueError: + error = response.text + status = response.status_code + self.error = error + self.status = status + super().__init__(f"Error {self.status}: {self.error}") From 92296aa74c88d9c4ac7c9e9c86cdf8a0f42f7e0e Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:46:20 -0800 Subject: [PATCH 13/24] Add Docstrings --- README.md | 2 +- docs/index.md | 2 +- src/vultr/vultr.py | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a104a16..bf04b0d 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ vultr = Vultr('VULTR_API_KEY') List plans and get available regions for that plan ```python -plans = vultr.list_plans({'type': 'vc2'}) # vc2 - Cloud Compute +plans = vultr.list_plans({'type': 'vc2'}) # Filter by type plan = plans[0] # 0 seems to be the base plan regions = vultr.list_regions() available = vultr.filter_regions(regions, plan['locations']) diff --git a/docs/index.md b/docs/index.md index bc10614..7c10ef2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,7 +54,7 @@ vultr = Vultr('VULTR_API_KEY') List plans and get available regions for that plan ```python -plans = vultr.list_plans({'type': 'vc2'}) # vc2 - Cloud Compute +plans = vultr.list_plans({'type': 'vc2'}) # Filter by type plan = plans[0] # 0 seems to be the base plan regions = vultr.list_regions() available = vultr.filter_regions(regions, plan['locations']) diff --git a/src/vultr/vultr.py b/src/vultr/vultr.py index 655bc0a..2e28af0 100644 --- a/src/vultr/vultr.py +++ b/src/vultr/vultr.py @@ -9,23 +9,47 @@ class Vultr(object): def __init__(self, api_key: Optional[str] = None): """ - :param str api_key: Vultr API Key or VULTR_API_KEY Environment Variable + :param api_key: Vultr API Key or `VULTR_API_KEY` Environment Variable """ self.api_key = api_key or os.getenv("VULTR_API_KEY") + """Provide the API key here or with the `VULTR_API_KEY` environment variable""" self._session = requests.session() if self.api_key: self._session.headers.update({"Authorization": f"Bearer {self.api_key}"}) def get(self, url: str, params: Optional[dict] = None): + """ + GET Data + :param url: Request URL. Example `/instances` + :param params: Query Parameters + :return: Response Data + """ return self._get(f"{self.url}/{url.lstrip('/')}", params) def post(self, url: str, **kwargs): + """ + POST Data + :param url: Request URL. Example `/instances` + :param kwargs: Request Data Keyword Arguments + :return: Response Data + """ return self._post(f"{self.url}/{url.lstrip('/')}", kwargs) def patch(self, url: str, **kwargs): + """ + PATCH Data + :param url: Request URL. Example `/instances/{resource_id}` + :param kwargs: Request Data Keyword Arguments + :return: Response Data + """ return self._patch(f"{self.url}/{url.lstrip('/')}", kwargs) def delete(self, url: str): + """ + DELETE a Resource + :param url: Request URL. Example `/instances/{resource_id}` + :return: None + """ return self._delete(f"{self.url}/{url.lstrip('/')}") def list_os(self, params: Optional[dict] = None): @@ -193,6 +217,7 @@ def _get_obj_key(obj, key="id"): class VultrException(Exception): + """ Exception class for all Vultr error responses. """ def __init__(self, response: requests.Response): try: data = response.json() @@ -200,6 +225,8 @@ def __init__(self, response: requests.Response): except ValueError: error = response.text status = response.status_code - self.error = error - self.status = status + self.error: str = error + """Error Message returned from the API""" + self.status: int = status + """Response Status Code""" super().__init__(f"Error {self.status}: {self.error}") From 9e8ff86856af03818c6c296eb545c2e73f979e7b Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:04:08 -0800 Subject: [PATCH 14/24] Add MANIFEST.in --- MANIFEST.in | 1 + README.md | 9 ++++++--- docs/index.md | 8 +++++--- src/vultr/vultr.py | 5 +++-- 4 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1eeef06 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +prune tests diff --git a/README.md b/README.md index bf04b0d..bbf9817 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![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) [![Workflow Test](https://img.shields.io/github/actions/workflow/status/cssnr/vultr-python/test.yaml?logo=cachet&label=test)](https://github.com/cssnr/vultr-python/actions/workflows/test.yaml) +[![Deployments PyPi](https://img.shields.io/github/deployments/cssnr/vultr-python/pypi?logo=pypi&logoColor=white&label=pypi)](https://pypi.org/project/vultr-python/) [![Deployments Pages](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) @@ -119,7 +120,7 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` -Errors Handling +Error Handling ```python from vultr import VultrException @@ -127,8 +128,10 @@ from vultr import VultrException try: instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", os_id=2284) except VultrException as error: - print(error.error) # 'Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory.' - print(error.status) # 400 + print(error.error) + # 'Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory.' + print(error.status) + # 400 ``` Full Documentation: [https://cssnr.github.io/vultr-python](https://cssnr.github.io/vultr-python/) diff --git a/docs/index.md b/docs/index.md index 7c10ef2..9df3733 100644 --- a/docs/index.md +++ b/docs/index.md @@ -96,7 +96,7 @@ sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` -Errors Handling +Error Handling ```python from vultr import VultrException @@ -104,8 +104,10 @@ from vultr import VultrException try: instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", os_id=2284) except VultrException as error: - print(error.error) # 'Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory.' - print(error.status) # 400 + print(error.error) + # 'Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory.' + print(error.status) + # 400 ```   diff --git a/src/vultr/vultr.py b/src/vultr/vultr.py index 2e28af0..9ebc686 100644 --- a/src/vultr/vultr.py +++ b/src/vultr/vultr.py @@ -217,7 +217,8 @@ def _get_obj_key(obj, key="id"): class VultrException(Exception): - """ Exception class for all Vultr error responses. """ + """Exception class for all Vultr error responses.""" + def __init__(self, response: requests.Response): try: data = response.json() @@ -226,7 +227,7 @@ def __init__(self, response: requests.Response): error = response.text status = response.status_code self.error: str = error - """Error Message returned from the API""" + """Error Message for 400 Codes""" self.status: int = status """Response Status Code""" super().__init__(f"Error {self.status}: {self.error}") From a4f02fbc34e1c28ffdc6244fbf5ecd81709c4fe5 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:10:34 -0800 Subject: [PATCH 15/24] Update Docs --- .github/workflows/docs.yaml | 32 +++++++++++++++---------------- .github/workflows/lint.yaml | 2 +- docs.ps1 | 22 +++++++++++---------- docs/index.md | 16 +++++++++++----- docs/module.html.jinja2 | 38 +++++++++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 32 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 3e5aa85..3ec6a5e 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -13,7 +13,7 @@ on: default: site env: - logo: "https://raw.githubusercontent.com/smashedr/repo-images/refs/heads/master/vultr-python/logo128.png" + logo: "https://raw.githubusercontent.com/cssnr/vultr-python/refs/heads/master/.github/assets/logo.svg" link: ${{ github.event.repository.html_url }} jobs: @@ -27,7 +27,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Debug CTX github" continue-on-error: true @@ -53,29 +53,29 @@ jobs: python -m pip install -U pdoc 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" + - name: "Fix Docs" + working-directory: ${{ inputs.path }} run: | - chmod -c -R +rX "${{ inputs.path }}" | while read name; do - echo "::notice::Fixed invalid file permissions: ${name}" - done + mv -f vultr.html index.html + + #- name: "Update Permissions" + # run: | + # 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 }} + working-directory: ${{ inputs.path }} run: | - echo "::group::ls" - ls -lAhR "${path}" - echo "::endgroup::" - echo "::group::tree" - tree "${path}" + results="$(tree . || ls -lAhR .)" + echo "::group::results" + echo "${results}" echo "::endgroup::" - results="$(tree "${path}")" - markdown='Artifacts:\n```text\n'"${results}"'\n```' + markdown='Artifacts: `${{ inputs.path }}`\n```text\n'"${results}"'\n```' echo -e "${markdown}" >> $GITHUB_STEP_SUMMARY - name: "Upload Pages Artifact" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 0e8b52f..8268ad4 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -26,7 +26,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Debug event.json" continue-on-error: true diff --git a/docs.ps1 b/docs.ps1 index 4d0d955..7fed3bc 100644 --- a/docs.ps1 +++ b/docs.ps1 @@ -22,17 +22,19 @@ if ($c) { } } -python -m pdoc -t .\docs\ -p 8008 ` - --favicon "https://df.cssnr.com/raw/logo128.png" ` - --logo "https://df.cssnr.com/raw/logo128.png" ` +if ($b) { + Write-Host -ForegroundColor Yellow "Building Docs..." + python -m pdoc -t .\docs\ -o site ` + --logo "https://raw.githubusercontent.com/cssnr/vultr-python/refs/heads/master/.github/assets/logo.svg" ` --logo-link "https://github.com/cssnr/vultr-python" ` vultr +} else { + Write-Host -ForegroundColor Green "Serving Docs..." + python -m pdoc -t .\docs\ -p 8000 -h 0.0.0.0 ` + --logo "https://raw.githubusercontent.com/cssnr/vultr-python/refs/heads/master/.github/assets/logo.svg" ` + --logo-link "https://github.com/cssnr/vultr-python" ` + vultr +} +#--favicon "https://df.cssnr.com/raw/logo128.png" ` #-e "vultr=https://github.com/cssnr/vultr-python/blob/updates/src/vultr/" ` - -#if ($b) { -# Write-Host -ForegroundColor Green "Building Docs..." -# zensical build -#} else { -# zensical serve -#} diff --git a/docs/index.md b/docs/index.md index 9df3733..175b178 100644 --- a/docs/index.md +++ b/docs/index.md @@ -39,11 +39,11 @@ 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. +You will need to create an api key and whitelist your IP address for most functions. - [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. +Initialize the `Vultr` class with your API Key or use the `VULTR_API_KEY` environment variable. ```python from vultr import Vultr @@ -88,16 +88,22 @@ instance = vultr.create_instance(available[0], plan, **data) Arbitrary Methods `get`, `post`, `patch`, `delete` ```python -# vultr.get('url', params) instances = vultr.get('instances', {'type': 'vc2'}) -# vultr.post('url', **kwargs) sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') -# vultr.delete('url') vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` Error Handling +```python +>>> instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", **data) +Traceback (most recent call last): + ... +vultr.vultr.VultrException: Error 400: Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory. +``` + +Using the `VultrException` class + ```python from vultr import VultrException diff --git a/docs/module.html.jinja2 b/docs/module.html.jinja2 index 229f2a8..82873b8 100644 --- a/docs/module.html.jinja2 +++ b/docs/module.html.jinja2 @@ -1,2 +1,40 @@ {% extends "default/module.html.jinja2" %} {% block title %}Vultr Python{% endblock %} +{% block head %} + + + + + + + + + + + + + + + + + + + + + + + + + +{% endblock %} From e2de5f3b1a315ba916384d97ea1eb411e2ab5d3d Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:41:07 -0800 Subject: [PATCH 16/24] Update Examples --- .github/workflows/build.yaml | 14 +++++--------- README.md | 21 +++++++++++++-------- docs/index.md | 3 ++- docs/module.html.jinja2 | 6 ++++++ 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0780dc7..681cad8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -55,17 +55,13 @@ jobs: python -m build - name: "List Artifacts" - env: - path: ${{ inputs.path }} + working-directory: ${{ inputs.path }} run: | - echo "::group::ls" - ls -lAhR "${path}" - echo "::endgroup::" - echo "::group::tree" - tree "${path}" + results="$(tree . || ls -lAhR .)" + echo "::group::results" + echo "${results}" echo "::endgroup::" - results="$(tree "${path}")" - markdown='Artifacts:\n```text\n'"${results}"'\n```' + markdown='Artifacts: `${{ inputs.path }}`\n```text\n'"${results}"'\n```' echo -e "${markdown}" >> $GITHUB_STEP_SUMMARY - name: "Upload to Actions" diff --git a/README.md b/README.md index bbf9817..97bc37a 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ Python 3 wrapper for the Vultr API v2. Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) > [!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). @@ -62,12 +61,11 @@ 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. +You will need to create an api key and whitelist your IP address for most functions. - [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. +Initialize the `Vultr` class with your API Key or use the `VULTR_API_KEY` environment variable. ```python from vultr import Vultr @@ -112,16 +110,23 @@ instance = vultr.create_instance(available[0], plan, **data) Arbitrary Methods `get`, `post`, `patch`, `delete` ```python -# vultr.get('url', params) -instances = vultr.get('instances', {'type': 'vc2'}) -# vultr.post('url', **kwargs) +plans = vultr.get('plans', {'type': 'vc2'}) sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') -# vultr.delete('url') +instance = vultr.patch('instances', plan=plans[1]['id']) vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` Error Handling +```python +>>> instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", **data) +Traceback (most recent call last): + ... +vultr.vultr.VultrException: Error 400: Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory. +``` + +Using the `VultrException` class + ```python from vultr import VultrException diff --git a/docs/index.md b/docs/index.md index 175b178..0f54e57 100644 --- a/docs/index.md +++ b/docs/index.md @@ -88,8 +88,9 @@ instance = vultr.create_instance(available[0], plan, **data) Arbitrary Methods `get`, `post`, `patch`, `delete` ```python -instances = vultr.get('instances', {'type': 'vc2'}) +plans = vultr.get('plans', {'type': 'vc2'}) sshkey = vultr.post('ssh-keys', name='key-name', ssh_key='ssh-rsa AAAA...') +instance = vultr.patch('instances', plan=plans[1]['id']) vultr.delete(f"instances/019ad1a8-2aa3-7650-83d1-8520d65ed6af") ``` diff --git a/docs/module.html.jinja2 b/docs/module.html.jinja2 index 82873b8..c3725a4 100644 --- a/docs/module.html.jinja2 +++ b/docs/module.html.jinja2 @@ -38,3 +38,9 @@ {% endblock %} + +{#{% block nav_footer %}#} +{# #} +{#{% endblock %}#} + +{#{% block attribution %}{% endblock %}#} From c8803d4b24b23d5ab567d129525f40ec1e91dd06 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:56:05 -0800 Subject: [PATCH 17/24] Cleanup --- README.md | 1 - docs/index.md | 3 ++- docs/module.html.jinja2 | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 97bc37a..6d88b2c 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,6 @@ Error Handling ```python >>> instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", **data) Traceback (most recent call last): - ... vultr.vultr.VultrException: Error 400: Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory. ``` diff --git a/docs/index.md b/docs/index.md index 0f54e57..58c6c4d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -99,7 +99,6 @@ Error Handling ```python >>> instance = vultr.create_instance("atl", "vc2-1c-0.5gb-v6", **data) Traceback (most recent call last): - ... vultr.vultr.VultrException: Error 400: Server add failed: Ubuntu 24.04 LTS x64 requires a plan with at least 1000 MB memory. ``` @@ -122,3 +121,5 @@ except VultrException as error: Vultr API Reference: [https://www.vultr.com/api](https://www.vultr.com/api/?ref=6905748) --- + +

API Documentation

diff --git a/docs/module.html.jinja2 b/docs/module.html.jinja2 index c3725a4..0948e60 100644 --- a/docs/module.html.jinja2 +++ b/docs/module.html.jinja2 @@ -26,10 +26,8 @@