Skip to content

Commit

Permalink
Refactor requests and responses to use classvar defaults to avoid mul…
Browse files Browse the repository at this point in the history
…tiple `__init__`s (#10037)
  • Loading branch information
bdraco authored Nov 26, 2024
1 parent 006fbc3 commit 2e369db
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 145 deletions.
1 change: 1 addition & 0 deletions CHANGES/10037.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved performances of creating objects during the HTTP request lifecycle -- by :user:`bdraco`.
1 change: 0 additions & 1 deletion aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 8 additions & 19 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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]
Expand Down
1 change: 0 additions & 1 deletion aiohttp/web_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
43 changes: 6 additions & 37 deletions aiohttp/web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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,
Expand Down
54 changes: 14 additions & 40 deletions aiohttp/web_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
60 changes: 17 additions & 43 deletions aiohttp/web_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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:
Expand Down
4 changes: 0 additions & 4 deletions tests/test_web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ def test_base_ctor() -> None:

assert req.keep_alive

assert "__dict__" not in dir(req)

assert req


Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 2e369db

Please sign in to comment.