Skip to content

Commit

Permalink
Merge branch 'master' into fix-swallow-cancel
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Sep 22, 2024
2 parents c9b9f1d + 7e0ef07 commit 0b1bc0d
Show file tree
Hide file tree
Showing 30 changed files with 232 additions and 91 deletions.
1 change: 1 addition & 0 deletions CHANGES/8977.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Made ``TestClient.app`` a ``Generic`` so type checkers will know the correct type (avoiding unneeded ``client.app is not None`` checks) -- by :user:`Dreamsorcerer`.
1 change: 1 addition & 0 deletions CHANGES/9018.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Updated Python parser to reject messages after a close message, matching C parser behaviour -- by :user:`Dreamsorcerer`.
1 change: 1 addition & 0 deletions CHANGES/9033.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Changed web entry point to not listen on TCP when only a Unix path is passed -- by :user:`Dreamsorcerer`.
1 change: 1 addition & 0 deletions CHANGES/9063.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``If-None-Match`` not using weak comparison -- by :user:`Dreamsorcerer`.
1 change: 1 addition & 0 deletions CHANGES/9109.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Changed default value to ``compress`` from ``None`` to ``False`` (``None`` is no longer an expected value) -- by :user:`Dreamsorcerer`.
3 changes: 3 additions & 0 deletions CHANGES/9200.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Improved middleware performance -- by :user:`bdraco`.

