From f289336a74d3a008d9f5e15bdec38fbce53f3ddb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Oct 2024 18:39:49 -0500 Subject: [PATCH 1/3] Add slots to dataclasses with Python 3.10+ --- aiohttp/client.py | 3 ++- aiohttp/client_reqrep.py | 6 +++--- aiohttp/client_ws.py | 5 ++--- aiohttp/helpers.py | 15 +++++++++++---- aiohttp/tracing.py | 34 +++++++++++++++++----------------- aiohttp/web_protocol.py | 5 ++--- aiohttp/web_request.py | 4 ++-- aiohttp/web_ws.py | 10 +++++++--- 8 files changed, 46 insertions(+), 36 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index 82cbce19d8d..d77cae088c7 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -94,6 +94,7 @@ BasicAuth, TimeoutHandle, ceil_timeout, + frozen_dataclass_decorator, get_env_proxy_for_url, method_must_be_empty_body, sentinel, @@ -191,7 +192,7 @@ class _RequestOptions(TypedDict, total=False): max_field_size: Union[int, None] -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class ClientTimeout: total: Optional[float] = None connect: Optional[float] = None diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 6468b2de3cd..a228222ac30 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -1,7 +1,6 @@ import asyncio import codecs import contextlib -import dataclasses import functools import io import re @@ -49,6 +48,7 @@ HeadersMixin, TimerNoop, basicauth_from_netrc, + frozen_dataclass_decorator, is_expected_content_type, netrc_from_env, parse_mimetype, @@ -98,14 +98,14 @@ def _gen_default_accept_encoding() -> str: return "gzip, deflate, br" if HAS_BROTLI else "gzip, deflate" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class ContentDisposition: type: Optional[str] parameters: "MappingProxyType[str, str]" filename: Optional[str] -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class RequestInfo: url: URL method: str diff --git a/aiohttp/client_ws.py b/aiohttp/client_ws.py index 850dd8ca8a8..56f9d3a2ca5 100644 --- a/aiohttp/client_ws.py +++ b/aiohttp/client_ws.py @@ -1,14 +1,13 @@ """WebSocket client for asyncio.""" import asyncio -import dataclasses import sys from types import TracebackType from typing import Any, Final, Optional, Type, cast from .client_exceptions import ClientError, ServerTimeoutError from .client_reqrep import ClientResponse -from .helpers import calculate_timeout_when, set_result +from .helpers import calculate_timeout_when, frozen_dataclass_decorator, set_result from .http import ( WS_CLOSED_MESSAGE, WS_CLOSING_MESSAGE, @@ -32,7 +31,7 @@ import async_timeout -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class ClientWSTimeout: ws_receive: Optional[float] = None ws_close: Optional[float] = None diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index ec67abf5ebf..e3c2e5684c5 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -60,7 +60,14 @@ else: import async_timeout -__all__ = ("BasicAuth", "ChainMapProxy", "ETag") +if sys.version_info < (3, 10): + frozen_dataclass_decorator = functools.partial(dataclasses.dataclass, frozen=True) +else: + frozen_dataclass_decorator = functools.partial( + dataclasses.dataclass, frozen=True, slots=True + ) + +__all__ = ("BasicAuth", "ChainMapProxy", "ETag", "frozen_dataclass_decorator") PY_310 = sys.version_info >= (3, 10) @@ -227,7 +234,7 @@ def netrc_from_env() -> Optional[netrc.netrc]: return None -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class ProxyInfo: proxy: URL proxy_auth: Optional[BasicAuth] @@ -302,7 +309,7 @@ def get_env_proxy_for_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]: return proxy_info.proxy, proxy_info.proxy_auth -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class MimeType: type: str subtype: str @@ -1072,7 +1079,7 @@ def populate_with_cookies(headers: "CIMultiDict[str]", cookies: SimpleCookie) -> ETAG_ANY = "*" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class ETag: value: str is_weak: bool = False diff --git a/aiohttp/tracing.py b/aiohttp/tracing.py index bb4630ef319..41df012d427 100644 --- a/aiohttp/tracing.py +++ b/aiohttp/tracing.py @@ -1,4 +1,3 @@ -import dataclasses from types import SimpleNamespace from typing import TYPE_CHECKING, Any, Awaitable, Generic, Protocol, TypeVar, overload @@ -7,6 +6,7 @@ from yarl import URL from .client_reqrep import ClientResponse +from .helpers import frozen_dataclass_decorator if TYPE_CHECKING: from .client import ClientSession @@ -221,7 +221,7 @@ def on_request_headers_sent( return self._on_request_headers_sent -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceRequestStartParams: """Parameters sent by the `on_request_start` signal""" @@ -230,7 +230,7 @@ class TraceRequestStartParams: headers: "CIMultiDict[str]" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceRequestChunkSentParams: """Parameters sent by the `on_request_chunk_sent` signal""" @@ -239,7 +239,7 @@ class TraceRequestChunkSentParams: chunk: bytes -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceResponseChunkReceivedParams: """Parameters sent by the `on_response_chunk_received` signal""" @@ -248,7 +248,7 @@ class TraceResponseChunkReceivedParams: chunk: bytes -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceRequestEndParams: """Parameters sent by the `on_request_end` signal""" @@ -258,7 +258,7 @@ class TraceRequestEndParams: response: ClientResponse -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceRequestExceptionParams: """Parameters sent by the `on_request_exception` signal""" @@ -268,7 +268,7 @@ class TraceRequestExceptionParams: exception: BaseException -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceRequestRedirectParams: """Parameters sent by the `on_request_redirect` signal""" @@ -278,60 +278,60 @@ class TraceRequestRedirectParams: response: ClientResponse -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceConnectionQueuedStartParams: """Parameters sent by the `on_connection_queued_start` signal""" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceConnectionQueuedEndParams: """Parameters sent by the `on_connection_queued_end` signal""" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceConnectionCreateStartParams: """Parameters sent by the `on_connection_create_start` signal""" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceConnectionCreateEndParams: """Parameters sent by the `on_connection_create_end` signal""" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceConnectionReuseconnParams: """Parameters sent by the `on_connection_reuseconn` signal""" -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceDnsResolveHostStartParams: """Parameters sent by the `on_dns_resolvehost_start` signal""" host: str -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceDnsResolveHostEndParams: """Parameters sent by the `on_dns_resolvehost_end` signal""" host: str -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceDnsCacheHitParams: """Parameters sent by the `on_dns_cache_hit` signal""" host: str -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceDnsCacheMissParams: """Parameters sent by the `on_dns_cache_miss` signal""" host: str -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class TraceRequestHeadersSentParams: """Parameters sent by the `on_request_headers_sent` signal""" diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py index 9f6fd42f5e9..eca4a24e7a0 100644 --- a/aiohttp/web_protocol.py +++ b/aiohttp/web_protocol.py @@ -1,6 +1,5 @@ import asyncio import asyncio.streams -import dataclasses import sys import traceback from collections import deque @@ -28,7 +27,7 @@ from .abc import AbstractAccessLogger, AbstractAsyncAccessLogger, AbstractStreamWriter from .base_protocol import BaseProtocol -from .helpers import ceil_timeout +from .helpers import ceil_timeout, frozen_dataclass_decorator from .http import ( HttpProcessingError, HttpRequestParser, @@ -109,7 +108,7 @@ async def log( self.access_logger.log(request, response, self._loop.time() - request_start) -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class _ErrInfo: status: int exc: BaseException diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py index b7b54fbd41d..22e1a5fcc80 100644 --- a/aiohttp/web_request.py +++ b/aiohttp/web_request.py @@ -1,5 +1,4 @@ import asyncio -import dataclasses import datetime import io import re @@ -38,6 +37,7 @@ ChainMapProxy, ETag, HeadersMixin, + frozen_dataclass_decorator, is_expected_content_type, parse_http_date, reify, @@ -76,7 +76,7 @@ from .web_urldispatcher import UrlMappingMatchInfo -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class FileField: name: str filename: str diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py index 56e4ad1cf8e..8e5dec234f0 100644 --- a/aiohttp/web_ws.py +++ b/aiohttp/web_ws.py @@ -1,7 +1,6 @@ import asyncio import base64 import binascii -import dataclasses import hashlib import json import sys @@ -11,7 +10,12 @@ from . import hdrs from .abc import AbstractStreamWriter -from .helpers import calculate_timeout_when, set_exception, set_result +from .helpers import ( + calculate_timeout_when, + frozen_dataclass_decorator, + set_exception, + set_result, +) from .http import ( WS_CLOSED_MESSAGE, WS_CLOSING_MESSAGE, @@ -46,7 +50,7 @@ THRESHOLD_CONNLOST_ACCESS: Final[int] = 5 -@dataclasses.dataclass(frozen=True) +@frozen_dataclass_decorator class WebSocketReady: ok: bool protocol: Optional[str] From 946444b50655a116ee3925a1e8738cde6d6729d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Oct 2024 18:42:38 -0500 Subject: [PATCH 2/3] Add slots to dataclasses with Python 3.10+ --- aiohttp/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index e3c2e5684c5..7ae6d0ef7ec 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -26,6 +26,7 @@ from pathlib import Path from types import TracebackType from typing import ( + TYPE_CHECKING, Any, Callable, ContextManager, @@ -60,7 +61,9 @@ else: import async_timeout -if sys.version_info < (3, 10): +if TYPE_CHECKING: + from dataclasses import dataclass as frozen_dataclass_decorator +elif sys.version_info < (3, 10): frozen_dataclass_decorator = functools.partial(dataclasses.dataclass, frozen=True) else: frozen_dataclass_decorator = functools.partial( From b3d666ed0846ac84ba705314fd48d06f8cb483fe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 4 Oct 2024 10:46:55 -0500 Subject: [PATCH 3/3] changelog --- CHANGES/9413.misc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 CHANGES/9413.misc.rst diff --git a/CHANGES/9413.misc.rst b/CHANGES/9413.misc.rst new file mode 100644 index 00000000000..314558b3385 --- /dev/null +++ b/CHANGES/9413.misc.rst @@ -0,0 +1 @@ +Reduced memory required many small objects by adding ``__slots__`` to dataclasses -- by :user:`bdraco`.