From 2f5b02939a96df8e6f2af63d75945b9be8ba509c Mon Sep 17 00:00:00 2001 From: Gonzalo Rafuls Date: Tue, 30 Dec 2025 10:51:27 -0300 Subject: [PATCH 1/5] CI: Modified release process --- .bumpversion.cfg | 2 +- .github/workflows/publish.yml | 33 ++------------------------------- CONTRIBUTING.rst | 10 ++++++++++ 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index b0b2f33..0028624 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -2,7 +2,7 @@ current_version = 0.1.8 commit = True tag = True -message = "Bump version: {current_version} → {new_version} [skip ci]" +message = "Bump version: {current_version} → {new_version} [publish]" [bumpversion:file:setup.py] search = version="{current_version}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cee962a..01a0ecc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,7 +7,7 @@ on: jobs: publish: - if: github.actor != 'github-actions[bot]' + if: github.actor != 'github-actions[bot]' && contains(github.event.head_commit.message, '[publish]') runs-on: ubuntu-latest permissions: @@ -28,40 +28,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine bump2version - - - name: Configure Git - run: | - git config --global user.email "grafuls@gmail.com" - git config --global user.name "Your Name" - - - name: Bump version - run: | - # Keep bumping until we find a version without an existing tag - for i in {1..10}; do - NEW_VERSION=$(bump2version --dry-run --list patch | grep new_version | sed -r 's/^.*=//') - if git rev-parse "v${NEW_VERSION}" >/dev/null 2>&1; then - echo "Tag v${NEW_VERSION} already exists, bumping past it..." - bump2version patch --no-tag --no-commit --allow-dirty - else - echo "Bumping to v${NEW_VERSION}" - bump2version patch - break - fi - done + pip install setuptools wheel twine - name: Build package run: python setup.py sdist bdist_wheel - - name: Sync changes with all branches - run: | - git checkout latest - git pull origin latest --rebase - git push origin latest - git checkout development - git merge latest - git push origin development - - name: Publish package env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3173008..e53f504 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -73,6 +73,16 @@ For merging, you should: 3. Add a note to ``CHANGELOG.rst`` about the changes. 4. Add yourself to ``AUTHORS.rst``. +Release Process +=============== + +To release a new version, run the following command:: + + bumpversion patch + +This will increment the version number and commit the changes. + + Tips ---- From 53ad1972dabf3ab07075e491b345fdb615521007 Mon Sep 17 00:00:00 2001 From: Gonzalo Rafuls Date: Tue, 30 Dec 2025 11:00:56 -0300 Subject: [PATCH 2/5] =?UTF-8?q?"Bump=20version:=200.1.8=20=E2=86=92=200.1.?= =?UTF-8?q?9=20[publish]"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- docs/conf.py | 2 +- setup.py | 2 +- src/quads_lib/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0028624..f82f2c5 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.8 +current_version = 0.1.9 commit = True tag = True message = "Bump version: {current_version} → {new_version} [publish]" diff --git a/docs/conf.py b/docs/conf.py index 3625d9a..924206a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,7 @@ year = "2025" author = "Gonzalo Rafuls" copyright = f"{year}, {author}" -version = release = "0.1.8" +version = release = "0.1.9" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index 000f906..ef1813d 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name="quads-lib", - version="0.1.8", + version="0.1.9", license="LGPL-3.0-only", description="Python client library for interacting with the QUADS API", long_description="{}\n{}".format( diff --git a/src/quads_lib/__init__.py b/src/quads_lib/__init__.py index 32d45c8..0526669 100644 --- a/src/quads_lib/__init__.py +++ b/src/quads_lib/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.8" +__version__ = "0.1.9" from .quads import QuadsApi From 4714ac9ceb2c4ef53b1923fe71d760acd551b0ba Mon Sep 17 00:00:00 2001 From: Will Foster Date: Mon, 26 Jan 2026 12:18:48 +0000 Subject: [PATCH 3/5] feat: refactor for pep8, pyright, flake8. --- src/quads_lib/base.py | 3 ++- src/quads_lib/quads.py | 58 ++++++++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/quads_lib/base.py b/src/quads_lib/base.py index 0f6ae35..790b973 100644 --- a/src/quads_lib/base.py +++ b/src/quads_lib/base.py @@ -32,7 +32,8 @@ def __init__( password: Password for QUADS authentication base_url: Base URL for the QUADS API verify: Controls TLS certificate verification. Can be: - - False: Disable certificate verification (default, for backward compatibility) + - False: Disable certificate verification (default, for + backward compatibility) - True: Enable verification using default CA bundle - str: Path to a custom CA bundle file """ diff --git a/src/quads_lib/quads.py b/src/quads_lib/quads.py index 591801d..27f0c8a 100644 --- a/src/quads_lib/quads.py +++ b/src/quads_lib/quads.py @@ -1,8 +1,6 @@ from pathlib import Path from typing import Optional -from urllib import parse as url_parse -from urllib.parse import urlencode -from urllib.parse import urljoin +from urllib.parse import urlencode, urljoin from quads_lib.base import QuadsBase from quads_lib.decorators import returns @@ -15,16 +13,27 @@ class QuadsApi(QuadsBase): # Auth def register(self) -> dict: - json_response = self._make_request("POST", "register", {"email": self.username, "password": self.password}) + json_response = self._make_request( + "POST", + "register", + { + "email": self.username, + "password": self.password + }, + ) return json_response def login(self) -> dict: endpoint = urljoin(self.base_url, "login") - _response = self.session.post(endpoint, auth=self.auth, verify=self.verify) + _response = self.session.post( + endpoint, auth=self.auth, verify=self.verify + ) json_response = _response.json() if json_response.get("status_code") == 201: self.token = json_response.get("auth_token") - self.session.headers.update({"Authorization": f"Bearer {self.token}"}) + self.session.headers.update( + {"Authorization": f"Bearer {self.token}"} + ) return json_response def logout(self) -> dict: @@ -46,19 +55,19 @@ def get_host_models(self) -> dict: @returns("List[Host]") def filter_hosts(self, data: dict) -> dict: - url_params = url_parse.urlencode(data) + url_params = urlencode(data) json_response = self.get(f"hosts?{url_params}") return json_response @returns("List[Cloud]") def filter_clouds(self, data: dict) -> dict: - url_params = url_parse.urlencode(data) + url_params = urlencode(data) json_response = self.get(f"clouds?{url_params}") return json_response @returns("List[Assignment]") def filter_assignments(self, data: dict) -> dict: - url_params = url_parse.urlencode(data) + url_params = urlencode(data) json_response = self.get(f"assignments?{url_params}") return json_response @@ -85,10 +94,10 @@ def remove_host(self, hostname: str) -> dict: return json_response def is_available(self, hostname: str, data: dict) -> bool: - url_params = url_parse.urlencode(data) + url_params = urlencode(data) endpoint = Path("available") / hostname json_response = self.get(f"{endpoint}?{url_params}") - return True if "true" in json_response else False + return "true" in json_response # Clouds @returns("List[Cloud]") @@ -107,7 +116,7 @@ def get_cloud(self, cloud_name: str) -> dict: return json_response def get_summary(self, data: dict) -> dict: - url_params = url_parse.urlencode(data) + url_params = urlencode(data) endpoint = Path("clouds") / "summary" url = f"{endpoint}" if data: @@ -135,7 +144,7 @@ def remove_cloud(self, cloud_name: str) -> dict: def get_schedules(self, data: Optional[dict] = None) -> dict: if data is None: data = {} - url_params = url_parse.urlencode(data) + url_params = urlencode(data) url = "schedules" if url_params: url = f"{url}?{url_params}" @@ -149,7 +158,7 @@ def get_current_schedules(self, data: Optional[dict] = None) -> dict: endpoint = Path("schedules") / "current" url = f"{endpoint}" if data: - url_params = url_parse.urlencode(data) + url_params = urlencode(data) url = f"{endpoint}?{url_params}" json_response = self.get(url) return json_response @@ -164,7 +173,7 @@ def get_schedule(self, schedule_id: int) -> dict: def get_future_schedules(self, data: Optional[dict] = None) -> dict: if data is None: data = {} - url_params = url_parse.urlencode(data) + url_params = urlencode(data) endpoint = Path("schedules") / "future" url = f"{endpoint}" if data: @@ -203,7 +212,10 @@ def filter_available(self, data: dict) -> dict: def create_assignment(self, data: dict) -> dict: response = self.post("assignments", data) if response and {"id", "cloud"} <= response.keys(): - print(f"Assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") + print( + f"Assignment created - ID: {response['id']}, " + f"Cloud: {response['cloud']['name']}" + ) return response @returns("Assignment") @@ -211,7 +223,10 @@ def create_self_assignment(self, data: dict) -> dict: endpoint = Path("assignments") / "self" response = self.post(str(endpoint), data) if response and {"id", "cloud"} <= response.keys(): - print(f"Self-assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") + print( + f"Self-assignment created - ID: {response['id']}, " + f"Cloud: {response['cloud']['name']}" + ) return response @returns("Assignment") @@ -279,7 +294,7 @@ def create_memory(self, hostname: str, data: dict) -> dict: return json_response def remove_memory(self, memory_id: int) -> dict: - endpoint = Path("memory") / memory_id + endpoint = Path("memory") / str(memory_id) json_response = self.delete(str(endpoint)) return json_response @@ -297,7 +312,7 @@ def update_disk(self, hostname: str, data: dict) -> dict: return json_response def remove_disk(self, hostname: str, disk_id: int) -> dict: - endpoint = Path("disks") / hostname / disk_id + endpoint = Path("disks") / hostname / str(disk_id) json_response = self.delete(str(endpoint)) return json_response @@ -309,11 +324,10 @@ def create_processor(self, hostname: str, data: dict) -> dict: return json_response def remove_processor(self, processor_id: int) -> dict: - endpoint = Path("processors") / processor_id + endpoint = Path("processors") / str(processor_id) json_response = self.delete(str(endpoint)) return json_response - # Vlans @returns("List[Vlan]") def get_vlans(self) -> dict: json_response = self.get("vlans") @@ -345,7 +359,7 @@ def create_vlan(self, data: dict) -> dict: def get_moves(self, date: Optional[str] = None) -> dict: url = "moves" if date: - url_params = url_parse.urlencode({"date": date}) + url_params = urlencode({"date": date}) url = f"moves?{url_params}" json_response = self.get(url) return json_response From 0395a282f3560d8266d048f1fc3b9bb30d1893ec Mon Sep 17 00:00:00 2001 From: Will Foster Date: Mon, 26 Jan 2026 12:28:04 +0000 Subject: [PATCH 4/5] fix ruff complaints --- src/quads_lib/quads.py | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/quads_lib/quads.py b/src/quads_lib/quads.py index 27f0c8a..9ee1242 100644 --- a/src/quads_lib/quads.py +++ b/src/quads_lib/quads.py @@ -1,6 +1,7 @@ from pathlib import Path from typing import Optional -from urllib.parse import urlencode, urljoin +from urllib.parse import urlencode +from urllib.parse import urljoin from quads_lib.base import QuadsBase from quads_lib.decorators import returns @@ -16,24 +17,17 @@ def register(self) -> dict: json_response = self._make_request( "POST", "register", - { - "email": self.username, - "password": self.password - }, + {"email": self.username, "password": self.password}, ) return json_response def login(self) -> dict: endpoint = urljoin(self.base_url, "login") - _response = self.session.post( - endpoint, auth=self.auth, verify=self.verify - ) + _response = self.session.post(endpoint, auth=self.auth, verify=self.verify) json_response = _response.json() if json_response.get("status_code") == 201: self.token = json_response.get("auth_token") - self.session.headers.update( - {"Authorization": f"Bearer {self.token}"} - ) + self.session.headers.update({"Authorization": f"Bearer {self.token}"}) return json_response def logout(self) -> dict: @@ -212,10 +206,7 @@ def filter_available(self, data: dict) -> dict: def create_assignment(self, data: dict) -> dict: response = self.post("assignments", data) if response and {"id", "cloud"} <= response.keys(): - print( - f"Assignment created - ID: {response['id']}, " - f"Cloud: {response['cloud']['name']}" - ) + print(f"Assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") return response @returns("Assignment") @@ -223,10 +214,7 @@ def create_self_assignment(self, data: dict) -> dict: endpoint = Path("assignments") / "self" response = self.post(str(endpoint), data) if response and {"id", "cloud"} <= response.keys(): - print( - f"Self-assignment created - ID: {response['id']}, " - f"Cloud: {response['cloud']['name']}" - ) + print(f"Self-assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") return response @returns("Assignment") @@ -356,14 +344,4 @@ def create_vlan(self, data: dict) -> dict: return self.post("vlans", data) # Moves - def get_moves(self, date: Optional[str] = None) -> dict: - url = "moves" - if date: - url_params = urlencode({"date": date}) - url = f"moves?{url_params}" - json_response = self.get(url) - return json_response - - def get_version(self) -> dict: - json_response = self.get("version") - return json_response + def get_moves(self From 1110721c2cf6618224c41387bafe5781d3e1f311 Mon Sep 17 00:00:00 2001 From: Will Foster Date: Mon, 26 Jan 2026 12:32:16 +0000 Subject: [PATCH 5/5] fix truncation issue --- src/quads_lib/quads.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/quads_lib/quads.py b/src/quads_lib/quads.py index 9ee1242..931d814 100644 --- a/src/quads_lib/quads.py +++ b/src/quads_lib/quads.py @@ -344,4 +344,14 @@ def create_vlan(self, data: dict) -> dict: return self.post("vlans", data) # Moves - def get_moves(self + def get_moves(self, date: Optional[str] = None) -> dict: + url = "moves" + if date: + url_params = urlencode({"date": date}) + url = f"moves?{url_params}" + json_response = self.get(url) + return json_response + + def get_version(self) -> dict: + json_response = self.get("version") + return json_response