diff --git a/src/requests/_types.py b/src/requests/_types.py index b2273d612b..7d98976146 100644 --- a/src/requests/_types.py +++ b/src/requests/_types.py @@ -111,7 +111,7 @@ class _ValidatedRequest(PreparedRequest): HeadersType: TypeAlias = Mapping[str, str | bytes] | None - CookiesType: TypeAlias = RequestsCookieJar | Mapping[str, str] + CookiesType: TypeAlias = RequestsCookieJar | Mapping[str, str | None] # Building blocks for FilesType _FileName: TypeAlias = str | None diff --git a/src/requests/cookies.py b/src/requests/cookies.py index 2e3fc21509..4212cf707b 100644 --- a/src/requests/cookies.py +++ b/src/requests/cookies.py @@ -12,7 +12,7 @@ import calendar import copy import time -from collections.abc import Iterator, MutableMapping +from collections.abc import Iterator, Mapping, MutableMapping from http.cookiejar import Cookie, CookieJar, CookiePolicy from typing import TYPE_CHECKING, Any, TypeVar, overload @@ -562,7 +562,7 @@ def morsel_to_cookie(morsel: Morsel[Any]) -> Cookie: @overload def cookiejar_from_dict( - cookie_dict: dict[str, str] | None, + cookie_dict: Mapping[str, str | None] | None, cookiejar: None = None, overwrite: bool = True, ) -> RequestsCookieJar: ... @@ -570,14 +570,14 @@ def cookiejar_from_dict( @overload def cookiejar_from_dict( - cookie_dict: dict[str, str] | None, + cookie_dict: Mapping[str, str | None] | None, cookiejar: _CookieJarT, overwrite: bool = True, ) -> _CookieJarT: ... def cookiejar_from_dict( - cookie_dict: dict[str, str] | None, + cookie_dict: Mapping[str, str | None] | None, cookiejar: CookieJar | None = None, overwrite: bool = True, ) -> CookieJar: @@ -594,15 +594,17 @@ def cookiejar_from_dict( if cookie_dict is not None: names_from_jar = [cookie.name for cookie in cookiejar] - for name in cookie_dict: - if overwrite or (name not in names_from_jar): - cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + for name, value in cookie_dict.items(): + if value is None: + remove_cookie_by_name(cookiejar, name) + elif overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, value)) return cookiejar def merge_cookies( - cookiejar: CookieJar, cookies: dict[str, str] | CookieJar | None + cookiejar: CookieJar, cookies: Mapping[str, str | None] | CookieJar | None ) -> CookieJar: """Add cookies to cookiejar and returns a merged CookieJar. diff --git a/src/requests/sessions.py b/src/requests/sessions.py index c2ab37391d..467346b3d5 100644 --- a/src/requests/sessions.py +++ b/src/requests/sessions.py @@ -523,14 +523,14 @@ def prepare_request(self, request: Request) -> PreparedRequest: cookies = request.cookies or {} - # Bootstrap CookieJar. - if not isinstance(cookies, cookielib.CookieJar): - cookies = cookiejar_from_dict(cookies) - # Merge with session cookies - merged_cookies = merge_cookies( - merge_cookies(RequestsCookieJar(), self.cookies), cookies - ) + merged_cookies = merge_cookies(RequestsCookieJar(), self.cookies) + if isinstance(cookies, cookielib.CookieJar): + merged_cookies = merge_cookies(merged_cookies, cookies) + else: + merged_cookies = cookiejar_from_dict( + cookies, cookiejar=merged_cookies, overwrite=True + ) # Set environment's basic authentication if not explicitly set. auth = request.auth diff --git a/tests/test_requests.py b/tests/test_requests.py index 571535fe79..4ed3a1b530 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -422,6 +422,18 @@ def test_request_cookie_overrides_session_cookie(self, httpbin): # Session cookie should not be modified assert s.cookies["foo"] == "bar" + def test_request_cookie_none_removes_session_cookie(self): + s = requests.session() + s.cookies["foo"] = "bar" + req = requests.Request( + "GET", "http://example.com/", cookies={"foo": None, "baz": "qux"} + ) + + prepared = s.prepare_request(req) + + assert prepared.headers["Cookie"] == "baz=qux" + assert s.cookies["foo"] == "bar" + def test_request_cookies_not_persisted(self, httpbin): s = requests.session() s.get(httpbin("cookies"), cookies={"foo": "baz"})