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/client_reqrep.py b/aiohttp/client_reqrep.py index 73309c61342..f2db7e85e50 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -802,7 +802,6 @@ def __init__( ) -> None: # URL forbids subclasses, so a simple type check is enough. assert type(url) is URL - super().__init__() self.method = method diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 9eddd829916..f04b74833bc 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -719,15 +719,12 @@ def ceil_timeout( class HeadersMixin: - __slots__ = ("_content_type", "_content_dict", "_stored_content_type") + """Mixin for handling headers.""" _headers: MultiMapping[str] - - def __init__(self) -> None: - super().__init__() - self._content_type: Optional[str] = None - self._content_dict: Optional[Dict[str, str]] = None - self._stored_content_type: Union[str, None, _SENTINEL] = sentinel + _content_type: Optional[str] = None + _content_dict: Optional[Dict[str, str]] = None + _stored_content_type: Union[str, None, _SENTINEL] = sentinel def _parse_content_type(self, raw: Optional[str]) -> None: self._stored_content_type = raw @@ -921,22 +918,14 @@ def __repr__(self) -> str: class CookieMixin: - # The `_cookies` slots is not defined here because non-empty slots cannot - # be combined with an Exception base class, as is done in HTTPException. - # CookieMixin subclasses with slots should define the `_cookies` - # slot themselves. - __slots__ = () + """Mixin for handling cookies.""" - def __init__(self) -> None: - super().__init__() - # Mypy doesn't like that _cookies isn't in __slots__. - # See the comment on this class's __slots__ for why this is OK. - self._cookies: Optional[SimpleCookie] = None # type: ignore[misc] + _cookies: Optional[SimpleCookie] = None @property def cookies(self) -> SimpleCookie: if self._cookies is None: - self._cookies = SimpleCookie() # type: ignore[misc] + self._cookies = SimpleCookie() return self._cookies def set_cookie( @@ -958,7 +947,7 @@ def set_cookie( Also updates only those params which are not None. """ if self._cookies is None: - self._cookies = SimpleCookie() # type: ignore[misc] + self._cookies = SimpleCookie() self._cookies[name] = value c = self._cookies[name] diff --git a/aiohttp/web_exceptions.py b/aiohttp/web_exceptions.py index 148eb22db3f..5fdd27695f5 100644 --- a/aiohttp/web_exceptions.py +++ b/aiohttp/web_exceptions.py @@ -96,7 +96,6 @@ def __init__( text: Optional[str] = None, content_type: Optional[str] = None, ) -> None: - super().__init__() if reason is None: reason = self.default_reason elif "\n" in reason: diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py index e2fcbae73e0..15283b890bd 100644 --- a/aiohttp/web_request.py +++ b/aiohttp/web_request.py @@ -127,26 +127,8 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin): hdrs.METH_DELETE, } - __slots__ = ( - "_message", - "_protocol", - "_payload_writer", - "_payload", - "_headers", - "_method", - "_version", - "_rel_url", - "_post", - "_read_bytes", - "_state", - "_cache", - "_task", - "_client_max_size", - "_loop", - "_transport_sslcontext", - "_transport_peername", - "__weakref__", - ) + _post: Optional[MultiDictProxy[Union[str, bytes, FileField]]] = None + _read_bytes: Optional[bytes] = None def __init__( self, @@ -163,9 +145,6 @@ def __init__( host: Optional[str] = None, remote: Optional[str] = None, ) -> None: - super().__init__() - if state is None: - state = {} self._message = message self._protocol = protocol self._payload_writer = payload_writer @@ -189,20 +168,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") @@ -838,16 +815,8 @@ def _finish(self) -> None: class Request(BaseRequest): - __slots__ = ("_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 def clone( self, diff --git a/aiohttp/web_response.py b/aiohttp/web_response.py index 02d0642afad..2edce1e8e1c 100644 --- a/aiohttp/web_response.py +++ b/aiohttp/web_response.py @@ -76,28 +76,20 @@ class ContentCoding(enum.Enum): class StreamResponse(BaseClass, HeadersMixin, CookieMixin): - __slots__ = ( - "_length_check", - "_body", - "_keep_alive", - "_chunked", - "_compression", - "_compression_force", - "_compression_strategy", - "_req", - "_payload_writer", - "_eof_sent", - "_must_be_empty_body", - "_body_length", - "_state", - "_headers", - "_status", - "_reason", - "_cookies", - "__weakref__", - ) _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 def __init__( self, @@ -114,20 +106,6 @@ def __init__( the headers when creating a new response object. It is not intended to be used by external code. """ - super().__init__() - self._length_check = True - 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._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: @@ -528,11 +506,8 @@ def __eq__(self, other: object) -> bool: class Response(StreamResponse): - __slots__ = ( - "_compressed_body", - "_zlib_executor_size", - "_zlib_executor", - ) + + _compressed_body: Optional[bytes] = None def __init__( self, @@ -598,7 +573,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 2f0f809280b..4f104780c7d 100644 --- a/aiohttp/web_ws.py +++ b/aiohttp/web_ws.py @@ -64,33 +64,23 @@ def __bool__(self) -> bool: class WebSocketResponse(StreamResponse): - __slots__ = ( - "_protocols", - "_ws_protocol", - "_writer", - "_reader", - "_closed", - "_closing", - "_conn_lost", - "_close_code", - "_loop", - "_waiting", - "_close_wait", - "_exception", - "_timeout", - "_receive_timeout", - "_autoclose", - "_autoping", - "_heartbeat", - "_heartbeat_when", - "_heartbeat_cb", - "_pong_heartbeat", - "_pong_response_cb", - "_compress", - "_max_msg_size", - "_ping_task", - "_writer_limit", - ) + + _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, @@ -106,32 +96,16 @@ def __init__( writer_limit: int = DEFAULT_LIMIT, ) -> None: super().__init__(status=101) - self._length_check = False 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: diff --git a/tests/test_web_request.py b/tests/test_web_request.py index 08a091947a6..0086d9b1688 100644 --- a/tests/test_web_request.py +++ b/tests/test_web_request.py @@ -61,8 +61,6 @@ def test_base_ctor() -> None: assert req.keep_alive - assert "__dict__" not in dir(req) - assert req @@ -109,8 +107,6 @@ def test_ctor() -> None: assert req.raw_headers == ((b"FOO", b"bar"),) assert req.task is req._task - assert "__dict__" not in dir(req) - def test_doubleslashes() -> None: # NB: //foo/bar is an absolute URL with foo netloc and /bar path