Spaces:
Sleeping
Sleeping
| """ | |
| HTTP μμ² μ²λ¦¬λ₯Ό μν μ νΈλ¦¬ν° λͺ¨λ | |
| """ | |
| import time | |
| import logging | |
| import requests | |
| from typing import Dict, Any, Optional, Union | |
| from src.utils.exceptions import APIError | |
| from src import config | |
| logger = config.setup_logger("http_client") | |
| class HTTPClient: | |
| """ | |
| HTTP μμ²μ μ²λ¦¬νλ ν΄λΌμ΄μΈνΈ ν΄λμ€ | |
| μ¬μλ λ‘μ§, μλ¬ μ²λ¦¬ λ±μ κ³΅ν΅ κΈ°λ₯μ μ 곡ν©λλ€. | |
| """ | |
| def __init__( | |
| self, | |
| base_url: str = None, | |
| default_headers: Dict[str, str] = None, | |
| timeout: int = None, | |
| retry_count: int = None, | |
| retry_delay: float = None | |
| ): | |
| """ | |
| HTTP ν΄λΌμ΄μΈνΈ μ΄κΈ°ν | |
| Args: | |
| base_url: κΈ°λ³Έ URL | |
| default_headers: κΈ°λ³Έ HTTP ν€λ | |
| timeout: μμ² νμμμ (μ΄) | |
| retry_count: μ¬μλ νμ | |
| retry_delay: μ¬μλ κ° μ§μ° μκ° (μ΄) | |
| """ | |
| self.base_url = base_url | |
| self.default_headers = default_headers or {} | |
| self.timeout = timeout or config.REQUEST_TIMEOUT | |
| self.retry_count = retry_count or config.API_RETRY_COUNT | |
| self.retry_delay = retry_delay or config.API_RETRY_DELAY | |
| def _make_url(self, endpoint: str) -> str: | |
| """ | |
| μλν¬μΈνΈλ‘λΆν° μ 체 URL μμ± | |
| Args: | |
| endpoint: API μλν¬μΈνΈ κ²½λ‘ | |
| Returns: | |
| μμ ν URL λ¬Έμμ΄ | |
| """ | |
| if self.base_url and not endpoint.startswith(('http://', 'https://')): | |
| # μλ κ²½λ‘μΈ κ²½μ° κΈ°λ³Έ URLκ³Ό κ²°ν© | |
| if endpoint.startswith('/'): | |
| return f"{self.base_url}{endpoint}" | |
| return f"{self.base_url}/{endpoint}" | |
| return endpoint | |
| def _prepare_headers(self, headers: Dict[str, str] = None) -> Dict[str, str]: | |
| """ | |
| μμ² ν€λ μ€λΉ | |
| Args: | |
| headers: μμ²λ³ ν€λ | |
| Returns: | |
| λ³ν©λ ν€λ λμ λ리 | |
| """ | |
| merged_headers = self.default_headers.copy() | |
| if headers: | |
| merged_headers.update(headers) | |
| return merged_headers | |
| def request( | |
| self, | |
| method: str, | |
| endpoint: str, | |
| params: Dict[str, Any] = None, | |
| data: Any = None, | |
| json: Any = None, | |
| headers: Dict[str, str] = None, | |
| timeout: int = None, | |
| retry_count: int = None, | |
| retry_delay: float = None, | |
| raise_for_status: bool = True | |
| ) -> requests.Response: | |
| """ | |
| HTTP μμ² μν | |
| Args: | |
| method: HTTP λ©μλ (GET, POST λ±) | |
| endpoint: API μλν¬μΈνΈ | |
| params: URL 쿼리 νλΌλ―Έν° | |
| data: μμ² λ°μ΄ν° | |
| json: JSON μμ² λ°μ΄ν° | |
| headers: μμ² ν€λ | |
| timeout: μμ² νμμμ (μ΄) | |
| retry_count: μ¬μλ νμ | |
| retry_delay: μ¬μλ κ° μ§μ° μκ° (μ΄) | |
| raise_for_status: μλ΅ μνκ° μλ¬λ©΄ μμΈ λ°μ μ¬λΆ | |
| Returns: | |
| HTTP μλ΅ κ°μ²΄ | |
| Raises: | |
| APIError: API ν΅μ μ€λ₯ λ°μ μ | |
| """ | |
| url = self._make_url(endpoint) | |
| merged_headers = self._prepare_headers(headers) | |
| timeout_value = timeout or self.timeout | |
| retry_count_value = retry_count if retry_count is not None else self.retry_count | |
| retry_delay_value = retry_delay if retry_delay is not None else self.retry_delay | |
| # μμ² λ‘κΉ | |
| logger.debug( | |
| f"HTTP {method} μμ²: {url}, νλΌλ―Έν°: {params}, " | |
| f"μ¬μλ: {retry_count_value}, νμμμ: {timeout_value}" | |
| ) | |
| # μ¬μλ 루ν | |
| for attempt in range(retry_count_value + 1): | |
| try: | |
| response = requests.request( | |
| method=method, | |
| url=url, | |
| params=params, | |
| data=data, | |
| json=json, | |
| headers=merged_headers, | |
| timeout=timeout_value | |
| ) | |
| # μλ΅ μν νμΈ | |
| if raise_for_status: | |
| response.raise_for_status() | |
| return response | |
| except requests.RequestException as e: | |
| # λ§μ§λ§ μλμμ μ€ν¨νλ©΄ μμΈ λ°μ | |
| if attempt >= retry_count_value: | |
| status_code = getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None | |
| response_data = None | |
| # μλ΅ λ°μ΄ν° μΆμΆ μλ | |
| if hasattr(e, 'response') and e.response: | |
| try: | |
| response_data = e.response.json() | |
| except ValueError: | |
| response_data = e.response.text | |
| error_msg = f"HTTP {method} μμ² μ€ν¨ (URL: {url}): {str(e)}" | |
| logger.error(error_msg) | |
| raise APIError( | |
| message=error_msg, | |
| status_code=status_code, | |
| response=response_data | |
| ) from e | |
| # μ¬μλ μ λκΈ° | |
| wait_time = retry_delay_value * (2 ** attempt) # μ§μ λ°±μ€ν | |
| logger.warning( | |
| f"HTTP {method} μμ² μ€ν¨ (URL: {url}), " | |
| f"{attempt+1}/{retry_count_value+1} λ²μ§Έ μλ, " | |
| f"{wait_time}μ΄ ν μ¬μλ: {str(e)}" | |
| ) | |
| time.sleep(wait_time) | |
| def get(self, endpoint, params=None, **kwargs): | |
| """GET μμ² μν""" | |
| return self.request("GET", endpoint, params=params, **kwargs) | |
| def post(self, endpoint, json=None, data=None, **kwargs): | |
| """POST μμ² μν""" | |
| return self.request("POST", endpoint, json=json, data=data, **kwargs) | |
| def put(self, endpoint, json=None, data=None, **kwargs): | |
| """PUT μμ² μν""" | |
| return self.request("PUT", endpoint, json=json, data=data, **kwargs) | |
| def delete(self, endpoint, **kwargs): | |
| """DELETE μμ² μν""" | |
| return self.request("DELETE", endpoint, **kwargs) | |