o2o-castad-backend/poc/instagram1-difi/exceptions.py

258 lines
7.2 KiB
Python

"""
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,
)