From f19cf4e8f0daed8bead41ce9b7097aeedb1884f2 Mon Sep 17 00:00:00 2001 From: Will Foster Date: Thu, 7 May 2026 21:38:12 +0100 Subject: [PATCH 1/7] fix: black, syntax, force release --- ci/bootstrap.py | 10 +- setup.py | 8 +- src/quads_lib/base.py | 7 +- src/quads_lib/quads.py | 11 +- tests/test_quads.py | 315 ++++++++++++++++++++++++++++++++++------- 5 files changed, 290 insertions(+), 61 deletions(-) diff --git a/ci/bootstrap.py b/ci/bootstrap.py index 6b72c2f..1136b38 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -59,7 +59,9 @@ def main(): # This uses sys.executable the same way that the call in # cookiecutter-pylibrary/hooks/post_gen_project.py # invokes this bootstrap.py itself. - for line in subprocess.check_output([sys.executable, "-m", "tox", "--listenvs"], universal_newlines=True).splitlines() + for line in subprocess.check_output( + [sys.executable, "-m", "tox", "--listenvs"], universal_newlines=True + ).splitlines() ] tox_environments = [line for line in tox_environments if line.startswith("py")] for template in templates_path.rglob("*"): @@ -67,7 +69,11 @@ def main(): template_path = template.relative_to(templates_path).as_posix() destination = base_path / template_path destination.parent.mkdir(parents=True, exist_ok=True) - destination.write_text(jinja.get_template(template_path).render(tox_environments=tox_environments)) + destination.write_text( + jinja.get_template(template_path).render( + tox_environments=tox_environments + ) + ) print(f"Wrote {template_path}") print("DONE.") diff --git a/setup.py b/setup.py index 8d30111..d2782fd 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,9 @@ def read(*names, **kwargs): - with Path(__file__).parent.joinpath(*names).open(encoding=kwargs.get("encoding", "utf8")) as fh: + with Path(__file__).parent.joinpath(*names).open( + encoding=kwargs.get("encoding", "utf8") + ) as fh: return fh.read() @@ -17,7 +19,9 @@ def read(*names, **kwargs): license="LGPL-3.0-only", description="Python client library for interacting with the QUADS API", long_description="{}\n{}".format( - re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub("", read("README.rst")), + re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub( + "", read("README.rst") + ), re.sub(":[a-z]+:`~?(.*?)`", r"``\1``", read("CHANGELOG.rst")), ), author="Gonzalo Rafuls", diff --git a/src/quads_lib/base.py b/src/quads_lib/base.py index 790b973..4a1f130 100644 --- a/src/quads_lib/base.py +++ b/src/quads_lib/base.py @@ -56,10 +56,13 @@ def __exit__(self, exc_type, exc_value, traceback): self.logout() self.session.close() - def _make_request(self, method: str, endpoint: str, data: Optional[dict] = None) -> dict: + def _make_request( + self, method: str, endpoint: str, data: Optional[dict] = None + ) -> dict: + full_url = urljoin(self.base_url, endpoint) _response = self.session.request( method, - urljoin(self.base_url, endpoint), + full_url, json=data, verify=self.verify, ) diff --git a/src/quads_lib/quads.py b/src/quads_lib/quads.py index e0179f8..9119d15 100644 --- a/src/quads_lib/quads.py +++ b/src/quads_lib/quads.py @@ -90,7 +90,8 @@ def remove_host(self, hostname: str) -> dict: def is_available(self, hostname: str, data: dict) -> bool: url_params = urlencode(data) endpoint = Path("available") / hostname - json_response = self.get(f"{endpoint}?{url_params}") + full_url = f"{endpoint}?{url_params}" + json_response = self.get(full_url) # Server returns {hostname: "True"} or {hostname: "False"} return json_response.get(hostname) == "True" @@ -207,7 +208,9 @@ 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']}, Cloud: {response['cloud']['name']}" + ) return response @returns("Assignment") @@ -215,7 +218,9 @@ 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']}, Cloud: {response['cloud']['name']}" + ) return response @returns("Assignment") diff --git a/tests/test_quads.py b/tests/test_quads.py index 4f16403..1e44420 100644 --- a/tests/test_quads.py +++ b/tests/test_quads.py @@ -21,7 +21,12 @@ def setup(self): @patch("requests.Session.request") def test_get_hosts(self, mock_get): - expected_response = {"hosts": [{"name": "host1", "model": "model1"}, {"name": "host2", "model": "model2"}]} + expected_response = { + "hosts": [ + {"name": "host1", "model": "model1"}, + {"name": "host2", "model": "model2"}, + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -57,7 +62,10 @@ def test_get_hosts_error(self, mock_get): @patch("requests.Session.request") def test_get_host_models(self, mock_get): expected_response = { - "model1": [{"name": "host1", "model": "model1"}, {"name": "host2", "model": "model1"}], + "model1": [ + {"name": "host1", "model": "model1"}, + {"name": "host2", "model": "model1"}, + ], "model2": [{"name": "host3", "model": "model2"}], } mock_response = Mock() @@ -124,7 +132,12 @@ def test_get_hosts_bad_request_no_json(self, mock_get): @patch("requests.Session.request") def test_filter_hosts(self, mock_get): - expected_response = {"hosts": [{"name": "host1", "model": "model1"}, {"name": "host2", "model": "model1"}]} + expected_response = { + "hosts": [ + {"name": "host1", "model": "model1"}, + {"name": "host2", "model": "model1"}, + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -182,7 +195,12 @@ def test_filter_hosts_bad_request(self, mock_get): @patch("requests.Session.request") def test_filter_clouds(self, mock_get): - expected_response = {"clouds": [{"name": "cloud1", "owner": "user1"}, {"name": "cloud2", "owner": "user1"}]} + expected_response = { + "clouds": [ + {"name": "cloud1", "owner": "user1"}, + {"name": "cloud2", "owner": "user1"}, + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -240,7 +258,12 @@ def test_filter_clouds_bad_request(self, mock_get): @patch("requests.Session.request") def test_filter_assignments(self, mock_get): - expected_response = {"assignments": [{"id": 1, "cloud": "cloud1", "host": "host1"}, {"id": 2, "cloud": "cloud1", "host": "host2"}]} + expected_response = { + "assignments": [ + {"id": 1, "cloud": "cloud1", "host": "host1"}, + {"id": 2, "cloud": "cloud1", "host": "host2"}, + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -298,7 +321,12 @@ def test_filter_assignments_bad_request(self, mock_get): @patch("requests.Session.request") def test_get_host(self, mock_get): - expected_response = {"name": "host1", "model": "model1", "cloud": "cloud1", "interfaces": []} + expected_response = { + "name": "host1", + "model": "model1", + "cloud": "cloud1", + "interfaces": [], + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -343,7 +371,12 @@ def test_get_host_special_chars(self, mock_get): @patch("requests.Session.request") def test_create_host(self, mock_post): - host_data = {"name": "new-host", "model": "model1", "cloud": "cloud1", "interfaces": []} + host_data = { + "name": "new-host", + "model": "model1", + "cloud": "cloud1", + "interfaces": [], + } mock_response = Mock() mock_response.json.return_value = host_data mock_post.return_value = mock_response @@ -556,7 +589,10 @@ def test_is_available_bad_request(self, mock_get): @patch("requests.Session.request") def test_get_clouds(self, mock_get): expected_response = { - "clouds": [{"name": "cloud1", "owner": "user1", "ticket": "123"}, {"name": "cloud2", "owner": "user2", "ticket": "456"}] + "clouds": [ + {"name": "cloud1", "owner": "user1", "ticket": "123"}, + {"name": "cloud2", "owner": "user2", "ticket": "456"}, + ] } mock_response = Mock() mock_response.json.return_value = expected_response @@ -603,7 +639,10 @@ def test_get_clouds_bad_request(self, mock_get): @patch("requests.Session.request") def test_get_free_clouds(self, mock_get): expected_response = { - "clouds": [{"name": "cloud1", "owner": "user1", "status": "free"}, {"name": "cloud2", "owner": "user2", "status": "free"}] + "clouds": [ + {"name": "cloud1", "owner": "user1", "status": "free"}, + {"name": "cloud2", "owner": "user2", "status": "free"}, + ] } mock_response = Mock() mock_response.json.return_value = expected_response @@ -650,7 +689,12 @@ def test_get_free_clouds_bad_request(self, mock_get): @patch("requests.Session.request") def test_get_cloud(self, mock_get): cloud_name = "test-cloud" - expected_response = {"name": "test-cloud", "owner": "user1", "ticket": "123", "description": "Test cloud environment"} + expected_response = { + "name": "test-cloud", + "owner": "user1", + "ticket": "123", + "description": "Test cloud environment", + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -693,7 +737,10 @@ def test_get_summary(self, mock_get): mock_get.assert_called_once() called_url = str(mock_get.call_args[0][1]) assert called_url.endswith( - ("/clouds/summary?start_date=2024-03-20&end_date=2024-03-21", "/clouds/summary?end_date=2024-03-21&start_date=2024-03-20") + ( + "/clouds/summary?start_date=2024-03-20&end_date=2024-03-21", + "/clouds/summary?end_date=2024-03-21&start_date=2024-03-20", + ) ) assert result == expected_response @@ -731,7 +778,12 @@ def test_get_summary_bad_request(self, mock_get): @patch("requests.Session.request") def test_create_cloud(self, mock_post): - cloud_data = {"name": "new-cloud", "owner": "user1", "ticket": "123", "description": "New test cloud"} + cloud_data = { + "name": "new-cloud", + "owner": "user1", + "ticket": "123", + "description": "New test cloud", + } mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = cloud_data @@ -781,7 +833,11 @@ def test_create_cloud_bad_request(self, mock_post): @patch("requests.Session.request") def test_update_cloud(self, mock_patch): cloud_name = "existing-cloud" - update_data = {"owner": "new-owner", "ticket": "456", "description": "Updated description"} + update_data = { + "owner": "new-owner", + "ticket": "456", + "description": "Updated description", + } mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = update_data @@ -850,8 +906,18 @@ def test_remove_cloud_bad_request(self, mock_delete): def test_get_schedules(self, mock_get): expected_response = { "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}, - {"id": 2, "cloud": "cloud2", "start": "2024-03-22", "end": "2024-03-23"}, + { + "id": 1, + "cloud": "cloud1", + "start": "2024-03-20", + "end": "2024-03-21", + }, + { + "id": 2, + "cloud": "cloud2", + "start": "2024-03-22", + "end": "2024-03-23", + }, ] } mock_response = Mock() @@ -867,7 +933,11 @@ def test_get_schedules(self, mock_get): @patch("requests.Session.request") def test_get_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1", "start": "2024-03-20"} - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -875,14 +945,20 @@ def test_get_schedules_with_params(self, mock_get): result = self.api.get_schedules(query_data) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith("/schedules?cloud=cloud1&start=2024-03-20") or str(mock_get.call_args[0][1]).endswith( + assert str(mock_get.call_args[0][1]).endswith( + "/schedules?cloud=cloud1&start=2024-03-20" + ) or str(mock_get.call_args[0][1]).endswith( "/schedules?start=2024-03-20&cloud=cloud1" ) assert result == expected_response @patch("requests.Session.request") def test_get_current_schedules(self, mock_get): - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -896,7 +972,11 @@ def test_get_current_schedules(self, mock_get): @patch("requests.Session.request") def test_get_current_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -928,7 +1008,12 @@ def test_get_current_schedules_error(self, mock_get): @patch("requests.Session.request") def test_get_schedule(self, mock_get): schedule_id = 123 - expected_response = {"id": 123, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + expected_response = { + "id": 123, + "cloud": "cloud1", + "start": "2024-03-20", + "end": "2024-03-21", + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -943,8 +1028,18 @@ def test_get_schedule(self, mock_get): def test_get_future_schedules(self, mock_get): expected_response = { "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}, - {"id": 2, "cloud": "cloud2", "start": "2024-03-22", "end": "2024-03-23"}, + { + "id": 1, + "cloud": "cloud1", + "start": "2024-03-20", + "end": "2024-03-21", + }, + { + "id": 2, + "cloud": "cloud2", + "start": "2024-03-22", + "end": "2024-03-23", + }, ] } mock_response = Mock() @@ -960,7 +1055,11 @@ def test_get_future_schedules(self, mock_get): @patch("requests.Session.request") def test_get_future_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1054,7 +1153,12 @@ def test_create_schedule(self, mock_post): @patch("requests.Session.request") def test_get_available(self, mock_get): - expected_response = {"hosts": [{"name": "host1", "model": "model1"}, {"name": "host2", "model": "model2"}]} + expected_response = { + "hosts": [ + {"name": "host1", "model": "model1"}, + {"name": "host2", "model": "model2"}, + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1067,7 +1171,11 @@ def test_get_available(self, mock_get): @patch("requests.Session.request") def test_filter_available(self, mock_get): - filter_data = {"start_date": "2024-03-20", "end_date": "2024-03-21", "model": "model1"} + filter_data = { + "start_date": "2024-03-20", + "end_date": "2024-03-21", + "model": "model1", + } expected_response = {"hosts": [{"name": "host1", "model": "model1"}]} mock_response = Mock() mock_response.json.return_value = expected_response @@ -1084,7 +1192,12 @@ def test_filter_available(self, mock_get): @patch("requests.Session.request") def test_create_assignment(self, mock_post): - assignment_data = {"cloud": "cloud1", "host": "host1", "start": "2024-03-20", "end": "2024-03-21"} + assignment_data = { + "cloud": "cloud1", + "host": "host1", + "start": "2024-03-20", + "end": "2024-03-21", + } mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = assignment_data @@ -1125,7 +1238,9 @@ def test_update_notification(self, mock_patch): result = self.api.update_notification(notification_id, update_data) mock_patch.assert_called_once() - assert str(mock_patch.call_args[0][1]).endswith(f"/notifications/{notification_id}") + assert str(mock_patch.call_args[0][1]).endswith( + f"/notifications/{notification_id}" + ) assert mock_patch.call_args[1]["json"] == update_data assert result == update_data @@ -1177,7 +1292,12 @@ def test_update_notification_error(self, mock_patch): @patch("requests.Session.request") def test_get_active_cloud_assignment(self, mock_get): cloud_name = "cloud1" - expected_response = {"id": 123, "cloud": "cloud1", "host": "host1", "status": "active"} + expected_response = { + "id": 123, + "cloud": "cloud1", + "host": "host1", + "status": "active", + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1185,7 +1305,9 @@ def test_get_active_cloud_assignment(self, mock_get): result = self.api.get_active_cloud_assignment(cloud_name) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith(f"/assignments/active/{cloud_name}") + assert str(mock_get.call_args[0][1]).endswith( + f"/assignments/active/{cloud_name}" + ) assert result == expected_response @patch("requests.Session.request") @@ -1210,7 +1332,10 @@ def test_get_active_assignments(self, mock_get): def test_get_host_interface(self, mock_get): hostname = "host1" expected_response = { - "interfaces": [{"name": "eth0", "mac_address": "00:11:22:33:44:55"}, {"name": "eth1", "mac_address": "00:11:22:33:44:66"}] + "interfaces": [ + {"name": "eth0", "mac_address": "00:11:22:33:44:55"}, + {"name": "eth1", "mac_address": "00:11:22:33:44:66"}, + ] } mock_response = Mock() mock_response.json.return_value = expected_response @@ -1268,13 +1393,19 @@ def test_remove_interface(self, mock_delete): result = self.api.remove_interface(hostname, if_name) mock_delete.assert_called_once() - assert str(mock_delete.call_args[0][1]).endswith(f"/interfaces/{hostname}/{if_name}") + assert str(mock_delete.call_args[0][1]).endswith( + f"/interfaces/{hostname}/{if_name}" + ) assert result == {} @patch("requests.Session.request") def test_create_interface(self, mock_post): hostname = "host1" - interface_data = {"name": "eth0", "mac_address": "00:11:22:33:44:55", "switch_port": "Gi1/0/1"} + interface_data = { + "name": "eth0", + "mac_address": "00:11:22:33:44:55", + "switch_port": "Gi1/0/1", + } mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = interface_data @@ -1451,7 +1582,11 @@ def test_get_vlans(self, mock_get): @patch("requests.Session.request") def test_get_vlan(self, mock_get): vlan_id = 100 - expected_response = {"id": 100, "name": "prod", "description": "Production network"} + expected_response = { + "id": 100, + "name": "prod", + "description": "Production network", + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1464,7 +1599,11 @@ def test_get_vlan(self, mock_get): @patch("requests.Session.request") def test_get_free_vlan(self, mock_get): - expected_response = {"id": 100, "name": "prod", "description": "Production network"} + expected_response = { + "id": 100, + "name": "prod", + "description": "Production network", + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1510,8 +1649,18 @@ def test_create_vlan(self, mock_post): def test_get_moves(self, mock_get): expected_response = { "moves": [ - {"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"}, - {"id": 2, "host": "host2", "from_cloud": "cloud2", "to_cloud": "cloud3"}, + { + "id": 1, + "host": "host1", + "from_cloud": "cloud1", + "to_cloud": "cloud2", + }, + { + "id": 2, + "host": "host2", + "from_cloud": "cloud2", + "to_cloud": "cloud3", + }, ] } mock_response = Mock() @@ -1527,7 +1676,11 @@ def test_get_moves(self, mock_get): @patch("requests.Session.request") def test_get_moves_with_date(self, mock_get): date = "2024-03-20" - expected_response = {"moves": [{"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"}]} + expected_response = { + "moves": [ + {"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1609,12 +1762,17 @@ def test_create_self_assignment(self, mock_post): with patch.object(self.api, "post") as mock_post: mock_post.return_value = expected_response result = self.api.create_self_assignment(test_data) - mock_post.assert_called_once_with(str(Path("assignments") / "self"), test_data) + mock_post.assert_called_once_with( + str(Path("assignments") / "self"), test_data + ) assert result == expected_response @patch("requests.Session.request") def test_register_success(self, mock_post): - expected_response = {"status_code": 201, "message": "User registered successfully"} + expected_response = { + "status_code": 201, + "message": "User registered successfully", + } mock_response = Mock() mock_response.status_code = 201 mock_response.json.return_value = expected_response @@ -1627,7 +1785,11 @@ def test_register_success(self, mock_post): @patch("requests.Session.request") def test_login_success(self, mock_post): - expected_response = {"status_code": 201, "auth_token": "fake-token-123", "message": "Login successful"} + expected_response = { + "status_code": 201, + "auth_token": "fake-token-123", + "message": "Login successful", + } mock_response = Mock() mock_response.status_code = 201 mock_response.json.return_value = expected_response @@ -1681,7 +1843,12 @@ def test_logout_failure(self, mock_post): @patch("builtins.print") @patch("requests.Session.request") def test_create_assignment_logging(self, mock_request, mock_print): - assignment_data = {"cloud": "cloud1", "host": "host1", "start": "2025-06-20", "end": "2025-06-21"} + assignment_data = { + "cloud": "cloud1", + "host": "host1", + "start": "2025-06-20", + "end": "2025-06-21", + } response_data = { "id": 42, "cloud": {"name": "cloud1", "owner": "user1"}, @@ -1702,9 +1869,19 @@ def test_create_assignment_logging(self, mock_request, mock_print): @patch("builtins.print") @patch("requests.Session.request") def test_create_assignment_no_logging(self, mock_request, mock_print): - assignment_data = {"cloud": "cloud1", "host": "host1", "start": "2025-06-20", "end": "2025-06-21"} + assignment_data = { + "cloud": "cloud1", + "host": "host1", + "start": "2025-06-20", + "end": "2025-06-21", + } # Missing 'id' field in response - should not trigger logging - response_data = {"cloud": {"name": "cloud1", "owner": "user1"}, "host": "host1", "start": "2025-06-20", "end": "2025-06-21"} + response_data = { + "cloud": {"name": "cloud1", "owner": "user1"}, + "host": "host1", + "start": "2025-06-20", + "end": "2025-06-21", + } mock_response = Mock() mock_response.status_code = 200 @@ -1718,9 +1895,18 @@ def test_create_assignment_no_logging(self, mock_request, mock_print): @patch("builtins.print") @patch("requests.Session.request") def test_create_assignment_limit_reached(self, mock_request, mock_print): - assignment_data = {"cloud": "cloud1", "host": "host1", "start": "2025-06-20", "end": "2025-06-21"} + assignment_data = { + "cloud": "cloud1", + "host": "host1", + "start": "2025-06-20", + "end": "2025-06-21", + } # Error response for scheduling limit reached - error_response = {"error": "Forbidden", "message": "Self scheduling limit reached", "status_code": 403} + error_response = { + "error": "Forbidden", + "message": "Self scheduling limit reached", + "status_code": 403, + } mock_response = Mock() mock_response.status_code = 403 @@ -1735,8 +1921,17 @@ def test_create_assignment_limit_reached(self, mock_request, mock_print): @patch("builtins.print") @patch("requests.Session.request") def test_create_self_assignment_logging(self, mock_request, mock_print): - assignment_data = {"cloud": "cloud1", "start": "2025-06-20", "end": "2025-06-21"} - response_data = {"id": 123, "cloud": {"name": "cloud1", "owner": "user1"}, "start": "2025-06-20", "end": "2025-06-21"} + assignment_data = { + "cloud": "cloud1", + "start": "2025-06-20", + "end": "2025-06-21", + } + response_data = { + "id": 123, + "cloud": {"name": "cloud1", "owner": "user1"}, + "start": "2025-06-20", + "end": "2025-06-21", + } mock_response = Mock() mock_response.status_code = 200 @@ -1745,12 +1940,18 @@ def test_create_self_assignment_logging(self, mock_request, mock_print): self.api.create_self_assignment(assignment_data) - mock_print.assert_called_once_with("Self-assignment created - ID: 123, Cloud: cloud1") + mock_print.assert_called_once_with( + "Self-assignment created - ID: 123, Cloud: cloud1" + ) @patch("builtins.print") @patch("requests.Session.request") def test_create_self_assignment_no_logging(self, mock_request, mock_print): - assignment_data = {"cloud": "cloud1", "start": "2025-06-20", "end": "2025-06-21"} + assignment_data = { + "cloud": "cloud1", + "start": "2025-06-20", + "end": "2025-06-21", + } # Missing 'cloud' field in response - should not trigger logging response_data = {"id": 123, "start": "2025-06-20", "end": "2025-06-21"} @@ -1766,9 +1967,17 @@ def test_create_self_assignment_no_logging(self, mock_request, mock_print): @patch("builtins.print") @patch("requests.Session.request") def test_create_self_assignment_limit_reached(self, mock_request, mock_print): - assignment_data = {"cloud": "cloud1", "start": "2025-06-20", "end": "2025-06-21"} + assignment_data = { + "cloud": "cloud1", + "start": "2025-06-20", + "end": "2025-06-21", + } # Error response for self scheduling limit reached - error_response = {"error": "Forbidden", "message": "Self scheduling limit reached", "status_code": 403} + error_response = { + "error": "Forbidden", + "message": "Self scheduling limit reached", + "status_code": 403, + } mock_response = Mock() mock_response.status_code = 403 @@ -1790,7 +1999,9 @@ def setup(self): @pytest.fixture def quads_base(self): - return QuadsBase(username=self.username, password=self.password, base_url=self.base_url) + return QuadsBase( + username=self.username, password=self.password, base_url=self.base_url + ) def test_context_manager_enter(self, quads_base): quads_base.login = Mock() From 6e6b3c0cfd7f036cac6f78b9afb858a070b0da5a Mon Sep 17 00:00:00 2001 From: Will Foster Date: Thu, 7 May 2026 21:49:26 +0100 Subject: [PATCH 2/7] fix: formatting, tests [publish] --- setup.py | 8 ++---- src/quads_lib/base.py | 4 +-- src/quads_lib/quads.py | 8 ++---- tests/test_quads.py | 58 +++++++++--------------------------------- 4 files changed, 17 insertions(+), 61 deletions(-) diff --git a/setup.py b/setup.py index d2782fd..8d30111 100755 --- a/setup.py +++ b/setup.py @@ -7,9 +7,7 @@ def read(*names, **kwargs): - with Path(__file__).parent.joinpath(*names).open( - encoding=kwargs.get("encoding", "utf8") - ) as fh: + with Path(__file__).parent.joinpath(*names).open(encoding=kwargs.get("encoding", "utf8")) as fh: return fh.read() @@ -19,9 +17,7 @@ def read(*names, **kwargs): license="LGPL-3.0-only", description="Python client library for interacting with the QUADS API", long_description="{}\n{}".format( - re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub( - "", read("README.rst") - ), + re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub("", read("README.rst")), re.sub(":[a-z]+:`~?(.*?)`", r"``\1``", read("CHANGELOG.rst")), ), author="Gonzalo Rafuls", diff --git a/src/quads_lib/base.py b/src/quads_lib/base.py index 4a1f130..06a5ea6 100644 --- a/src/quads_lib/base.py +++ b/src/quads_lib/base.py @@ -56,9 +56,7 @@ def __exit__(self, exc_type, exc_value, traceback): self.logout() self.session.close() - def _make_request( - self, method: str, endpoint: str, data: Optional[dict] = None - ) -> dict: + def _make_request(self, method: str, endpoint: str, data: Optional[dict] = None) -> dict: full_url = urljoin(self.base_url, endpoint) _response = self.session.request( method, diff --git a/src/quads_lib/quads.py b/src/quads_lib/quads.py index 9119d15..4e2501e 100644 --- a/src/quads_lib/quads.py +++ b/src/quads_lib/quads.py @@ -208,9 +208,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']}, Cloud: {response['cloud']['name']}" - ) + print(f"Assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") return response @returns("Assignment") @@ -218,9 +216,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']}, Cloud: {response['cloud']['name']}" - ) + print(f"Self-assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") return response @returns("Assignment") diff --git a/tests/test_quads.py b/tests/test_quads.py index 1e44420..66ca2a2 100644 --- a/tests/test_quads.py +++ b/tests/test_quads.py @@ -933,11 +933,7 @@ def test_get_schedules(self, mock_get): @patch("requests.Session.request") def test_get_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1", "start": "2024-03-20"} - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -945,20 +941,14 @@ def test_get_schedules_with_params(self, mock_get): result = self.api.get_schedules(query_data) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith( - "/schedules?cloud=cloud1&start=2024-03-20" - ) or str(mock_get.call_args[0][1]).endswith( + assert str(mock_get.call_args[0][1]).endswith("/schedules?cloud=cloud1&start=2024-03-20") or str(mock_get.call_args[0][1]).endswith( "/schedules?start=2024-03-20&cloud=cloud1" ) assert result == expected_response @patch("requests.Session.request") def test_get_current_schedules(self, mock_get): - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -972,11 +962,7 @@ def test_get_current_schedules(self, mock_get): @patch("requests.Session.request") def test_get_current_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1055,11 +1041,7 @@ def test_get_future_schedules(self, mock_get): @patch("requests.Session.request") def test_get_future_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1238,9 +1220,7 @@ def test_update_notification(self, mock_patch): result = self.api.update_notification(notification_id, update_data) mock_patch.assert_called_once() - assert str(mock_patch.call_args[0][1]).endswith( - f"/notifications/{notification_id}" - ) + assert str(mock_patch.call_args[0][1]).endswith(f"/notifications/{notification_id}") assert mock_patch.call_args[1]["json"] == update_data assert result == update_data @@ -1305,9 +1285,7 @@ def test_get_active_cloud_assignment(self, mock_get): result = self.api.get_active_cloud_assignment(cloud_name) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith( - f"/assignments/active/{cloud_name}" - ) + assert str(mock_get.call_args[0][1]).endswith(f"/assignments/active/{cloud_name}") assert result == expected_response @patch("requests.Session.request") @@ -1393,9 +1371,7 @@ def test_remove_interface(self, mock_delete): result = self.api.remove_interface(hostname, if_name) mock_delete.assert_called_once() - assert str(mock_delete.call_args[0][1]).endswith( - f"/interfaces/{hostname}/{if_name}" - ) + assert str(mock_delete.call_args[0][1]).endswith(f"/interfaces/{hostname}/{if_name}") assert result == {} @patch("requests.Session.request") @@ -1676,11 +1652,7 @@ def test_get_moves(self, mock_get): @patch("requests.Session.request") def test_get_moves_with_date(self, mock_get): date = "2024-03-20" - expected_response = { - "moves": [ - {"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"} - ] - } + expected_response = {"moves": [{"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1762,9 +1734,7 @@ def test_create_self_assignment(self, mock_post): with patch.object(self.api, "post") as mock_post: mock_post.return_value = expected_response result = self.api.create_self_assignment(test_data) - mock_post.assert_called_once_with( - str(Path("assignments") / "self"), test_data - ) + mock_post.assert_called_once_with(str(Path("assignments") / "self"), test_data) assert result == expected_response @patch("requests.Session.request") @@ -1940,9 +1910,7 @@ def test_create_self_assignment_logging(self, mock_request, mock_print): self.api.create_self_assignment(assignment_data) - mock_print.assert_called_once_with( - "Self-assignment created - ID: 123, Cloud: cloud1" - ) + mock_print.assert_called_once_with("Self-assignment created - ID: 123, Cloud: cloud1") @patch("builtins.print") @patch("requests.Session.request") @@ -1999,9 +1967,7 @@ def setup(self): @pytest.fixture def quads_base(self): - return QuadsBase( - username=self.username, password=self.password, base_url=self.base_url - ) + return QuadsBase(username=self.username, password=self.password, base_url=self.base_url) def test_context_manager_enter(self, quads_base): quads_base.login = Mock() From ae038a615f9067a5b398784ceacb7bc4708a4d15 Mon Sep 17 00:00:00 2001 From: Will Foster Date: Thu, 7 May 2026 22:01:17 +0100 Subject: [PATCH 3/7] fix: update CI frameworks [publish] --- .github/workflows/publish.yml | 46 +++++++++--------- .github/workflows/update-rpm-and-sync.yml | 56 ++++++++++++++++++++++ setup.py | 8 +++- src/quads_lib/base.py | 4 +- src/quads_lib/quads.py | 8 +++- tests/test_quads.py | 58 ++++++++++++++++++----- 6 files changed, 140 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/update-rpm-and-sync.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 01a0ecc..abba74d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,26 +15,26 @@ jobs: pull-requests: write steps: - - name: Check out code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - - name: Build package - run: python setup.py sdist bdist_wheel - - - name: Publish package - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: twine upload dist/* + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + + - name: Build package + run: python setup.py sdist bdist_wheel + + - name: Publish package + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: twine upload dist/* diff --git a/.github/workflows/update-rpm-and-sync.yml b/.github/workflows/update-rpm-and-sync.yml new file mode 100644 index 0000000..c4286d1 --- /dev/null +++ b/.github/workflows/update-rpm-and-sync.yml @@ -0,0 +1,56 @@ +name: Update RPM Spec and Sync + +on: + workflow_run: + workflows: ["Publish Python Package"] # Listens for your existing publish action + types: + - completed + +jobs: + update-and-sync: + runs-on: ubuntu-latest + # Only run if the PyPI publish actually succeeded + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git fetch origin + + - name: Update RPM spec version on latest + run: | + git checkout latest + + # Extract version from your __init__.py file + VERSION=$(grep -oP "^__version__ = ['\"]([^'\"]+)['\"]" src/quads_lib/__init__.py | grep -oP "[0-9]+\.[0-9]+\.[0-9]+") + echo "Extracted version: $VERSION" + + # Update the spec file + sed -i "s/^%define version.*/%define version $VERSION/" rpm/quads-lib.spec + + # Commit and push if there are changes + git add rpm/quads-lib.spec + if ! git diff --staged --quiet; then + # Added [skip ci] so this commit doesn't accidentally trigger other tests + git commit -m "chore: update RPM spec version to $VERSION [skip ci]" + git push origin latest + echo "Updated RPM spec to version $VERSION on latest" + else + echo "No version changes to commit" + fi + + - name: Sync back to development + run: | + git checkout development + # Merge the updated latest branch into development + git merge origin/latest --no-edit + git push origin development diff --git a/setup.py b/setup.py index 8d30111..d2782fd 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,9 @@ def read(*names, **kwargs): - with Path(__file__).parent.joinpath(*names).open(encoding=kwargs.get("encoding", "utf8")) as fh: + with Path(__file__).parent.joinpath(*names).open( + encoding=kwargs.get("encoding", "utf8") + ) as fh: return fh.read() @@ -17,7 +19,9 @@ def read(*names, **kwargs): license="LGPL-3.0-only", description="Python client library for interacting with the QUADS API", long_description="{}\n{}".format( - re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub("", read("README.rst")), + re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub( + "", read("README.rst") + ), re.sub(":[a-z]+:`~?(.*?)`", r"``\1``", read("CHANGELOG.rst")), ), author="Gonzalo Rafuls", diff --git a/src/quads_lib/base.py b/src/quads_lib/base.py index 06a5ea6..4a1f130 100644 --- a/src/quads_lib/base.py +++ b/src/quads_lib/base.py @@ -56,7 +56,9 @@ def __exit__(self, exc_type, exc_value, traceback): self.logout() self.session.close() - def _make_request(self, method: str, endpoint: str, data: Optional[dict] = None) -> dict: + def _make_request( + self, method: str, endpoint: str, data: Optional[dict] = None + ) -> dict: full_url = urljoin(self.base_url, endpoint) _response = self.session.request( method, diff --git a/src/quads_lib/quads.py b/src/quads_lib/quads.py index 4e2501e..9119d15 100644 --- a/src/quads_lib/quads.py +++ b/src/quads_lib/quads.py @@ -208,7 +208,9 @@ 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']}, Cloud: {response['cloud']['name']}" + ) return response @returns("Assignment") @@ -216,7 +218,9 @@ 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']}, Cloud: {response['cloud']['name']}" + ) return response @returns("Assignment") diff --git a/tests/test_quads.py b/tests/test_quads.py index 66ca2a2..1e44420 100644 --- a/tests/test_quads.py +++ b/tests/test_quads.py @@ -933,7 +933,11 @@ def test_get_schedules(self, mock_get): @patch("requests.Session.request") def test_get_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1", "start": "2024-03-20"} - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -941,14 +945,20 @@ def test_get_schedules_with_params(self, mock_get): result = self.api.get_schedules(query_data) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith("/schedules?cloud=cloud1&start=2024-03-20") or str(mock_get.call_args[0][1]).endswith( + assert str(mock_get.call_args[0][1]).endswith( + "/schedules?cloud=cloud1&start=2024-03-20" + ) or str(mock_get.call_args[0][1]).endswith( "/schedules?start=2024-03-20&cloud=cloud1" ) assert result == expected_response @patch("requests.Session.request") def test_get_current_schedules(self, mock_get): - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -962,7 +972,11 @@ def test_get_current_schedules(self, mock_get): @patch("requests.Session.request") def test_get_current_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1041,7 +1055,11 @@ def test_get_future_schedules(self, mock_get): @patch("requests.Session.request") def test_get_future_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} + expected_response = { + "schedules": [ + {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1220,7 +1238,9 @@ def test_update_notification(self, mock_patch): result = self.api.update_notification(notification_id, update_data) mock_patch.assert_called_once() - assert str(mock_patch.call_args[0][1]).endswith(f"/notifications/{notification_id}") + assert str(mock_patch.call_args[0][1]).endswith( + f"/notifications/{notification_id}" + ) assert mock_patch.call_args[1]["json"] == update_data assert result == update_data @@ -1285,7 +1305,9 @@ def test_get_active_cloud_assignment(self, mock_get): result = self.api.get_active_cloud_assignment(cloud_name) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith(f"/assignments/active/{cloud_name}") + assert str(mock_get.call_args[0][1]).endswith( + f"/assignments/active/{cloud_name}" + ) assert result == expected_response @patch("requests.Session.request") @@ -1371,7 +1393,9 @@ def test_remove_interface(self, mock_delete): result = self.api.remove_interface(hostname, if_name) mock_delete.assert_called_once() - assert str(mock_delete.call_args[0][1]).endswith(f"/interfaces/{hostname}/{if_name}") + assert str(mock_delete.call_args[0][1]).endswith( + f"/interfaces/{hostname}/{if_name}" + ) assert result == {} @patch("requests.Session.request") @@ -1652,7 +1676,11 @@ def test_get_moves(self, mock_get): @patch("requests.Session.request") def test_get_moves_with_date(self, mock_get): date = "2024-03-20" - expected_response = {"moves": [{"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"}]} + expected_response = { + "moves": [ + {"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"} + ] + } mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1734,7 +1762,9 @@ def test_create_self_assignment(self, mock_post): with patch.object(self.api, "post") as mock_post: mock_post.return_value = expected_response result = self.api.create_self_assignment(test_data) - mock_post.assert_called_once_with(str(Path("assignments") / "self"), test_data) + mock_post.assert_called_once_with( + str(Path("assignments") / "self"), test_data + ) assert result == expected_response @patch("requests.Session.request") @@ -1910,7 +1940,9 @@ def test_create_self_assignment_logging(self, mock_request, mock_print): self.api.create_self_assignment(assignment_data) - mock_print.assert_called_once_with("Self-assignment created - ID: 123, Cloud: cloud1") + mock_print.assert_called_once_with( + "Self-assignment created - ID: 123, Cloud: cloud1" + ) @patch("builtins.print") @patch("requests.Session.request") @@ -1967,7 +1999,9 @@ def setup(self): @pytest.fixture def quads_base(self): - return QuadsBase(username=self.username, password=self.password, base_url=self.base_url) + return QuadsBase( + username=self.username, password=self.password, base_url=self.base_url + ) def test_context_manager_enter(self, quads_base): quads_base.login = Mock() From 93163f87e3816f53bdfd5997c85cd1b10bb4f348 Mon Sep 17 00:00:00 2001 From: Will Foster Date: Thu, 7 May 2026 22:05:53 +0100 Subject: [PATCH 4/7] chore: ruff formatting --- setup.py | 8 ++---- src/quads_lib/base.py | 4 +-- src/quads_lib/quads.py | 8 ++---- tests/test_quads.py | 58 +++++++++--------------------------------- 4 files changed, 17 insertions(+), 61 deletions(-) diff --git a/setup.py b/setup.py index d2782fd..8d30111 100755 --- a/setup.py +++ b/setup.py @@ -7,9 +7,7 @@ def read(*names, **kwargs): - with Path(__file__).parent.joinpath(*names).open( - encoding=kwargs.get("encoding", "utf8") - ) as fh: + with Path(__file__).parent.joinpath(*names).open(encoding=kwargs.get("encoding", "utf8")) as fh: return fh.read() @@ -19,9 +17,7 @@ def read(*names, **kwargs): license="LGPL-3.0-only", description="Python client library for interacting with the QUADS API", long_description="{}\n{}".format( - re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub( - "", read("README.rst") - ), + re.compile("^.. start-badges.*^.. end-badges", re.M | re.S).sub("", read("README.rst")), re.sub(":[a-z]+:`~?(.*?)`", r"``\1``", read("CHANGELOG.rst")), ), author="Gonzalo Rafuls", diff --git a/src/quads_lib/base.py b/src/quads_lib/base.py index 4a1f130..06a5ea6 100644 --- a/src/quads_lib/base.py +++ b/src/quads_lib/base.py @@ -56,9 +56,7 @@ def __exit__(self, exc_type, exc_value, traceback): self.logout() self.session.close() - def _make_request( - self, method: str, endpoint: str, data: Optional[dict] = None - ) -> dict: + def _make_request(self, method: str, endpoint: str, data: Optional[dict] = None) -> dict: full_url = urljoin(self.base_url, endpoint) _response = self.session.request( method, diff --git a/src/quads_lib/quads.py b/src/quads_lib/quads.py index 9119d15..4e2501e 100644 --- a/src/quads_lib/quads.py +++ b/src/quads_lib/quads.py @@ -208,9 +208,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']}, Cloud: {response['cloud']['name']}" - ) + print(f"Assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") return response @returns("Assignment") @@ -218,9 +216,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']}, Cloud: {response['cloud']['name']}" - ) + print(f"Self-assignment created - ID: {response['id']}, Cloud: {response['cloud']['name']}") return response @returns("Assignment") diff --git a/tests/test_quads.py b/tests/test_quads.py index 1e44420..66ca2a2 100644 --- a/tests/test_quads.py +++ b/tests/test_quads.py @@ -933,11 +933,7 @@ def test_get_schedules(self, mock_get): @patch("requests.Session.request") def test_get_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1", "start": "2024-03-20"} - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -945,20 +941,14 @@ def test_get_schedules_with_params(self, mock_get): result = self.api.get_schedules(query_data) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith( - "/schedules?cloud=cloud1&start=2024-03-20" - ) or str(mock_get.call_args[0][1]).endswith( + assert str(mock_get.call_args[0][1]).endswith("/schedules?cloud=cloud1&start=2024-03-20") or str(mock_get.call_args[0][1]).endswith( "/schedules?start=2024-03-20&cloud=cloud1" ) assert result == expected_response @patch("requests.Session.request") def test_get_current_schedules(self, mock_get): - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -972,11 +962,7 @@ def test_get_current_schedules(self, mock_get): @patch("requests.Session.request") def test_get_current_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1055,11 +1041,7 @@ def test_get_future_schedules(self, mock_get): @patch("requests.Session.request") def test_get_future_schedules_with_params(self, mock_get): query_data = {"cloud": "cloud1"} - expected_response = { - "schedules": [ - {"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"} - ] - } + expected_response = {"schedules": [{"id": 1, "cloud": "cloud1", "start": "2024-03-20", "end": "2024-03-21"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1238,9 +1220,7 @@ def test_update_notification(self, mock_patch): result = self.api.update_notification(notification_id, update_data) mock_patch.assert_called_once() - assert str(mock_patch.call_args[0][1]).endswith( - f"/notifications/{notification_id}" - ) + assert str(mock_patch.call_args[0][1]).endswith(f"/notifications/{notification_id}") assert mock_patch.call_args[1]["json"] == update_data assert result == update_data @@ -1305,9 +1285,7 @@ def test_get_active_cloud_assignment(self, mock_get): result = self.api.get_active_cloud_assignment(cloud_name) mock_get.assert_called_once() - assert str(mock_get.call_args[0][1]).endswith( - f"/assignments/active/{cloud_name}" - ) + assert str(mock_get.call_args[0][1]).endswith(f"/assignments/active/{cloud_name}") assert result == expected_response @patch("requests.Session.request") @@ -1393,9 +1371,7 @@ def test_remove_interface(self, mock_delete): result = self.api.remove_interface(hostname, if_name) mock_delete.assert_called_once() - assert str(mock_delete.call_args[0][1]).endswith( - f"/interfaces/{hostname}/{if_name}" - ) + assert str(mock_delete.call_args[0][1]).endswith(f"/interfaces/{hostname}/{if_name}") assert result == {} @patch("requests.Session.request") @@ -1676,11 +1652,7 @@ def test_get_moves(self, mock_get): @patch("requests.Session.request") def test_get_moves_with_date(self, mock_get): date = "2024-03-20" - expected_response = { - "moves": [ - {"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"} - ] - } + expected_response = {"moves": [{"id": 1, "host": "host1", "from_cloud": "cloud1", "to_cloud": "cloud2"}]} mock_response = Mock() mock_response.json.return_value = expected_response mock_get.return_value = mock_response @@ -1762,9 +1734,7 @@ def test_create_self_assignment(self, mock_post): with patch.object(self.api, "post") as mock_post: mock_post.return_value = expected_response result = self.api.create_self_assignment(test_data) - mock_post.assert_called_once_with( - str(Path("assignments") / "self"), test_data - ) + mock_post.assert_called_once_with(str(Path("assignments") / "self"), test_data) assert result == expected_response @patch("requests.Session.request") @@ -1940,9 +1910,7 @@ def test_create_self_assignment_logging(self, mock_request, mock_print): self.api.create_self_assignment(assignment_data) - mock_print.assert_called_once_with( - "Self-assignment created - ID: 123, Cloud: cloud1" - ) + mock_print.assert_called_once_with("Self-assignment created - ID: 123, Cloud: cloud1") @patch("builtins.print") @patch("requests.Session.request") @@ -1999,9 +1967,7 @@ def setup(self): @pytest.fixture def quads_base(self): - return QuadsBase( - username=self.username, password=self.password, base_url=self.base_url - ) + return QuadsBase(username=self.username, password=self.password, base_url=self.base_url) def test_context_manager_enter(self, quads_base): quads_base.login = Mock() From f7d50a4cd476acc9f321df5e8c64663db63785bf Mon Sep 17 00:00:00 2001 From: Will Foster Date: Thu, 7 May 2026 22:08:26 +0100 Subject: [PATCH 5/7] fix: better black + ruff interop --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1426e1e..f2a6d7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,8 @@ ignore = [ "E501", # pycodestyle line-too-long "S105", # Possible hardcoded password "S106", # Hardcoded password + "COM812", # conflicts with black + "ISC001", # conflicts with black ] select = [ "B", # flake8-bugbear From da1a2617c3d0ab467e324a41abb398126818f00b Mon Sep 17 00:00:00 2001 From: Will Foster Date: Thu, 7 May 2026 22:14:42 +0100 Subject: [PATCH 6/7] add mention of RPM too README [publish] --- README.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.rst b/README.rst index 334d729..5995327 100644 --- a/README.rst +++ b/README.rst @@ -58,6 +58,14 @@ You can also install the in-development version with:: pip install https://github.com/quadsproject/python-quads-lib/archive/development.zip +RPM Installation +---------------- + +For Red Hat-based distributions (RHEL, Rocky, Fedora):: + + dnf copr enable quadsdev/python3-quads -y + dnf install quads-lib + Documentation ============= From 4661341e6e981282fe723c12ab684684627d7844 Mon Sep 17 00:00:00 2001 From: Will Foster Date: Thu, 7 May 2026 22:17:01 +0100 Subject: [PATCH 7/7] chore: doc update --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5995327..03fe882 100644 --- a/README.rst +++ b/README.rst @@ -61,7 +61,7 @@ You can also install the in-development version with:: RPM Installation ---------------- -For Red Hat-based distributions (RHEL, Rocky, Fedora):: +For Fedora Linux:: dnf copr enable quadsdev/python3-quads -y dnf install quads-lib