text: """ HTTP Request/Response 추상화를 위한 클래스 """
  from abc import ABCMeta, abstractmethod
  from dataclasses import dataclass, field
  from typing import Dict, Optional, Awaitable, Any, Callable, Type
  from functools import wraps
  import enum
  
  from bs4 import BeautifulSoup
  
  from ..exceptions import ClientError, ServerError
  
  __all__ = (
      "Response",
      "HTTPRequestMethod",
      "BodyFormatter",
      "IRequestPostCondition",
      "BaseRequest",
      "DEFAULT_REQUEST_TIMEOUT",
  )
  
  DEFAULT_REQUEST_TIMEOUT: float = 30.0
  
  
  @dataclass
  class Response:
      """ HTTP Response 데이터 클래스 """
  
      status: int
      url: str
      reason: str = ""
      headers: Dict[str, str] = field(default_factory=dict)
      raw: bytes = b""
      text: str = ""
      cookies: Dict[str, str] = field(default_factory=dict)
  
      def __post_init__(self):
          self.headers = {key.lower(): value for key, value in self.headers.items()}
  
      def __bool__(self):
          return bool(self.status)
  
      @property
      def soup(self):
          try:
              return self._soup
          except AttributeError:
              self._soup = BeautifulSoup(self.text, features="html.parser")
              return self._soup
  
      @property
      def etc(self):
          try:
              return self._etc
          except AttributeError:
              self._etc: Dict[str, Any] = {}
              return self._etc
  
  
  @enum.unique
  class HTTPRequestMethod(enum.Enum):
      GET = "GET"
      POST = "POST"
  
  
  @enum.unique
  class BodyFormatter(enum.Enum):
      URL_ENCODE: str = "data"
      JSON: str = "json"
  
  
  class IRequestPostCondition(metaclass=ABCMeta):
      """ HTTP request post-condition interface
  
      이 인터페이스를 상속한 모든 파생 클래스는 HTTP 응답 수신 이후 사후조건으로 실행됨
      """
  
      @staticmethod
      @abstractmethod
      def check(response: Response) -> None:
          pass
  
  
  class _StatusCheck(IRequestPostCondition):
      """ Check HTTP response status code
  
      400번 이상의 상태 코드는 예외를 발생시킴
      4xx: ClientError
      5xx: ServerError
      """
  
      @staticmethod
      def check(response: Response) -> None:
          status = response.status
          n = status % 100
          if n == 4:
              raise ClientError(f"클라이언트 요청 오류입니다. -> 응답코드: {status}", response)
          elif n == 5:
              raise ServerError(f"서버 응답 오입니다. -> 응답코드: {status}", response)
  
  
  class PostCondition:
      """ HTTP Request 요청의 사전/사후조건 처리를 위한 데코레이터
  
      사전조건: 타임아웃 설정
      사후조건: 응답 객체에 대한 사후조건 처리 (heckConditionBase 의 파생 클래스 실행)
      """
  
      def __init__(self, method: HTTPRequestMethod):
          self.method = method
  
      def __call__(
          self, request: Callable[..., Awaitable[Response]],
      ) -> Callable[..., Awaitable[Response]]:
          @wraps(request)
          async def check_condition(
              cls: Type["BaseRequest"], *args: Any, **kwargs: Any
          ) -> Response:
              response = await cls._request(self.method, *args, **kwargs)
  
              subclass: Type[IRequestPostCondition]
              for subclass in IRequestPostCondition.__subclasses__():
                  subclass.check(response)
              return response
  
          return check_condition
  
  
  class BaseRequest(metaclass=ABCMeta):
      """ HTTP Request abstract class
  
      파생 클래스는 _request 추상 메서드만 구현
      """
  
      @classmethod
      @PostCondition(HTTPRequestMethod.GET)
      async def get(
          cls,
          url: str,
          *,
          headers: Optional[Dict[str, str]] = None,
          cookies: Dict[str, str] = None,
          verify: bool = True,
          allow_redirects: bool = False,
          timeout: Optional[float] = None,
          proxies: Optional[str] = None,
      ) -> Response:
          pass
  
      @classmethod
      @PostCondition(HTTPRequestMethod.POST)
      async def post(
          cls,
          url: str,
          *,
          headers: Optional[Dict[str, str]] = None,
          body: Optional[Dict[str, str]] = None,
          body_encoding: BodyFormatter = BodyFormatter.URL_ENCODE,
          cookies: Dict[str, str] = None,
          verify: bool = True,
          allow_redirects: bool = False,
          timeout: Optional[float] = None,
          proxies: Optional[str] = None,
      ) -> Response:
          pass
  
      @classmethod
      @abstractmethod
      async def _request(
          cls,
          method: HTTPRequestMethod,
          url: str,
          *,
          headers: Optional[Dict[str, str]] = None,
          body: Optional[Dict[str, str]] = None,
          body_encoding: BodyFormatter = BodyFormatter.URL_ENCODE,
          cookies: Dict[str, str] = None,
          verify: bool = True,
          allow_redirects: bool = False,
          timeout: Optional[float] = None,
          proxies: Optional[str] = None,
      ) -> Response:
          pass
  