feat(api-client): implement session management for AdvowareAPI and EspoCRMAPI

This commit is contained in:
bsiggel
2026-03-03 17:24:35 +00:00
parent 69a48f5f9a
commit a53051ea8e
2 changed files with 201 additions and 176 deletions

View File

@@ -73,6 +73,17 @@ class AdvowareAPI:
self.logger.info("AdvowareAPI initialized")
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession()
return self._session
async def close(self) -> None:
if self._session and not self._session.closed:
await self._session.close()
def _generate_hmac(self, request_time_stamp: str, nonce: Optional[str] = None) -> str:
"""Generate HMAC-SHA512 signature for authentication"""
if not nonce:
@@ -260,77 +271,79 @@ class AdvowareAPI:
# Use 'data' parameter if provided, otherwise 'json_data'
json_payload = data if data is not None else json_data
async with aiohttp.ClientSession(timeout=effective_timeout) as session:
try:
with self.logger.api_call(endpoint, method):
async with session.request(
method,
url,
headers=effective_headers,
params=params,
json=json_payload
) as response:
# Handle 401 - retry with fresh token
if response.status == 401:
self.logger.warning("401 Unauthorized, refreshing token")
token = self.get_access_token(force_refresh=True)
effective_headers['Authorization'] = f'Bearer {token}'
async with session.request(
method,
url,
headers=effective_headers,
params=params,
json=json_payload
) as retry_response:
if retry_response.status == 401:
raise AdvowareAuthError(
"Authentication failed even after token refresh",
status_code=401
)
if retry_response.status >= 500:
error_text = await retry_response.text()
raise RetryableError(
f"Server error {retry_response.status}: {error_text}"
)
retry_response.raise_for_status()
return await self._parse_response(retry_response)
# Handle other error codes
if response.status == 404:
error_text = await response.text()
raise AdvowareAPIError(
f"Resource not found: {endpoint}",
status_code=404,
response_body=error_text
)
if response.status >= 500:
error_text = await response.text()
raise RetryableError(
f"Server error {response.status}: {error_text}"
)
if response.status >= 400:
error_text = await response.text()
raise AdvowareAPIError(
f"API error {response.status}: {error_text}",
status_code=response.status,
response_body=error_text
)
return await self._parse_response(response)
except asyncio.TimeoutError:
raise AdvowareTimeoutError(
f"Request timed out after {effective_timeout.total}s",
status_code=408
)
except aiohttp.ClientError as e:
self.logger.error(f"API call failed: {e}")
raise AdvowareAPIError(f"Request failed: {str(e)}")
session = await self._get_session()
try:
with self.logger.api_call(endpoint, method):
async with session.request(
method,
url,
headers=effective_headers,
params=params,
json=json_payload,
timeout=effective_timeout
) as response:
# Handle 401 - retry with fresh token
if response.status == 401:
self.logger.warning("401 Unauthorized, refreshing token")
token = self.get_access_token(force_refresh=True)
effective_headers['Authorization'] = f'Bearer {token}'
async with session.request(
method,
url,
headers=effective_headers,
params=params,
json=json_payload,
timeout=effective_timeout
) as retry_response:
if retry_response.status == 401:
raise AdvowareAuthError(
"Authentication failed even after token refresh",
status_code=401
)
if retry_response.status >= 500:
error_text = await retry_response.text()
raise RetryableError(
f"Server error {retry_response.status}: {error_text}"
)
retry_response.raise_for_status()
return await self._parse_response(retry_response)
# Handle other error codes
if response.status == 404:
error_text = await response.text()
raise AdvowareAPIError(
f"Resource not found: {endpoint}",
status_code=404,
response_body=error_text
)
if response.status >= 500:
error_text = await response.text()
raise RetryableError(
f"Server error {response.status}: {error_text}"
)
if response.status >= 400:
error_text = await response.text()
raise AdvowareAPIError(
f"API error {response.status}: {error_text}",
status_code=response.status,
response_body=error_text
)
return await self._parse_response(response)
except asyncio.TimeoutError:
raise AdvowareTimeoutError(
f"Request timed out after {effective_timeout.total}s",
status_code=408
)
except aiohttp.ClientError as e:
self.logger.error(f"API call failed: {e}")
raise AdvowareAPIError(f"Request failed: {str(e)}")
async def _parse_response(self, response: aiohttp.ClientResponse) -> Any:
"""Parse API response"""