diff --git a/app/base_api.py b/app/base_api.py index bb36523..ad56870 100644 --- a/app/base_api.py +++ b/app/base_api.py @@ -1,6 +1,10 @@ import httpx class BaseAPI(): + def __init__(self): + self._client = httpx.AsyncClient() + self.access_token = None # Placeholder, will be set by derived class or external config + async def __aenter__(self): """Allows using the client with async with.""" return self @@ -11,68 +15,29 @@ class BaseAPI(): async def close(self): """Closes the underlying asynchronous HTTP client.""" - await self._client.close() + if self._client: # Ensure _client exists before trying to close + await self._client.close() async def _post(self, endpoint: str, data: dict = None): """ Asynchronous helper method for making POST requests. - - Args: - endpoint: The API endpoint (e.g., "/op25/start"). - data: The data to send in the request body (as a dictionary). - - Returns: - The JSON response from the API. - - Raises: - httpx.RequestError: If the request fails. - httpx.HTTPStatusError: If the API returns an error status code (4xx or 5xx). + This method will now implicitly use the _request method, + so authentication logic is centralized. """ - url = f"{self.base_url}{endpoint}" - try: - # Use await with the asynchronous httpx client - response = await self._client.post(url, json=data) - response.raise_for_status() # Raise HTTPStatusError for bad responses (4xx or 5xx) - return response.json() - except httpx.HTTPStatusError as e: - print(f"HTTP error occurred: {e}") - print(f"Response body: {e.response.text}") # Access response text from the exception - raise - except httpx.RequestError as e: - print(f"Request to '{url}' failed: {e}") - raise + return await self._request("POST", endpoint, json=data) async def _get(self, endpoint: str): """ Asynchronous helper method for making GET requests. - - Args: - endpoint: The API endpoint (e.g., "/op25/status"). - - Returns: - The JSON response from the API. - - Raises: - httpx.RequestError: If the request fails. - httpx.HTTPStatusError: If the API returns an error status code (4xx or 5xx). + This method will now implicitly use the _request method, + so authentication logic is centralized. """ - url = f"{self.base_url}{endpoint}" - try: - # Use await with the asynchronous httpx client - response = await self._client.get(url) - response.raise_for_status() # Raise HTTPStatusError for bad responses (4xx or 5xx) - return response.json() - except httpx.HTTPStatusError as e: - print(f"HTTP error occurred: {e}") - print(f"Response body: {e.response.text}") # Access response text from the exception - raise - except httpx.RequestError as e: - print(f"Request to '{url}' failed: {e}") - raise + return await self._request("GET", endpoint) async def _request(self, method, endpoint, **kwargs): """ Helper method to make an asynchronous HTTP request. + This is where the access_token will be injected. Args: method (str): The HTTP method (e.g., 'GET', 'POST'). @@ -86,15 +51,20 @@ class BaseAPI(): httpx.HTTPStatusError: If the request returns a non-2xx status code. httpx.RequestError: For other request-related errors. """ - url = f"{self.base_url}{endpoint}" + url = f"{self.base_url}{endpoint}" # base_url must be defined in derived class or passed to BaseAPI + + headers = kwargs.pop("headers", {}) + if self.access_token: # Check if access_token is set + headers["Authorization"] = f"Bearer {self.access_token}" + kwargs["headers"] = headers + try: response = await self._client.request(method, url, **kwargs) response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx) return response.json() except httpx.HTTPStatusError as e: print(f"HTTP error occurred: {e}") - # You might want to return the error response body or raise the exception raise except httpx.RequestError as e: print(f"An error occurred while requesting {e.request.url!r}: {e}") - raise + raise \ No newline at end of file diff --git a/app/client.py b/app/client.py index 203333a..c69f225 100644 --- a/app/client.py +++ b/app/client.py @@ -20,6 +20,9 @@ CLIENT_API_URL = os.getenv("CLIENT_API_URL", "http://localhost:8001") if not app_conf.get("client_id"): app_conf.set("client_id", f"client-{uuid.uuid4().hex[:8]}") CLIENT_ID = app_conf.client_id + +# Get the nickname (even if it's empty) +NICKNAME = app_conf.get("nickname") # ---------------------------- # Dictionary mapping command names (strings) to local client functions @@ -222,6 +225,9 @@ async def receive_commands(websocket): else: print(f"Received unknown command: {command_name}") elif data.get("type") == "handshake_ack": + # Set the session token + app_conf.set(data.get("access_token")) + print(f"Server acknowledged handshake.") else: print(f"Received unknown message type: {data.get('type')}") @@ -242,7 +248,7 @@ async def main_client(): print("Connection established.") # Handshake: Send client ID immediately after connecting - handshake_message = json.dumps({"type": "handshake", "id": CLIENT_ID}) + handshake_message = json.dumps({"type": "handshake", "id": CLIENT_ID, "nickname": NICKNAME}) await websocket.send(handshake_message) print(f"Sent handshake with ID: {CLIENT_ID}") diff --git a/app/server_api.py b/app/server_api.py index 9a3566a..fbc51a0 100644 --- a/app/server_api.py +++ b/app/server_api.py @@ -2,6 +2,7 @@ import httpx import json from base_api import BaseAPI from config import Config +import asyncio # Add this import for the example usage app_config = Config() @@ -19,8 +20,9 @@ class RadioAPIClient(BaseAPI): """ super().__init__() self.base_url = base_url - # Use an AsyncClient for making asynchronous requests self._client = httpx.AsyncClient() + # Set the access token on the BaseAPI instance + self.access_token = app_config.get("access_token") # Or app_config.access_token async def get_systems(self): """ @@ -161,5 +163,4 @@ if __name__ == "__main__": # 1. Ensure the server (server.py) is running. # 2. Ensure at least one client (client.py) is running. # 3. Run this script: python api_client.py - asyncio.run(example_api_usage()) - + asyncio.run(example_api_usage()) \ No newline at end of file