aboutsummaryrefslogtreecommitdiff
path: root/requests/cookies.py
diff options
context:
space:
mode:
Diffstat (limited to 'requests/cookies.py')
-rw-r--r--requests/cookies.py159
1 files changed, 134 insertions, 25 deletions
diff --git a/requests/cookies.py b/requests/cookies.py
index 0e0dd67..bd7289e 100644
--- a/requests/cookies.py
+++ b/requests/cookies.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
"""
Compatibility code to be able to use `cookielib.CookieJar` with requests.
@@ -14,6 +16,7 @@ try:
except ImportError:
import dummy_threading as threading
+
class MockRequest(object):
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
@@ -29,26 +32,22 @@ class MockRequest(object):
def __init__(self, request):
self._r = request
self._new_headers = {}
+ self.type = urlparse(self._r.url).scheme
def get_type(self):
- return urlparse(self._r.full_url).scheme
+ return self.type
def get_host(self):
- return urlparse(self._r.full_url).netloc
+ return urlparse(self._r.url).netloc
def get_origin_req_host(self):
- if self._r.response.history:
- r = self._r.response.history[0]
- return urlparse(r).netloc
- else:
- return self.get_host()
+ return self.get_host()
def get_full_url(self):
- return self._r.full_url
+ return self._r.url
def is_unverifiable(self):
- # unverifiable == redirected
- return bool(self._r.response.history)
+ return True
def has_header(self, name):
return name in self._r.headers or name in self._new_headers
@@ -66,6 +65,11 @@ class MockRequest(object):
def get_new_headers(self):
return self._new_headers
+ @property
+ def unverifiable(self):
+ return self.is_unverifiable()
+
+
class MockResponse(object):
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
@@ -86,6 +90,7 @@ class MockResponse(object):
def getheaders(self, name):
self._headers.getheaders(name)
+
def extract_cookies_to_jar(jar, request, response):
"""Extract the cookies from the response into a CookieJar.
@@ -94,13 +99,11 @@ def extract_cookies_to_jar(jar, request, response):
:param response: urllib3.HTTPResponse object
"""
# the _original_response field is the wrapped httplib.HTTPResponse object,
- # and in safe mode, it may be None if the request didn't actually complete.
- # in that case, just skip the cookie extraction.
- if response._original_response is not None:
- req = MockRequest(request)
- # pull out the HTTPMessage with the headers and put it in the mock:
- res = MockResponse(response._original_response.msg)
- jar.extract_cookies(res, req)
+ req = MockRequest(request)
+ # pull out the HTTPMessage with the headers and put it in the mock:
+ res = MockResponse(response._original_response.msg)
+ jar.extract_cookies(res, req)
+
def get_cookie_header(jar, request):
"""Produce an appropriate Cookie header string to be sent with `request`, or None."""
@@ -108,6 +111,7 @@ def get_cookie_header(jar, request):
jar.add_cookie_header(r)
return r.get_new_headers().get('Cookie')
+
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
"""Unsets a cookie by name, by default over all domains and paths.
@@ -123,6 +127,12 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
for domain, path, name in clearables:
cookiejar.clear(domain, path, name)
+
+class CookieConflictError(RuntimeError):
+ """There are two cookies that meet the criteria specified in the cookie jar.
+ Use .get and .set and include domain and path args in order to be more specific."""
+
+
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
@@ -140,13 +150,19 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
Unlike a regular CookieJar, this class is pickleable.
"""
- def get(self, name, domain=None, path=None, default=None):
+ def get(self, name, default=None, domain=None, path=None):
+ """Dict-like get() that also supports optional domain and path args in
+ order to resolve naming collisions from using one cookie jar over
+ multiple domains. Caution: operation is O(n), not O(1)."""
try:
- return self._find(name, domain, path)
+ return self._find_no_duplicates(name, domain, path)
except KeyError:
return default
def set(self, name, value, **kwargs):
+ """Dict-like set() that also supports optional domain and path args in
+ order to resolve naming collisions from using one cookie jar over
+ multiple domains."""
# support client code that unsets cookies by assignment of a None value:
if value is None:
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
@@ -159,16 +175,88 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
self.set_cookie(c)
return c
+ def keys(self):
+ """Dict-like keys() that returns a list of names of cookies from the jar.
+ See values() and items()."""
+ keys = []
+ for cookie in iter(self):
+ keys.append(cookie.name)
+ return keys
+
+ def values(self):
+ """Dict-like values() that returns a list of values of cookies from the jar.
+ See keys() and items()."""
+ values = []
+ for cookie in iter(self):
+ values.append(cookie.value)
+ return values
+
+ def items(self):
+ """Dict-like items() that returns a list of name-value tuples from the jar.
+ See keys() and values(). Allows client-code to call "dict(RequestsCookieJar)
+ and get a vanilla python dict of key value pairs."""
+ items = []
+ for cookie in iter(self):
+ items.append((cookie.name, cookie.value))
+ return items
+
+ def list_domains(self):
+ """Utility method to list all the domains in the jar."""
+ domains = []
+ for cookie in iter(self):
+ if cookie.domain not in domains:
+ domains.append(cookie.domain)
+ return domains
+
+ def list_paths(self):
+ """Utility method to list all the paths in the jar."""
+ paths = []
+ for cookie in iter(self):
+ if cookie.path not in paths:
+ paths.append(cookie.path)
+ return paths
+
+ def multiple_domains(self):
+ """Returns True if there are multiple domains in the jar.
+ Returns False otherwise."""
+ domains = []
+ for cookie in iter(self):
+ if cookie.domain is not None and cookie.domain in domains:
+ return True
+ domains.append(cookie.domain)
+ return False # there is only one domain in jar
+
+ def get_dict(self, domain=None, path=None):
+ """Takes as an argument an optional domain and path and returns a plain old
+ Python dict of name-value pairs of cookies that meet the requirements."""
+ dictionary = {}
+ for cookie in iter(self):
+ if (domain is None or cookie.domain == domain) and (path is None
+ or cookie.path == path):
+ dictionary[cookie.name] = cookie.value
+ return dictionary
+
def __getitem__(self, name):
- return self._find(name)
+ """Dict-like __getitem__() for compatibility with client code. Throws exception
+ if there are more than one cookie with name. In that case, use the more
+ explicit get() method instead. Caution: operation is O(n), not O(1)."""
+ return self._find_no_duplicates(name)
def __setitem__(self, name, value):
+ """Dict-like __setitem__ for compatibility with client code. Throws exception
+ if there is already a cookie of that name in the jar. In that case, use the more
+ explicit set() method instead."""
self.set(name, value)
def __delitem__(self, name):
+ """Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
remove_cookie_by_name(self, name)
def _find(self, name, domain=None, path=None):
+ """Requests uses this method internally to get cookie values. Takes as args name
+ and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
+ _find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown
+ if there are conflicting cookies."""
for cookie in iter(self):
if cookie.name == name:
if domain is None or cookie.domain == domain:
@@ -177,21 +265,42 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
+ def _find_no_duplicates(self, name, domain=None, path=None):
+ """__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
+ Takes as args name and optional domain and path. Returns a cookie.value.
+ Throws KeyError if cookie is not found and CookieConflictError if there are
+ multiple cookies that match name and optionally domain and path."""
+ toReturn = None
+ for cookie in iter(self):
+ if cookie.name == name:
+ if domain is None or cookie.domain == domain:
+ if path is None or cookie.path == path:
+ if toReturn is not None: # if there are multiple cookies that meet passed in criteria
+ raise CookieConflictError('There are multiple cookies with name, %r' % (name))
+ toReturn = cookie.value # we will eventually return this as long as no cookie conflict
+
+ if toReturn:
+ return toReturn
+ raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
+
def __getstate__(self):
+ """Unlike a normal CookieJar, this class is pickleable."""
state = self.__dict__.copy()
# remove the unpickleable RLock object
state.pop('_cookies_lock')
return state
def __setstate__(self, state):
+ """Unlike a normal CookieJar, this class is pickleable."""
self.__dict__.update(state)
if '_cookies_lock' not in self.__dict__:
self._cookies_lock = threading.RLock()
def copy(self):
- """We're probably better off forbidding this."""
+ """This is not implemented. Calling this will throw an exception."""
raise NotImplementedError
+
def create_cookie(name, value, **kwargs):
"""Make a cookie from underspecified parameters.
@@ -211,8 +320,7 @@ def create_cookie(name, value, **kwargs):
comment=None,
comment_url=None,
rest={'HttpOnly': None},
- rfc2109=False,
- )
+ rfc2109=False,)
badargs = set(kwargs) - set(result)
if badargs:
@@ -227,6 +335,7 @@ def create_cookie(name, value, **kwargs):
return cookielib.Cookie(**result)
+
def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
c = create_cookie(
@@ -246,10 +355,10 @@ def morsel_to_cookie(morsel):
comment=morsel['comment'],
comment_url=bool(morsel['comment']),
rest={'HttpOnly': morsel['httponly']},
- rfc2109=False,
- )
+ rfc2109=False,)
return c
+
def cookiejar_from_dict(cookie_dict, cookiejar=None):
"""Returns a CookieJar from a key/value dictionary.