diff --git a/src/requests/cookies.py b/src/requests/cookies.py index 24dd9a7829..345d490327 100644 --- a/src/requests/cookies.py +++ b/src/requests/cookies.py @@ -299,6 +299,23 @@ def items(self) -> list[tuple[str, str | None]]: # type: ignore[override] """ return list(self.iteritems()) + def popitem(self) -> tuple[str, str | None]: + """Dict-like popitem() that removes and returns a (name, value) pair, + or raises KeyError if the jar is empty. + + The inherited ``MutableMapping.popitem`` does not work here because + ``__iter__`` yields ``Cookie`` objects rather than names. + """ + try: + cookie = next(iter(self)) + except StopIteration: + raise KeyError("popitem(): cookie jar is empty") from None + # Remove the exact cookie the iterator selected. Deleting by name alone + # (``del self[name]``) would drop every cookie sharing that name across + # other domains/paths, not just the one returned here. + self.clear(cookie.domain, cookie.path, cookie.name) + return cookie.name, cookie.value + def list_domains(self) -> list[str]: """Utility method to list all the domains in the jar.""" domains: list[str] = [] diff --git a/tests/test_requests.py b/tests/test_requests.py index 8bcb81d8b1..ac6398e8f2 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1313,6 +1313,40 @@ def test_cookie_parameters(self): assert cookie.domain == domain assert cookie._rest["HttpOnly"] == rest["HttpOnly"] + def test_cookie_popitem(self): + jar = requests.cookies.RequestsCookieJar() + jar.set("some_cookie", "some_value") + jar.set("some_cookie1", "some_value1") + + items = dict(jar.items()) + + name, value = jar.popitem() + assert (name, value) in items.items() + assert name not in jar + assert len(jar) == 1 + + jar.popitem() + assert len(jar) == 0 + + with pytest.raises(KeyError): + jar.popitem() + + def test_cookie_popitem_removes_only_the_selected_cookie(self): + # Two cookies share a name but live on different domains. popitem() must + # remove exactly the one it returns, not every cookie with that name. + jar = requests.cookies.RequestsCookieJar() + jar.set("dup", "value-a", domain="a.example.com", path="/") + jar.set("dup", "value-b", domain="b.example.com", path="/") + assert len(jar) == 2 + + name, value = jar.popitem() + assert name == "dup" + assert len(jar) == 1 + + remaining = next(iter(jar)) + assert remaining.name == "dup" + assert {value, remaining.value} == {"value-a", "value-b"} + def test_cookie_as_dict_keeps_len(self): key = "some_cookie" value = "some_value"