from __future__ import absolute_import
from collections.abc import Mapping, MutableMapping
from collections import Mapping, MutableMapping
from threading import RLock
except ImportError: # Platform-specific: No threads available
def __exit__(self, exc_type, exc_value, traceback):
from collections import OrderedDict
from .exceptions import InvalidHeader
from .packages.six import iterkeys, itervalues, PY3
__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict']
class RecentlyUsedContainer(MutableMapping):
Provides a thread-safe dict-like container which maintains up to
``maxsize`` keys while throwing away the least-recently-used keys beyond
Maximum number of recent elements to retain.
Every time an item is evicted from the container,
``dispose_func(value)`` is called. Callback which will get called
ContainerCls = OrderedDict
def __init__(self, maxsize=10, dispose_func=None):
self.dispose_func = dispose_func
self._container = self.ContainerCls()
def __getitem__(self, key):
# Re-insert the item, moving it to the end of the eviction line.
item = self._container.pop(key)
self._container[key] = item
def __setitem__(self, key, value):
# Possibly evict the existing value of 'key'
evicted_value = self._container.get(key, _Null)
self._container[key] = value
# If we didn't evict an existing value, we might have to evict the
# least recently used item from the beginning of the container.
if len(self._container) > self._maxsize:
_key, evicted_value = self._container.popitem(last=False)
if self.dispose_func and evicted_value is not _Null:
self.dispose_func(evicted_value)
def __delitem__(self, key):
value = self._container.pop(key)
return len(self._container)
raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.')
# Copy pointers to all values, then wipe the mapping
values = list(itervalues(self._container))
return list(iterkeys(self._container))
class HTTPHeaderDict(MutableMapping):
An iterable of field-value pairs. Must not contain multiple field names
when compared case-insensitively.
Additional field-value pairs to pass in to ``dict.update``.
A ``dict`` like container for storing HTTP Headers.
Field names are stored and compared case-insensitively in compliance with
RFC 7230. Iteration provides the first case-sensitive key seen for each
Using ``__setitem__`` syntax overwrites fields that compare equal
case-insensitively in order to maintain ``dict``'s api. For fields that
compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``
If multiple fields that are equal case-insensitively are passed to the
constructor or ``.update``, the behavior is undefined and some will be
>>> headers = HTTPHeaderDict()
>>> headers.add('Set-Cookie', 'foo=bar')
>>> headers.add('set-cookie', 'baz=quxx')
>>> headers['content-length'] = '7'
>>> headers['SET-cookie']
>>> headers['Content-Length']
def __init__(self, headers=None, **kwargs):
super(HTTPHeaderDict, self).__init__()
self._container = OrderedDict()
if isinstance(headers, HTTPHeaderDict):
def __setitem__(self, key, val):
self._container[key.lower()] = [key, val]
return self._container[key.lower()]
def __getitem__(self, key):
val = self._container[key.lower()]
return ', '.join(val[1:])
def __delitem__(self, key):
del self._container[key.lower()]
def __contains__(self, key):
return key.lower() in self._container
if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
if not isinstance(other, type(self)):
other = type(self)(other)
return (dict((k.lower(), v) for k, v in self.itermerged()) ==
dict((k.lower(), v) for k, v in other.itermerged()))
return not self.__eq__(other)
iterkeys = MutableMapping.iterkeys
itervalues = MutableMapping.itervalues
return len(self._container)
# Only provide the originally cased names
for vals in self._container.values():
def pop(self, key, default=__marker):
'''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised.
# Using the MutableMapping function directly fails due to the private marker.
# Using ordinary dict.pop would expose the internal structures.
# So let's reinvent the wheel.
if default is self.__marker:
"""Adds a (name, value) pair, doesn't overwrite the value if it already
>>> headers = HTTPHeaderDict(foo='bar')
>>> headers.add('Foo', 'baz')
# Keep the common case aka no item present as fast as possible
vals = self._container.setdefault(key_lower, new_vals)
def extend(self, *args, **kwargs):
"""Generic import function for any type of header-like object.
Adapted version of MutableMapping.update in order to insert items
with self.add instead of self.__setitem__
raise TypeError("extend() takes at most 1 positional "
"arguments ({0} given)".format(len(args)))
other = args[0] if len(args) >= 1 else ()
if isinstance(other, HTTPHeaderDict):
for key, val in other.iteritems():
elif isinstance(other, Mapping):
self.add(key, other[key])
elif hasattr(other, "keys"):
self.add(key, other[key])
for key, value in kwargs.items():
def getlist(self, key, default=__marker):
"""Returns a list of all the values for the named field. Returns an
empty list if the key doesn't exist."""
vals = self._container[key.lower()]
if default is self.__marker:
# Backwards compatibility for httplib
getallmatchingheaders = getlist
# Backwards compatibility for http.cookiejar
return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
def _copy_from(self, other):
if isinstance(val, list):
# Don't need to convert tuples
self._container[key.lower()] = [key] + val
"""Iterate over all header lines, including duplicate ones."""
vals = self._container[key.lower()]
"""Iterate over all headers, merging duplicate ones together."""
val = self._container[key.lower()]
yield val[0], ', '.join(val[1:])
return list(self.iteritems())
def from_httplib(cls, message): # Python 2
"""Read headers from a Python 2 httplib message object."""
# python2.7 does not expose a proper API for exporting multiheaders
# efficiently. This function re-reads raw lines from the message
# object and extracts the multiheaders properly.
obs_fold_continued_leaders = (' ', '\t')
for line in message.headers:
if line.startswith(obs_fold_continued_leaders):
# We received a header line that starts with OWS as described
# in RFC-7230 S3.2.4. This indicates a multiline header, but
# there exists no previous header to which we can attach it.
'Header continuation with no previous header: %s' % line
headers[-1] = (key, value + ' ' + line.strip())
key, value = line.split(':', 1)
headers.append((key, value.strip()))