The ``set_current_app`` method was removed from ``UrlMappingMatchInfo`` because it is no longer used, and it was unlikely external caller would ever use it.
1 change: 1 addition & 0 deletions CHANGES/9204.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Significantly speed up filtering cookies -- by :user:`bdraco`.
1 change: 1 addition & 0 deletions CHANGES/9241.misc.rst
6 changes: 3 additions & 3 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class _RequestOptions(TypedDict, total=False):
auth: Union[BasicAuth, None]
allow_redirects: bool
max_redirects: int
compress: Union[str, bool, None]
compress: Union[str, bool]
chunked: Union[bool, None]
expect100: bool
raise_for_status: Union[None, bool, Callable[[ClientResponse], Awaitable[None]]]
Expand Down Expand Up @@ -418,7 +418,7 @@ async def _request(
auth: Optional[BasicAuth] = None,
allow_redirects: bool = True,
max_redirects: int = 10,
compress: Union[str, bool, None] = None,
compress: Union[str, bool] = False,
chunked: Optional[bool] = None,
expect100: bool = False,
raise_for_status: Union[
Expand Down Expand Up @@ -1372,7 +1372,7 @@ def request(
auth: Optional[BasicAuth] = None,
allow_redirects: bool = True,
max_redirects: int = 10,
compress: Optional[str] = None,
compress: Union[str, bool] = False,
chunked: Optional[bool] = None,
expect100: bool = False,
raise_for_status: Optional[bool] = None,
Expand Down
22 changes: 9 additions & 13 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def __init__(
cookies: Optional[LooseCookies] = None,
auth: Optional[BasicAuth] = None,
version: http.HttpVersion = http.HttpVersion11,
compress: Union[str, bool, None] = None,
compress: Union[str, bool] = False,
chunked: Optional[bool] = None,
expect100: bool = False,
loop: asyncio.AbstractEventLoop,
Expand Down Expand Up @@ -235,7 +235,6 @@ def __init__(
self.url = url.with_fragment(None)
self.method = method.upper()
self.chunked = chunked
self.compress = compress
self.loop = loop
self.length = None
if response_class is None:
Expand All @@ -255,7 +254,7 @@ def __init__(
self.update_headers(headers)
self.update_auto_headers(skip_auto_headers)
self.update_cookies(cookies)
self.update_content_encoding(data)
self.update_content_encoding(data, compress)
self.update_auth(auth, trust_env)
self.update_proxy(proxy, proxy_auth, proxy_headers)

Expand Down Expand Up @@ -422,22 +421,19 @@ def update_cookies(self, cookies: Optional[LooseCookies]) -> None:

self.headers[hdrs.COOKIE] = c.output(header="", sep=";").strip()

def update_content_encoding(self, data: Any) -> None:
def update_content_encoding(self, data: Any, compress: Union[bool, str]) -> None:
"""Set request content encoding."""
self.compress = None
if not data:
# Don't compress an empty body.
self.compress = None
return

enc = self.headers.get(hdrs.CONTENT_ENCODING, "").lower()
if enc:
if self.compress:
if self.headers.get(hdrs.CONTENT_ENCODING):
if compress:
raise ValueError(
"compress can not be set if Content-Encoding header is set"
)
elif self.compress:
if not isinstance(self.compress, str):
self.compress = "deflate"
elif compress:
self.compress = compress if isinstance(compress, str) else "deflate"
self.headers[hdrs.CONTENT_ENCODING] = self.compress
self.chunked = True # enable chunked, no need to deal with length

Expand Down Expand Up @@ -640,7 +636,7 @@ async def send(self, conn: "Connection") -> "ClientResponse":
)

if self.compress:
writer.enable_compression(self.compress) # type: ignore[arg-type]
writer.enable_compression(self.compress)

if self.chunked is not None:
writer.enable_chunking()
Expand Down
53 changes: 33 additions & 20 deletions aiohttp/cookiejar.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ def __init__(
self._cookies: DefaultDict[Tuple[str, str], SimpleCookie] = defaultdict(
SimpleCookie
)
self._morsel_cache: DefaultDict[Tuple[str, str], Dict[str, Morsel[str]]] = (
defaultdict(dict)
)
self._host_only_cookies: Set[Tuple[str, str]] = set()
self._unsafe = unsafe
self._quote_cookie = quote_cookie
Expand Down Expand Up @@ -129,6 +132,7 @@ def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None:
if predicate is None:
self._expire_heap.clear()
self._cookies.clear()
self._morsel_cache.clear()
self._host_only_cookies.clear()
self._expirations.clear()
return
Expand Down Expand Up @@ -210,6 +214,7 @@ def _delete_cookies(self, to_del: List[Tuple[str, str, str]]) -> None:
for domain, path, name in to_del:
self._host_only_cookies.discard((domain, name))
self._cookies[(domain, path)].pop(name, None)
self._morsel_cache[(domain, path)].pop(name, None)
self._expirations.pop((domain, path, name), None)

def _expire_cookie(self, when: float, domain: str, path: str, name: str) -> None:
Expand Down Expand Up @@ -285,7 +290,12 @@ def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> No
else:
cookie["expires"] = ""

self._cookies[(domain, path)][name] = cookie
key = (domain, path)
if self._cookies[key].get(name) != cookie:
# Don't blow away the cache if the same
# cookie gets set again
self._cookies[key][name] = cookie
self._morsel_cache[key].pop(name, None)

self._do_expiration()

Expand Down Expand Up @@ -337,30 +347,33 @@ def filter_cookies(self, request_url: URL) -> "BaseCookie[str]":
# Create every combination of (domain, path) pairs.
pairs = itertools.product(domains, paths)

# Point 2: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4
cookies = itertools.chain.from_iterable(
self._cookies[p].values() for p in pairs
)
path_len = len(request_url.path)
for cookie in cookies:
name = cookie.key
domain = cookie["domain"]
# Point 2: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4
for p in pairs:
for name, cookie in self._cookies[p].items():
domain = cookie["domain"]

if (domain, name) in self._host_only_cookies and domain != hostname:
continue
if (domain, name) in self._host_only_cookies and domain != hostname:
continue

# Skip edge case when the cookie has a trailing slash but request doesn't.
if len(cookie["path"]) > path_len:
continue
# Skip edge case when the cookie has a trailing slash but request doesn't.
if len(cookie["path"]) > path_len:
continue

if is_not_secure and cookie["secure"]:
continue
if is_not_secure and cookie["secure"]:
continue

# We already built the Morsel so reuse it here
if name in self._morsel_cache[p]:
filtered[name] = self._morsel_cache[p][name]
continue

# It's critical we use the Morsel so the coded_value
# (based on cookie version) is preserved
mrsl_val = cast("Morsel[str]", cookie.get(cookie.key, Morsel()))
mrsl_val.set(cookie.key, cookie.value, cookie.coded_value)
filtered[name] = mrsl_val
# It's critical we use the Morsel so the coded_value
# (based on cookie version) is preserved
mrsl_val = cast("Morsel[str]", cookie.get(cookie.key, Morsel()))
mrsl_val.set(cookie.key, cookie.value, cookie.coded_value)
self._morsel_cache[p][name] = mrsl_val
filtered[name] = mrsl_val

return filtered

Expand Down
5 changes: 5 additions & 0 deletions aiohttp/http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ def feed_data(
start_pos = 0
loop = self.loop

should_close = False
while start_pos < data_len:
# read HTTP message (request/response line + headers), \r\n\r\n
# and split by lines
Expand All @@ -317,6 +318,9 @@ def feed_data(
continue

if pos >= start_pos:
if should_close:
raise BadHttpMessage("Data after `Connection: close`")

# line found
line = data[start_pos:pos]
if SEP == b"\n": # For lax response parsing
Expand Down Expand Up @@ -426,6 +430,7 @@ def get_content_length() -> Optional[int]:
payload = EMPTY_PAYLOAD

messages.append((msg, payload))
should_close = msg.should_close
else:
self._tail = data[start_pos:]
data = EMPTY
Expand Down
4 changes: 2 additions & 2 deletions aiohttp/http_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ def _write(self, chunk: bytes) -> None:
size = len(chunk)
self.buffer_size += size
self.output_size += size
transport = self.transport
if not self._protocol.connected or transport is None or transport.is_closing():
transport = self._protocol.transport
if transport is None or transport.is_closing():
raise ClientConnectionResetError("Cannot write to closing transport")
transport.write(chunk)

Expand Down
14 changes: 7 additions & 7 deletions aiohttp/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ async def __call__(
*,
server_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any
) -> TestClient[Request]: ...
) -> TestClient[Request, Application]: ...
@overload
async def __call__(
self,
__param: BaseTestServer[_Request],
*,
server_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any
) -> TestClient[_Request]: ...
) -> TestClient[_Request, None]: ...


class AiohttpServer(Protocol):
Expand Down Expand Up @@ -349,7 +349,7 @@ async def finalize() -> None:


@pytest.fixture
def aiohttp_client_cls() -> Type[TestClient[Any]]:
def aiohttp_client_cls() -> Type[TestClient[Any, Any]]:
"""
Client class to use in ``aiohttp_client`` factory.
Expand Down Expand Up @@ -377,7 +377,7 @@ def test_login(aiohttp_client):

@pytest.fixture
def aiohttp_client(
loop: asyncio.AbstractEventLoop, aiohttp_client_cls: Type[TestClient[Any]]
loop: asyncio.AbstractEventLoop, aiohttp_client_cls: Type[TestClient[Any, Any]]
) -> Iterator[AiohttpClient]:
"""Factory to create a TestClient instance.
Expand All @@ -393,20 +393,20 @@ async def go(
*,
server_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any
) -> TestClient[Request]: ...
) -> TestClient[Request, Application]: ...
@overload
async def go(
__param: BaseTestServer[_Request],
*,
server_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any
) -> TestClient[_Request]: ...
) -> TestClient[_Request, None]: ...
async def go(
__param: Union[Application, BaseTestServer[Any]],
*,
server_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any
) -> TestClient[Any]:
) -> TestClient[Any, Any]:
if isinstance(__param, Application):
server_kwargs = server_kwargs or {}
server = TestServer(__param, **server_kwargs)
Expand Down
26 changes: 22 additions & 4 deletions aiohttp/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
TypeVar,
Union,
cast,
overload,
)
from unittest import IsolatedAsyncioTestCase, mock

Expand Down Expand Up @@ -72,6 +73,7 @@
else:
Self = Any

_ApplicationNone = TypeVar("_ApplicationNone", Application, None)
_Request = TypeVar("_Request", bound=BaseRequest)

REUSE_ADDRESS = os.name == "posix" and sys.platform != "cygwin"
Expand Down Expand Up @@ -251,7 +253,7 @@ async def _make_runner(self, **kwargs: Any) -> ServerRunner:
return ServerRunner(srv, **kwargs)


class TestClient(Generic[_Request]):
class TestClient(Generic[_Request, _ApplicationNone]):
"""
A test client implementation.
Expand All @@ -261,7 +263,23 @@ class TestClient(Generic[_Request]):

__test__ = False

@overload
def __init__(
self: "TestClient[Request, Application]",
server: TestServer,
*,
cookie_jar: Optional[AbstractCookieJar] = None,
**kwargs: Any,
) -> None: ...
@overload
def __init__(
self: "TestClient[_Request, None]",
server: BaseTestServer[_Request],
*,
cookie_jar: Optional[AbstractCookieJar] = None,
**kwargs: Any,
) -> None: ...
def __init__( # type: ignore[misc]
self,
server: BaseTestServer[_Request],
*,
Expand Down Expand Up @@ -300,8 +318,8 @@ def server(self) -> BaseTestServer[_Request]:
return self._server

@property
def app(self) -> Optional[Application]:
return cast(Optional[Application], getattr(self._server, "app", None))
def app(self) -> _ApplicationNone:
return getattr(self._server, "app", None) # type: ignore[return-value]

@property
def session(self) -> ClientSession:
Expand Down Expand Up @@ -505,7 +523,7 @@ async def get_server(self, app: Application) -> TestServer:
"""Return a TestServer instance."""
return TestServer(app)

async def get_client(self, server: TestServer) -> TestClient[Request]:
async def get_client(self, server: TestServer) -> TestClient[Request, Application]:
"""Return a TestClient instance."""
return TestClient(server)

Expand Down
Loading

0 comments on commit 0b1bc0d

Please sign in to comment.