From e5dd82a16b413b0cebeb97fad3be563935313f11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Nov 2024 16:04:27 -0800 Subject: [PATCH] [PR #10037/2e369db backport][3.12] Refactor requests and responses to use classvar defaults to avoid multiple `__init__`s (#10054) --- CHANGES/10037.misc.rst | 1 + aiohttp/helpers.py | 3 ++- aiohttp/web_app.py | 2 ++ aiohttp/web_request.py | 21 ++++++--------------- aiohttp/web_response.py | 32 ++++++++++++++++---------------- aiohttp/web_ws.py | 32 ++++++++++++++++---------------- 6 files changed, 43 insertions(+), 48 deletions(-) create mode 100644 CHANGES/10037.misc.rst diff --git a/CHANGES/10037.misc.rst b/CHANGES/10037.misc.rst new file mode 100644 index 00000000000..655c804c995 --- /dev/null +++ b/CHANGES/10037.misc.rst @@ -0,0 +1 @@ +Improved performances of creating objects during the HTTP request lifecycle -- by :user:`bdraco`. diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 522cce2972b..8038931ebec 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -694,10 +694,11 @@ def ceil_timeout( class HeadersMixin: + """Mixin for handling headers.""" + ATTRS = frozenset(["_content_type", "_content_dict", "_stored_content_type"]) _headers: MultiMapping[str] - _content_type: Optional[str] = None _content_dict: Optional[Dict[str, str]] = None _stored_content_type: Union[str, None, _SENTINEL] = sentinel diff --git a/aiohttp/web_app.py b/aiohttp/web_app.py index 5d542ab9222..4bdc54034de 100644 --- a/aiohttp/web_app.py +++ b/aiohttp/web_app.py @@ -498,6 +498,8 @@ def _make_request( task: "asyncio.Task[None]", _cls: Type[Request] = Request, ) -> Request: + if TYPE_CHECKING: + assert self._loop is not None return _cls( message, payload, diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py index 502a93d247a..f11d49020a0 100644 --- a/aiohttp/web_request.py +++ b/aiohttp/web_request.py @@ -146,6 +146,8 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin): "_transport_peername", ] ) + _post: Optional[MultiDictProxy[Union[str, bytes, FileField]]] = None + _read_bytes: Optional[bytes] = None def __init__( self, @@ -162,8 +164,6 @@ def __init__( host: Optional[str] = None, remote: Optional[str] = None, ) -> None: - if state is None: - state = {} self._message = message self._protocol = protocol self._payload_writer = payload_writer @@ -187,20 +187,18 @@ def __init__( self._cache["scheme"] = url.scheme self._rel_url = url.relative() else: - self._rel_url = message.url + self._rel_url = url if scheme is not None: self._cache["scheme"] = scheme if host is not None: self._cache["host"] = host - self._post: Optional[MultiDictProxy[Union[str, bytes, FileField]]] = None - self._read_bytes: Optional[bytes] = None - self._state = state + self._state = {} if state is None else state self._task = task self._client_max_size = client_max_size self._loop = loop - transport = self._protocol.transport + transport = protocol.transport assert transport is not None self._transport_sslcontext = transport.get_extra_info("sslcontext") self._transport_peername = transport.get_extra_info("peername") @@ -847,14 +845,7 @@ class Request(BaseRequest): ATTRS = BaseRequest.ATTRS | frozenset(["_match_info"]) - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - - # matchdict, route_name, handler - # or information about traversal lookup - - # initialized after route resolving - self._match_info: Optional[UrlMappingMatchInfo] = None + _match_info: Optional["UrlMappingMatchInfo"] = None if DEBUG: diff --git a/aiohttp/web_response.py b/aiohttp/web_response.py index d3d8afe5433..e05799ca7b7 100644 --- a/aiohttp/web_response.py +++ b/aiohttp/web_response.py @@ -76,9 +76,20 @@ class ContentCoding(enum.Enum): class StreamResponse(BaseClass, HeadersMixin): - _length_check = True - _body: Union[None, bytes, bytearray, Payload] + _length_check = True + _body = None + _keep_alive: Optional[bool] = None + _chunked: bool = False + _compression: bool = False + _compression_strategy: int = zlib.Z_DEFAULT_STRATEGY + _compression_force: Optional[ContentCoding] = None + _req: Optional["BaseRequest"] = None + _payload_writer: Optional[AbstractStreamWriter] = None + _eof_sent: bool = False + _must_be_empty_body: Optional[bool] = None + _body_length = 0 + _cookies: Optional[SimpleCookie] = None def __init__( self, @@ -95,19 +106,6 @@ def __init__( the headers when creating a new response object. It is not intended to be used by external code. """ - self._body = None - self._keep_alive: Optional[bool] = None - self._chunked = False - self._compression = False - self._compression_strategy: int = zlib.Z_DEFAULT_STRATEGY - self._compression_force: Optional[ContentCoding] = None - self._cookies: Optional[SimpleCookie] = None - - self._req: Optional[BaseRequest] = None - self._payload_writer: Optional[AbstractStreamWriter] = None - self._eof_sent = False - self._must_be_empty_body: Optional[bool] = None - self._body_length = 0 self._state: Dict[str, Any] = {} if _real_headers is not None: @@ -613,6 +611,9 @@ def __eq__(self, other: object) -> bool: class Response(StreamResponse): + + _compressed_body: Optional[bytes] = None + def __init__( self, *, @@ -677,7 +678,6 @@ def __init__( else: self.body = body - self._compressed_body: Optional[bytes] = None self._zlib_executor_size = zlib_executor_size self._zlib_executor = zlib_executor diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py index c18f88eaf00..0fb1549a3aa 100644 --- a/aiohttp/web_ws.py +++ b/aiohttp/web_ws.py @@ -61,7 +61,22 @@ def __bool__(self) -> bool: class WebSocketResponse(StreamResponse): - _length_check = False + _length_check: bool = False + _ws_protocol: Optional[str] = None + _writer: Optional[WebSocketWriter] = None + _reader: Optional[WebSocketDataQueue] = None + _closed: bool = False + _closing: bool = False + _conn_lost: int = 0 + _close_code: Optional[int] = None + _loop: Optional[asyncio.AbstractEventLoop] = None + _waiting: bool = False + _close_wait: Optional[asyncio.Future[None]] = None + _exception: Optional[BaseException] = None + _heartbeat_when: float = 0.0 + _heartbeat_cb: Optional[asyncio.TimerHandle] = None + _pong_response_cb: Optional[asyncio.TimerHandle] = None + _ping_task: Optional[asyncio.Task[None]] = None def __init__( self, @@ -78,30 +93,15 @@ def __init__( ) -> None: super().__init__(status=101) self._protocols = protocols - self._ws_protocol: Optional[str] = None - self._writer: Optional[WebSocketWriter] = None - self._reader: Optional[WebSocketDataQueue] = None - self._closed = False - self._closing = False - self._conn_lost = 0 - self._close_code: Optional[int] = None - self._loop: Optional[asyncio.AbstractEventLoop] = None - self._waiting: bool = False - self._close_wait: Optional[asyncio.Future[None]] = None - self._exception: Optional[BaseException] = None self._timeout = timeout self._receive_timeout = receive_timeout self._autoclose = autoclose self._autoping = autoping self._heartbeat = heartbeat - self._heartbeat_when = 0.0 - self._heartbeat_cb: Optional[asyncio.TimerHandle] = None if heartbeat is not None: self._pong_heartbeat = heartbeat / 2.0 - self._pong_response_cb: Optional[asyncio.TimerHandle] = None self._compress: Union[bool, int] = compress self._max_msg_size = max_msg_size - self._ping_task: Optional[asyncio.Task[None]] = None self._writer_limit = writer_limit def _cancel_heartbeat(self) -> None: