예외 (Exception)

ahttp_client 는 표준 HTTP 4xx/5xx 상태 코드마다 예외 클래스를 미리 만들어둬서, response.status 를 직접 확인하고 커스텀 예외를 던지는 코드를 따로 짤 필요가 없습니다.

응답에서 HTTPException 발생시키기
 1import aiohttp
 2
 3from ahttp_client import AsyncSession, HTTPNotFound, Response
 4
 5
 6class GithubService(AsyncSession):
 7    async def after_request(self, response: Response) -> Response:
 8        response.raise_for_status()
 9        return response
10
11async with GithubService("https://api.github.com", aiohttp.ClientSession) as service:
12    try:
13        await service.list_repositories(user="does-not-exist")
14    except HTTPNotFound as exc:
15        print(exc.status, exc.url)

Response.raise_for_status() 는 응답 상태 코드에 등록된 예외 클래스를 찾아서, 있으면 응답을 붙여 그 예외를 던집니다. 성공했거나 리다이렉트된 응답(2xx/3xx)은 그냥 놔둡니다.

요청 상태 검증

엔드포인트가 HTTP 200만 허용할 때 @request 또는 HTTP 메서드 데코레이터에 raise_on=True 를 지정합니다. 요청 실행 경로는 응답을 반환하거나 역직렬화하기 전에 원본 응답을 검증합니다. 그 밖의 모든 상태는 해당 응답을 담은 예외를 발생시킵니다. 4xx와 5xx 응답은 등록된 하위 클래스(또는 HTTPClientError/HTTPServerError)를 사용하고, 나머지 상태는 기본 HTTPException 을 사용합니다. 예외를 발생시키기 전에 응답을 닫습니다.

class GithubService(AsyncSession):
    @get("/health", raise_on=True)
    async def health(self) -> None:
        ...

Response.raise_for_status() 와 달리 raise_on=True 는 201이나 204 같은 다른 성공 상태와 리다이렉트를 포함해 200 이외의 모든 상태를 거부합니다.

예외 계층 구조

모든 예외는 HTTPException 을 상속받고, 예외를 유발한 response 와 그 status 코드, url 을 들고 있습니다.

  • HTTPClientError - 4xx 응답을 위한 기본 클래스입니다. 전용 클래스가 없는 4xx 상태 코드에 직접 사용됩니다.

  • HTTPServerError - 5xx 응답에 대해서도 같은 방식으로 동작합니다.

  • 표준 상태 코드마다 구체적인 서브클래스가 하나씩 있습니다. HTTPBadRequest (400), HTTPNotFound (404), HTTPTooManyRequests (429), HTTPInternalServerError (500), HTTPServiceUnavailable (503) 같은 식입니다.

참고

RFC 9110에서 이름이 바뀐 상태 코드가 몇 개 있는데, 예전 이름과 지금 이름 둘 다 같은 클래스의 별칭으로 내보내고 있습니다: HTTPPayloadTooLarge/HTTPContentTooLarge (413), HTTPRequestURITooLong/HTTPURITooLong (414), HTTPUnprocessableEntity/HTTPUnprocessableContent (422).

정확한 상태 코드 대신 카테고리 단위로 잡으면, 모든 4xx나 5xx 응답을 똑같이 재시도하거나 로그로 남기고 싶을 때 편합니다:

from ahttp_client import HTTPClientError, HTTPServerError

try:
    await service.list_repositories(user="gunyu1019")
except HTTPClientError:
    ...  # any 4xx status
except HTTPServerError:
    ...  # any 5xx status

재시도(Retry)와 함께 사용하기

raise_for_status() 가 실패한 응답을 타입 있는 예외로 바꿔주니까, 상태 코드를 직접 검사하는 코드 없이도 그 예외를 retry_on 에 그냥 적어 넣으면 서버 오류에서만 재시도하게 만들 수 있습니다 (재시도 (Retry) 참고):

from ahttp_client import HTTPServerError, retry

class GithubService(AsyncSession):
    async def after_request(self, response: Response) -> Response:
        response.raise_for_status()
        return response

    @retry(retry_on=(HTTPServerError,))
    @get("/users/{user}/repos")
    async def list_repositories(self, response: Response, user: Annotated[str, Path]):
        return response.json()

raise_on=True 는 4xx와 5xx 상태에 같은 타입 변환을 적용하면서 200 이외의 모든 상태도 거부하므로, 기본 retry_on=(HTTPServerError,) 설정과 직접 함께 사용할 수 있습니다:

@retry(max_retries=3)
@get("/health", raise_on=True)
async def health(self, response: Response):
    return response.json()

예외를 발생시키지 않고 응답을 재시도해야 한다면 retry_on_status 를 사용하세요. 재시도 (Retry) 를 참고하세요.

Reference

class HTTPException

성공하지 못한 HTTP 응답을 위한 기본 예외입니다.

각 구체적인 상태 예외는 HTTP status_code 와 사람이 읽기 좋은 title 을 클래스 변수로 갖고 있습니다. raise_for_status() 로 예외가 발생할 때는 원본 응답도 함께 보존됩니다.

class HTTPClientError

기반 클래스: HTTPException

등록되지 않은 4xx 클라이언트 오류 응답을 위한 기본 예외입니다.

class HTTPServerError

기반 클래스: HTTPException

등록되지 않은 5xx 서버 오류 응답을 위한 기본 예외입니다.

exception_for_status(status_code: int) → type[HTTPException] | None

성공하지 못한 상태 코드에 연관된 예외 클래스를 반환합니다.

등록된 HTTP 상태 코드는 그에 맞는 구체적인 예외 클래스로, 4xx·5xx 범위의 나머지는 각 카테고리의 기본 클래스로 연결됩니다. 성공하거나 리다이렉트된 응답이면 None 을 반환합니다.