""" Instagram Graph API 커스텀 예외 모듈 Instagram API 에러 코드에 맞는 계층화된 예외 클래스를 정의합니다. """ from typing import Optional class InstagramAPIError(Exception): """ Instagram API 기본 예외 모든 Instagram API 관련 예외의 기본 클래스입니다. Attributes: message: 에러 메시지 code: Instagram API 에러 코드 subcode: Instagram API 에러 서브코드 fbtrace_id: Facebook 트레이스 ID (디버깅용) """ def __init__( self, message: str, code: Optional[int] = None, subcode: Optional[int] = None, fbtrace_id: Optional[str] = None, ): self.message = message self.code = code self.subcode = subcode self.fbtrace_id = fbtrace_id super().__init__(self.message) def __str__(self) -> str: parts = [self.message] if self.code is not None: parts.append(f"code={self.code}") if self.subcode is not None: parts.append(f"subcode={self.subcode}") if self.fbtrace_id: parts.append(f"fbtrace_id={self.fbtrace_id}") return " | ".join(parts) def __repr__(self) -> str: return ( f"{self.__class__.__name__}(" f"message={self.message!r}, " f"code={self.code}, " f"subcode={self.subcode}, " f"fbtrace_id={self.fbtrace_id!r})" ) class AuthenticationError(InstagramAPIError): """ 인증 관련 에러 토큰이 만료되었거나, 유효하지 않거나, 앱 권한이 없는 경우 발생합니다. 관련 에러 코드: - code=190, subcode=458: 앱에 권한 없음 - code=190, subcode=463: 토큰 만료 - code=190, subcode=467: 유효하지 않은 토큰 """ pass class RateLimitError(InstagramAPIError): """ Rate Limit 초과 에러 시간당 API 호출 제한(200회/시간/사용자)을 초과한 경우 발생합니다. HTTP 429 응답 또는 API 에러 코드 4와 함께 발생합니다. Attributes: retry_after: 재시도까지 대기해야 하는 시간 (초) 관련 에러 코드: - code=4: Rate limit 초과 - code=17: User request limit reached - code=341: Application request limit reached """ def __init__( self, message: str, retry_after: Optional[int] = None, code: Optional[int] = 4, subcode: Optional[int] = None, fbtrace_id: Optional[str] = None, ): super().__init__(message, code, subcode, fbtrace_id) self.retry_after = retry_after def __str__(self) -> str: base = super().__str__() if self.retry_after is not None: return f"{base} | retry_after={self.retry_after}s" return base class InstagramPermissionError(InstagramAPIError): """ 권한 부족 에러 요청한 작업을 수행할 권한이 없는 경우 발생합니다. Python 내장 PermissionError와 구분하기 위해 접두사를 사용합니다. 관련 에러 코드: - code=10: 권한 거부됨 - code=200: 비즈니스 계정 필요 - code=230: 이 권한에 대한 앱 검토 필요 """ pass # 하위 호환성을 위한 alias (deprecated - InstagramPermissionError 사용 권장) PermissionError = InstagramPermissionError class MediaPublishError(InstagramAPIError): """ 미디어 게시 실패 에러 이미지/비디오 게시 과정에서 발생하는 에러입니다. 발생 원인: - 이미지/비디오 URL 접근 불가 - 지원하지 않는 미디어 포맷 - 컨테이너 생성 실패 - 게시 타임아웃 """ pass class InvalidRequestError(InstagramAPIError): """ 잘못된 요청 에러 요청 파라미터가 잘못되었거나 필수 값이 누락된 경우 발생합니다. 관련 에러 코드: - code=100: 유효하지 않은 파라미터 - code=21009: 지원하지 않는 POST 요청 """ pass class ResourceNotFoundError(InstagramAPIError): """ 리소스를 찾을 수 없음 에러 요청한 미디어, 댓글, 계정 등이 존재하지 않는 경우 발생합니다. 관련 에러 코드: - code=803: 객체가 존재하지 않음 - code=100 + subcode=33: 객체가 존재하지 않음 (다른 형태) """ pass class ContainerStatusError(InstagramAPIError): """ 컨테이너 상태 에러 미디어 컨테이너가 ERROR 상태가 되었을 때 발생합니다. """ pass class ContainerTimeoutError(InstagramAPIError): """ 컨테이너 타임아웃 에러 미디어 컨테이너가 지정된 시간 내에 FINISHED 상태가 되지 않은 경우 발생합니다. """ pass # ========================================================================== # 에러 코드 → 예외 클래스 매핑 # ========================================================================== ERROR_CODE_MAPPING: dict[int, type[InstagramAPIError]] = { 4: RateLimitError, # Rate limit 10: InstagramPermissionError, # Permission denied 17: RateLimitError, # User request limit 100: InvalidRequestError, # Invalid parameter 190: AuthenticationError, # Invalid OAuth access token 200: InstagramPermissionError, # Requires business account 230: InstagramPermissionError, # App review required 341: RateLimitError, # Application request limit 803: ResourceNotFoundError, # Object does not exist } # (code, subcode) 세부 매핑 - 더 정확한 예외 분류 ERROR_CODE_SUBCODE_MAPPING: dict[tuple[int, int], type[InstagramAPIError]] = { (100, 33): ResourceNotFoundError, # Object does not exist (190, 458): AuthenticationError, # App not authorized (190, 463): AuthenticationError, # Token expired (190, 467): AuthenticationError, # Invalid token } def create_exception_from_error( message: str, code: Optional[int] = None, subcode: Optional[int] = None, fbtrace_id: Optional[str] = None, ) -> InstagramAPIError: """ API 에러 응답에서 적절한 예외 객체 생성 (code, subcode) 조합을 먼저 확인하고, 없으면 code만으로 매핑합니다. Args: message: 에러 메시지 code: API 에러 코드 subcode: API 에러 서브코드 fbtrace_id: Facebook 트레이스 ID Returns: 적절한 예외 클래스의 인스턴스 """ exception_class = InstagramAPIError # 먼저 (code, subcode) 조합으로 정확한 매핑 시도 if code is not None and subcode is not None: key = (code, subcode) if key in ERROR_CODE_SUBCODE_MAPPING: exception_class = ERROR_CODE_SUBCODE_MAPPING[key] return exception_class( message=message, code=code, subcode=subcode, fbtrace_id=fbtrace_id, ) # 기본 코드 매핑 if code is not None: exception_class = ERROR_CODE_MAPPING.get(code, InstagramAPIError) return exception_class( message=message, code=code, subcode=subcode, fbtrace_id=fbtrace_id, )