import requests import json import os class CoreAPI: def _handle_response(self, response): """ Handles the API response, checking for successful status codes and raising exceptions for errors. Args: response (requests.Response): The response object from a requests call. Returns: dict or list: The JSON response body if the request was successful. Raises: requests.exceptions.HTTPError: If the response status code indicates an error. requests.exceptions.RequestException: For other request-related errors. json.JSONDecodeError: If the response body is not valid JSON. """ try: response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) if response.content: return response.json() return None # Handle cases with no response body (e.g., 204 No Content) except requests.exceptions.RequestException as e: print(f"Request failed: {e}") raise except json.JSONDecodeError: print(f"Failed to decode JSON from response: {response.text}") raise class NodeAPIWrapper(CoreAPI): """ A wrapper class for interacting with the Node API endpoints. """ def __init__(self): """ Initializes the API wrapper with the base URL of the Quart application. Args: base_url (str): The base URL of your Quart application (e.g., "http://localhost:5000"). """ self.base_url = f"{os.getenv('API_BASE_URL', 'http://localhost:5000')}/nodes" async def get_nodes(self) -> requests.Response: """ API endpoint to list currently connected client IDs. Returns: requests.Response: The response object from the API. On success (200), response.json() will be a list of client IDs. """ url = self.base_url try: response = requests.get(url) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error creating system: {e}") raise async def join(self, client_id: str, system_id: str, guild_id: str, channel_id: str) -> requests.Response: """ Send a join command to the specific system specified. Args: client_id (str): The ID of the client to send the command to. system_id (str): The system ID for the join command. guild_id (str): The guild ID for the join command. channel_id (str): The channel ID for the join command. Returns: requests.Response: The response object from the API. On success (200), response.json() will contain status information. """ url = f"{self.base_url}/join" payload = { "client_id": client_id, "system_id": system_id, "guild_id": guild_id, "channel_id": channel_id } try: response = requests.post(url, json=payload) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error creating system: {e}") raise async def leave(self, client_id: str, guild_id: str) -> requests.Response: """ Send a leave command to the specific client. Args: client_id (str): The ID of the client to send the command to. guild_id (str): The guild ID for the leave command. Returns: requests.Response: The response object from the API. On success (200), response.json() will contain status information. """ url = f"{self.base_url}/leave" payload = { "client_id": client_id, "guild_id": guild_id } try: response = requests.post(url, json=payload) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error creating system: {e}") raise class SystemAPIWrapper(CoreAPI): """ A Python wrapper class for interacting with the System API endpoints using the requests library. """ def __init__(self): """ Initializes the SystemAPIWrapper with the base URL of the API. Args: base_url (str): The base URL of the System API (e.g., "http://localhost:5000/systems"). """ self.base_url = f"{os.getenv('API_BASE_URL', 'http://localhost:5000')}/systems" async def create_system(self, system_data): """ Creates a new system. Args: system_data (dict): A dictionary containing the system data. Returns: dict: The created system object. Raises: requests.exceptions.RequestException: If the request fails. json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/" try: response = requests.post(url, json=system_data) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error creating system: {e}") raise async def list_systems(self): """ Retrieves a list of all systems. Returns: list: A list of system objects. Raises: requests.exceptions.RequestException: If the request fails. json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/" try: response = requests.get(url) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error listing systems: {e}") raise async def get_system_by_id(self, system_id): """ Retrieves details for a specific system by ID. Args: system_id (str): The ID of the system. Returns: dict: The system object. Raises: requests.exceptions.RequestException: If the request fails (e.g., 404 if not found). json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/{system_id}" try: response = requests.get(url) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error getting system by ID {system_id}: {e}") raise async def get_systems_by_client_id(self, client_id): """ Retrieves a list of systems available on a specific client ID. Args: client_id (str): The ID of the client. Returns: list: A list of system objects. Raises: requests.exceptions.RequestException: If the request fails (e.g., 404 if client not found or no systems). json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/client/{client_id}" try: response = requests.get(url) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error getting systems by client ID {client_id}: {e}") raise async def update_system(self, system_id, updated_system_data): """ Updates a specific system by ID. Args: system_id (str): The ID of the system to update. updated_system_data (dict): A dictionary containing the updated system data. Returns: dict: The updated system object. Raises: requests.exceptions.RequestException: If the request fails. json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/{system_id}" try: response = requests.put(url, json=updated_system_data) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error updating system {system_id}: {e}") raise async def delete_system(self, system_id): """ Deletes a specific system by ID. Args: system_id (str): The ID of the system to delete. Returns: dict: The deleted system object. Raises: requests.exceptions.RequestException: If the request fails. json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/{system_id}" try: response = requests.delete(url) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error deleting system {system_id}: {e}") raise async def search_systems(self, **kwargs): """ Searches for systems based on provided query parameters. Args: **kwargs: Keyword arguments representing the query parameters (e.g., name="MySystem", frequency_khz=1000). Returns: list: A list of system objects matching the criteria. Raises: requests.exceptions.RequestException: If the request fails. json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/search" try: # requests.get automatically handles the params dictionary response = requests.get(url, params=kwargs) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error searching systems with parameters {kwargs}: {e}") raise async def assign_client_to_system(self, system_id, client_id): """ Assigns a client ID to a system's available_on_nodes list. Args: system_id (str): The ID of the system. client_id (str): The ID of the client to assign. Returns: dict: A dictionary containing the status and the updated system object. Raises: requests.exceptions.RequestException: If the request fails. json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/{system_id}/assign" payload = {"client_id": client_id} try: response = requests.post(url, json=payload) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error assigning client {client_id} to system {system_id}: {e}") raise async def dismiss_client_from_system(self, system_id, client_id): """ Dismisses (removes) a client ID from a system's available_on_nodes list. Args: system_id (str): The ID of the system. client_id (str): The ID of the client to dismiss. Returns: dict: A dictionary containing the status and the updated system object. Raises: requests.exceptions.RequestException: If the request fails. json.JSONDecodeError: If the response is not valid JSON. """ url = f"{self.base_url}/{system_id}/dismiss" payload = {"client_id": client_id} try: response = requests.post(url, json=payload) return self._handle_response(response) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f"Error dismissing client {client_id} from system {system_id}: {e}") raise # Example Usage (assuming your Quart app is running on http://localhost:5000) if __name__ == '__main__': api = SystemAPIWrapper("http://localhost:5000/systems") # Example: Create a new system new_system_data = { "name": "Test System", "frequency_khz": 100, "avail_on_nodes": [] } try: created_system = api.create_system(new_system_data) print("Created System:", created_system) except requests.exceptions.HTTPError as e: print(f"Failed to create system: {e.response.status_code} - {e.response.text}") except Exception as e: print(f"An unexpected error occurred: {e}") # Example: List all systems try: all_systems = api.list_systems() print("\nAll Systems:", all_systems) except requests.exceptions.RequestException as e: print(f"Failed to list systems: {e}") # Example: Get a system by ID (replace with a known system ID) system_id_to_get = "some_system_id" # Replace with an actual ID try: system = api.get_system_by_id(system_id_to_get) print(f"\nSystem with ID {system_id_to_get}:", system) except requests.exceptions.HTTPError as e: print(f"Failed to get system by ID {system_id_to_get}: {e.response.status_code} - {e.response.text}") except Exception as e: print(f"An unexpected error occurred: {e}") # Example: Get systems by client ID (replace with a known client ID) client_id_to_get = "some_client_id" # Replace with an actual ID try: client_systems = api.get_systems_by_client_id(client_id_to_get) print(f"\nSystems for client ID {client_id_to_get}:", client_systems) except requests.exceptions.HTTPError as e: print(f"Failed to get systems by client ID {client_id_to_get}: {e.response.status_code} - {e.response.text}") except Exception as e: print(f"An unexpected error occurred: {e}") # Example: Update a system (replace with a known system ID) system_id_to_update = "some_system_id" # Replace with an actual ID updated_data = {"frequency_khz": 120} try: updated_system = api.update_system(system_id_to_update, updated_data) print(f"\nUpdated system {system_id_to_update}:", updated_system) except requests.exceptions.HTTPError as e: print(f"Failed to update system {system_id_to_update}: {e.response.status_code} - {e.response.text}") except Exception as e: print(f"An unexpected error occurred: {e}") # Example: Assign a client to a system (replace with known IDs) system_id_to_assign = "some_system_id" # Replace with an actual ID client_id_to_assign = "new_client_1" # Replace with an actual ID try: assign_result = api.assign_client_to_system(system_id_to_assign, client_id_to_assign) print(f"\nAssign client {client_id_to_assign} to system {system_id_to_assign} result:", assign_result) except requests.exceptions.HTTPError as e: print(f"Failed to assign client to system: {e.response.status_code} - {e.response.text}") except Exception as e: print(f"An unexpected error occurred: {e}") # Example: Dismiss a client from a system (replace with known IDs) system_id_to_dismiss = "some_system_id" # Replace with an actual ID client_id_to_dismiss = "new_client_1" # Replace with an actual ID try: dismiss_result = api.dismiss_client_from_system(system_id_to_dismiss, client_id_to_dismiss) print(f"\nDismiss client {client_id_to_dismiss} from system {system_id_to_dismiss} result:", dismiss_result) except requests.exceptions.HTTPError as e: print(f"Failed to dismiss client from system: {e.response.status_code} - {e.response.text}") except Exception as e: print(f"An unexpected error occurred: {e}") # Example: Delete a system (replace with a known system ID) system_id_to_delete = "some_system_id" # Replace with an actual ID try: deleted_system = api.delete_system(system_id_to_delete) print(f"\nDeleted system {system_id_to_delete}:", deleted_system) except requests.exceptions.HTTPError as e: print(f"Failed to delete system {system_id_to_delete}: {e.response.status_code} - {e.response.text}") except Exception as e: print(f"An unexpected error occurred: {e}")