Skip to content

Commit

Permalink
Add treq.cookies
Browse files Browse the repository at this point in the history
If we're going to ditch requests.cookies we need to offer *something*
less raw than a bare CookieJar.
  • Loading branch information
twm committed May 1, 2024
1 parent 1d6fb74 commit 372b0ac
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 46 deletions.
1 change: 1 addition & 0 deletions changelog.d/384.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The new :mod:`treq.cookies` module provides helper functions for working with `http.cookiejar.Cookie` and `CookieJar` objects.
Empty file removed changelog.d/384.misc.rst
Empty file.
7 changes: 7 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ Authentication

.. autoexception:: UnknownAuthConfig

Cookies
-------

.. module:: treq.cookies

.. autofunction:: scoped_cookie

Test Helpers
------------

Expand Down
21 changes: 9 additions & 12 deletions docs/examples/using_cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@
import treq


def main(reactor, *args):
d = treq.get('https://httpbin.org/cookies/set?hello=world')
async def main(reactor):
resp = await treq.get("https://httpbin.org/cookies/set?hello=world")

def _get_jar(resp):
jar = resp.cookies()
jar = resp.cookies()
[cookie] = treq.cookies.raid(jar, domain="httpbin.org", name="hello")
print("The server set our hello cookie to: {}".format(cookie.value))

print('The server set our hello cookie to: {}'.format(jar['hello']))
await treq.get("https://httpbin.org/cookies", cookies=jar).addCallback(
print_response
)

return treq.get('https://httpbin.org/cookies', cookies=jar)

d.addCallback(_get_jar)
d.addCallback(print_response)

return d

react(main, [])
react(main)
37 changes: 3 additions & 34 deletions src/treq/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import mimetypes
import uuid
from collections import abc
from http.cookiejar import Cookie, CookieJar
from http.cookiejar import CookieJar
from json import dumps as json_dumps
from typing import (
Any,
Expand All @@ -20,6 +20,7 @@

from hyperlink import DecodedURL, EncodedURL
from requests.cookies import merge_cookies
from treq.cookies import scoped_cookie
from twisted.internet.defer import Deferred
from twisted.internet.interfaces import IProtocol
from twisted.python.components import proxyForInterface, registerAdapter
Expand Down Expand Up @@ -78,39 +79,7 @@ def _scoped_cookiejar_from_dict(
if cookie_dict is None:
return cookie_jar
for k, v in cookie_dict.items():
secure = url_object.scheme == "https"
port_specified = not (
(url_object.scheme == "https" and url_object.port == 443)
or (url_object.scheme == "http" and url_object.port == 80)
)
port = str(url_object.port) if port_specified else None
domain = url_object.host
netscape_domain = domain if "." in domain else domain + ".local"

cookie_jar.set_cookie(
Cookie(
# Scoping
domain=netscape_domain,
port=port,
secure=secure,
port_specified=port_specified,
# Contents
name=k,
value=v,
# Constant/always-the-same stuff
version=0,
path="/",
expires=None,
discard=False,
comment=None,
comment_url=None,
rfc2109=False,
path_specified=False,
domain_specified=False,
domain_initial_dot=False,
rest={},
)
)
cookie_jar.set_cookie(scoped_cookie(url_object, k, v))
return cookie_jar


Expand Down
101 changes: 101 additions & 0 deletions src/treq/cookies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Convenience helpers for :mod:`http.cookiejar`
"""

from typing import Union, Iterable, Optional
from http.cookiejar import Cookie, CookieJar

from hyperlink import EncodedURL


def scoped_cookie(origin: Union[str, EncodedURL], name: str, value: str) -> Cookie:
"""
Create a cookie scoped to a given URL's origin.
You can insert the result directly into a `CookieJar`, like::
jar = CookieJar()
jar.set_cookie(scoped_cookie("https://example.tld", "flavor", "chocolate"))
await treq.get("https://domain.example", cookies=jar)
:param origin:
A URL that specifies the domain and port number of the cookie.
If the protocol is HTTP*S* the cookie is marked ``Secure``, meaning
it will not be attached to HTTP requests. Otherwise the cookie will be
attached to both HTTP and HTTPS requests
:param name: Name of the cookie.
:param value: Value of the cookie.
.. note::
This does not scope the cookies to any particular path, only the
host, port, and scheme of the given URL.
"""
if isinstance(origin, EncodedURL):
url_object = origin
else:
url_object = EncodedURL.from_text(origin)

secure = url_object.scheme == "https"
port_specified = not (
(url_object.scheme == "https" and url_object.port == 443)
or (url_object.scheme == "http" and url_object.port == 80)
)
port = str(url_object.port) if port_specified else None
domain = url_object.host
netscape_domain = domain if "." in domain else domain + ".local"
return Cookie(
# Scoping
domain=netscape_domain,
port=port,
secure=secure,
port_specified=port_specified,
# Contents
name=name,
value=value,
# Constant/always-the-same stuff
version=0,
path="/",
expires=None,
discard=False,
comment=None,
comment_url=None,
rfc2109=False,
path_specified=False,
domain_specified=False,
domain_initial_dot=False,
rest={},
)


def raid(
jar: CookieJar, *, domain: str, name: Optional[str] = None
) -> Iterable[Cookie]:
"""
Search the cookie jar for matching cookies.
This is O(n) on the number of cookies in the jar.
:param jar: The `CookieJar` (or subclass thereof) to search.
:param domain:
Domain, as in the URL, to match. ``.local`` is appended to
a bare hostname. Subdomains are not matched (i.e., searching
for ``foo.bar.tld`` won't return a cookie set for ``bar.tld``).
:param name: Cookie name to match (exactly)
:param path: URL path to match (exactly)
"""
netscape_domain = domain if "." in domain else domain + ".local"

for c in jar:
if c.domain != netscape_domain:
continue
if name is not None and c.name != name:
continue
yield c

0 comments on commit 372b0ac

Please sign in to comment.