11"""COMCheck API service module for making HTTP requests to the COMCheck API."""
22
3- """Note: Service layer accepts raw data types (dicts) as inputs to stay simple and
4- HTTP-library-friendly, but returns validated Pydantic models to provide type safety
3+ """Note: Service layer accepts raw data types (dicts) as inputs to stay simple and
4+ HTTP-library-friendly, but returns validated Pydantic models to provide type safety
55and catch API schema mismatches at the boundary."""
66
7- from typing import Any , Dict , Optional
7+ import logging
8+ import os
9+ from typing import Any , Dict , NoReturn , Optional
810
911import httpx
1012
13+ from comcheck_api .exceptions import (
14+ COMCheckHTTPError ,
15+ COMCheckConnectionError ,
16+ COMCheckValidationError ,
17+ )
1118from comcheck_api .types .api_types import (
1219 RunSimulationResponse ,
1320 SimulationStatusResponse ,
1825class COMCheckApiService :
1926 """COMCheck API service class for interacting with the COM API."""
2027
21- BASE_URL : str = "https://becp-dev.pnl.gov/ahj/COM" # COM API base URL
22-
2328 def __init__ (self , api_key : str ) -> None :
2429 """Initialize COMCheck API service.
2530
@@ -35,6 +40,9 @@ def __init__(self, api_key: str) -> None:
3540 "or set it in your environment variables."
3641 )
3742 self .api_key = api_key
43+ self .base_url : str = os .getenv (
44+ "COMCHECK_API_URL" , "https://becp-dev.pnl.gov/ahj/COM"
45+ )
3846 self ._client : Optional [httpx .Client ] = None
3947
4048 def _get_client (self ) -> httpx .Client :
@@ -45,7 +53,7 @@ def _get_client(self) -> httpx.Client:
4553 """
4654 if self ._client is None :
4755 self ._client = httpx .Client (
48- base_url = self .BASE_URL ,
56+ base_url = self .base_url ,
4957 headers = self ._prepare_headers (),
5058 timeout = 30.0 ,
5159 )
@@ -63,22 +71,48 @@ def _prepare_headers(self) -> Dict[str, str]:
6371 "Accept" : "application/json" ,
6472 }
6573
66- def _handle_api_error (self , error : Exception ) -> None :
67- """Handle API errors with detailed logging.
74+ def _handle_api_error (self , error : Exception ) -> NoReturn :
75+ """Handle API errors with detailed logging and raise custom exceptions .
6876
6977 Args:
7078 error: The error object to handle
79+
80+ Raises:
81+ COMCheckHTTPError: For HTTP status errors
82+ COMCheckConnectionError: For connection/request errors
83+ COMCheckValidationError: For validation errors
7184 """
85+ logger = logging .getLogger (__name__ )
86+
7287 if isinstance (error , httpx .HTTPStatusError ):
73- print (f"HTTP error occurred: { error } " )
74- print (f"Status: { error .response .status_code } " )
75- print (f"Response data: { error .response .text } " )
76- print (f"Response headers: { error .response .headers } " )
88+ logger .error (
89+ "HTTP error occurred: %s (Status: %s)" ,
90+ error ,
91+ error .response .status_code ,
92+ exc_info = True ,
93+ extra = {
94+ "response_data" : error .response .text ,
95+ "response_headers" : dict (error .response .headers ),
96+ },
97+ )
98+ raise COMCheckHTTPError (
99+ status_code = error .response .status_code ,
100+ message = error .response .reason_phrase ,
101+ response_data = error .response .text ,
102+ ) from error
77103 elif isinstance (error , httpx .RequestError ):
78- print (f"Request error occurred: { error } " )
79- print (f"Request: { error .request } " )
104+ logger .error (
105+ "Request error occurred: %s" ,
106+ error ,
107+ exc_info = True ,
108+ extra = {"request" : str (error .request )},
109+ )
110+ raise COMCheckConnectionError (
111+ f"Failed to connect to COMcheck API: { str (error )} "
112+ ) from error
80113 else :
81- print (f"Unexpected error: { error } " )
114+ logger .error ("Unexpected error: %s" , error , exc_info = True )
115+ raise
82116
83117 def get_project (self , project_id : str ) -> Dict [str , Any ]:
84118 """Get a single project by ID.
@@ -90,8 +124,8 @@ def get_project(self, project_id: str) -> Dict[str, Any]:
90124 API response data as dictionary
91125
92126 Raises:
93- httpx.HTTPStatusError : If the API returns an error status
94- httpx.RequestError : If the request fails
127+ COMCheckHTTPError : If the API returns an error status
128+ COMCheckConnectionError : If the request fails
95129 """
96130 try :
97131 client = self ._get_client ()
@@ -100,7 +134,6 @@ def get_project(self, project_id: str) -> Dict[str, Any]:
100134 return response .json ()
101135 except Exception as error :
102136 self ._handle_api_error (error )
103- raise
104137
105138 def get_project_list (self ) -> Dict [str , Any ]:
106139 """Get a list of all projects.
@@ -109,8 +142,8 @@ def get_project_list(self) -> Dict[str, Any]:
109142 API response data as dictionary
110143
111144 Raises:
112- httpx.HTTPStatusError : If the API returns an error status
113- httpx.RequestError : If the request fails
145+ COMCheckHTTPError : If the API returns an error status
146+ COMCheckConnectionError : If the request fails
114147 """
115148 try :
116149 client = self ._get_client ()
@@ -119,7 +152,6 @@ def get_project_list(self) -> Dict[str, Any]:
119152 return response .json ()
120153 except Exception as error :
121154 self ._handle_api_error (error )
122- raise
123155
124156 def update_project (
125157 self , project_id : str , project_data : Dict [str , Any ]
@@ -134,8 +166,8 @@ def update_project(
134166 API response data as dictionary
135167
136168 Raises:
137- httpx.HTTPStatusError : If the API returns an error status
138- httpx.RequestError : If the request fails
169+ COMCheckHTTPError : If the API returns an error status
170+ COMCheckConnectionError : If the request fails
139171 """
140172 try :
141173 client = self ._get_client ()
@@ -144,7 +176,6 @@ def update_project(
144176 return response .json ()
145177 except Exception as error :
146178 self ._handle_api_error (error )
147- raise
148179
149180 def start_run_simulation (
150181 self , project_data : Dict [str , Any ]
@@ -158,8 +189,8 @@ def start_run_simulation(
158189 RunSimulationResponse with session information
159190
160191 Raises:
161- httpx.HTTPStatusError : If the API returns an error status
162- httpx.RequestError : If the request fails
192+ COMCheckHTTPError : If the API returns an error status
193+ COMCheckConnectionError : If the request fails
163194 """
164195 try :
165196 client = self ._get_client ()
@@ -170,7 +201,6 @@ def start_run_simulation(
170201 return RunSimulationResponse .model_construct (** response .json ())
171202 except Exception as error :
172203 self ._handle_api_error (error )
173- raise
174204
175205 def get_simulation_status (self , session_id : str ) -> SimulationStatusResponse :
176206 """Get status of a simulation.
@@ -182,8 +212,8 @@ def get_simulation_status(self, session_id: str) -> SimulationStatusResponse:
182212 SimulationStatusResponse with status information
183213
184214 Raises:
185- httpx.HTTPStatusError : If the API returns an error status
186- httpx.RequestError : If the request fails
215+ COMCheckHTTPError : If the API returns an error status
216+ COMCheckConnectionError : If the request fails
187217 """
188218 try :
189219 client = self ._get_client ()
@@ -194,7 +224,6 @@ def get_simulation_status(self, session_id: str) -> SimulationStatusResponse:
194224 return SimulationStatusResponse .model_construct (** response .json ())
195225 except Exception as error :
196226 self ._handle_api_error (error )
197- raise
198227
199228 def get_simulation_result (self , session_id : str ) -> SimulationResultResponse :
200229 """Get result of a simulation.
@@ -206,8 +235,8 @@ def get_simulation_result(self, session_id: str) -> SimulationResultResponse:
206235 SimulationResultResponse with simulation results
207236
208237 Raises:
209- httpx.HTTPStatusError : If the API returns an error status
210- httpx.RequestError : If the request fails
238+ COMCheckHTTPError : If the API returns an error status
239+ COMCheckConnectionError : If the request fails
211240 """
212241 try :
213242 client = self ._get_client ()
@@ -218,7 +247,6 @@ def get_simulation_result(self, session_id: str) -> SimulationResultResponse:
218247 return SimulationResultResponse .model_construct (** response .json ())
219248 except Exception as error :
220249 self ._handle_api_error (error )
221- raise
222250
223251 def close (self ) -> None :
224252 """Close the HTTP client connection."""
0 commit comments