abc.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import asyncio
  2. import logging
  3. import socket
  4. from abc import ABC, abstractmethod
  5. from collections.abc import Sized
  6. from http.cookies import BaseCookie, Morsel
  7. from typing import (
  8. TYPE_CHECKING,
  9. Any,
  10. Awaitable,
  11. Callable,
  12. Dict,
  13. Generator,
  14. Iterable,
  15. List,
  16. Optional,
  17. Sequence,
  18. Tuple,
  19. TypedDict,
  20. Union,
  21. )
  22. from multidict import CIMultiDict
  23. from yarl import URL
  24. from ._cookie_helpers import parse_set_cookie_headers
  25. from .typedefs import LooseCookies
  26. if TYPE_CHECKING:
  27. from .web_app import Application
  28. from .web_exceptions import HTTPException
  29. from .web_request import BaseRequest, Request
  30. from .web_response import StreamResponse
  31. else:
  32. BaseRequest = Request = Application = StreamResponse = None
  33. HTTPException = None
  34. class AbstractRouter(ABC):
  35. def __init__(self) -> None:
  36. self._frozen = False
  37. def post_init(self, app: Application) -> None:
  38. """Post init stage.
  39. Not an abstract method for sake of backward compatibility,
  40. but if the router wants to be aware of the application
  41. it can override this.
  42. """
  43. @property
  44. def frozen(self) -> bool:
  45. return self._frozen
  46. def freeze(self) -> None:
  47. """Freeze router."""
  48. self._frozen = True
  49. @abstractmethod
  50. async def resolve(self, request: Request) -> "AbstractMatchInfo":
  51. """Return MATCH_INFO for given request"""
  52. class AbstractMatchInfo(ABC):
  53. __slots__ = ()
  54. @property # pragma: no branch
  55. @abstractmethod
  56. def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]:
  57. """Execute matched request handler"""
  58. @property
  59. @abstractmethod
  60. def expect_handler(
  61. self,
  62. ) -> Callable[[Request], Awaitable[Optional[StreamResponse]]]:
  63. """Expect handler for 100-continue processing"""
  64. @property # pragma: no branch
  65. @abstractmethod
  66. def http_exception(self) -> Optional[HTTPException]:
  67. """HTTPException instance raised on router's resolving, or None"""
  68. @abstractmethod # pragma: no branch
  69. def get_info(self) -> Dict[str, Any]:
  70. """Return a dict with additional info useful for introspection"""
  71. @property # pragma: no branch
  72. @abstractmethod
  73. def apps(self) -> Tuple[Application, ...]:
  74. """Stack of nested applications.
  75. Top level application is left-most element.
  76. """
  77. @abstractmethod
  78. def add_app(self, app: Application) -> None:
  79. """Add application to the nested apps stack."""
  80. @abstractmethod
  81. def freeze(self) -> None:
  82. """Freeze the match info.
  83. The method is called after route resolution.
  84. After the call .add_app() is forbidden.
  85. """
  86. class AbstractView(ABC):
  87. """Abstract class based view."""
  88. def __init__(self, request: Request) -> None:
  89. self._request = request
  90. @property
  91. def request(self) -> Request:
  92. """Request instance."""
  93. return self._request
  94. @abstractmethod
  95. def __await__(self) -> Generator[None, None, StreamResponse]:
  96. """Execute the view handler."""
  97. class ResolveResult(TypedDict):
  98. """Resolve result.
  99. This is the result returned from an AbstractResolver's
  100. resolve method.
  101. :param hostname: The hostname that was provided.
  102. :param host: The IP address that was resolved.
  103. :param port: The port that was resolved.
  104. :param family: The address family that was resolved.
  105. :param proto: The protocol that was resolved.
  106. :param flags: The flags that were resolved.
  107. """
  108. hostname: str
  109. host: str
  110. port: int
  111. family: int
  112. proto: int
  113. flags: int
  114. class AbstractResolver(ABC):
  115. """Abstract DNS resolver."""
  116. @abstractmethod
  117. async def resolve(
  118. self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
  119. ) -> List[ResolveResult]:
  120. """Return IP address for given hostname"""
  121. @abstractmethod
  122. async def close(self) -> None:
  123. """Release resolver"""
  124. if TYPE_CHECKING:
  125. IterableBase = Iterable[Morsel[str]]
  126. else:
  127. IterableBase = Iterable
  128. ClearCookiePredicate = Callable[["Morsel[str]"], bool]
  129. class AbstractCookieJar(Sized, IterableBase):
  130. """Abstract Cookie Jar."""
  131. def __init__(self, *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
  132. self._loop = loop or asyncio.get_running_loop()
  133. @property
  134. @abstractmethod
  135. def quote_cookie(self) -> bool:
  136. """Return True if cookies should be quoted."""
  137. @abstractmethod
  138. def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None:
  139. """Clear all cookies if no predicate is passed."""
  140. @abstractmethod
  141. def clear_domain(self, domain: str) -> None:
  142. """Clear all cookies for domain and all subdomains."""
  143. @abstractmethod
  144. def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:
  145. """Update cookies."""
  146. def update_cookies_from_headers(
  147. self, headers: Sequence[str], response_url: URL
  148. ) -> None:
  149. """Update cookies from raw Set-Cookie headers."""
  150. if headers and (cookies_to_update := parse_set_cookie_headers(headers)):
  151. self.update_cookies(cookies_to_update, response_url)
  152. @abstractmethod
  153. def filter_cookies(self, request_url: URL) -> "BaseCookie[str]":
  154. """Return the jar's cookies filtered by their attributes."""
  155. class AbstractStreamWriter(ABC):
  156. """Abstract stream writer."""
  157. buffer_size: int = 0
  158. output_size: int = 0
  159. length: Optional[int] = 0
  160. @abstractmethod
  161. async def write(self, chunk: Union[bytes, bytearray, memoryview]) -> None:
  162. """Write chunk into stream."""
  163. @abstractmethod
  164. async def write_eof(self, chunk: bytes = b"") -> None:
  165. """Write last chunk."""
  166. @abstractmethod
  167. async def drain(self) -> None:
  168. """Flush the write buffer."""
  169. @abstractmethod
  170. def enable_compression(
  171. self, encoding: str = "deflate", strategy: Optional[int] = None
  172. ) -> None:
  173. """Enable HTTP body compression"""
  174. @abstractmethod
  175. def enable_chunking(self) -> None:
  176. """Enable HTTP chunked mode"""
  177. @abstractmethod
  178. async def write_headers(
  179. self, status_line: str, headers: "CIMultiDict[str]"
  180. ) -> None:
  181. """Write HTTP headers"""
  182. def send_headers(self) -> None:
  183. """Force sending buffered headers if not already sent.
  184. Required only if write_headers() buffers headers instead of sending immediately.
  185. For backwards compatibility, this method does nothing by default.
  186. """
  187. class AbstractAccessLogger(ABC):
  188. """Abstract writer to access log."""
  189. __slots__ = ("logger", "log_format")
  190. def __init__(self, logger: logging.Logger, log_format: str) -> None:
  191. self.logger = logger
  192. self.log_format = log_format
  193. @abstractmethod
  194. def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:
  195. """Emit log to logger."""
  196. @property
  197. def enabled(self) -> bool:
  198. """Check if logger is enabled."""
  199. return True