普通文本  |  232行  |  6.61 KB

"""
Represents the Cache-Control header
"""
import re

class UpdateDict(dict):
    """
    Dict that has a callback on all updates
    """
    # these are declared as class attributes so that
    # we don't need to override constructor just to
    # set some defaults
    updated = None
    updated_args = None

    def _updated(self):
        """
        Assign to new_dict.updated to track updates
        """
        updated = self.updated
        if updated is not None:
            args = self.updated_args
            if args is None:
                args = (self,)
            updated(*args)

    def __setitem__(self, key, item):
        dict.__setitem__(self, key, item)
        self._updated()

    def __delitem__(self, key):
        dict.__delitem__(self, key)
        self._updated()

    def clear(self):
        dict.clear(self)
        self._updated()

    def update(self, *args, **kw):
        dict.update(self, *args, **kw)
        self._updated()

    def setdefault(self, key, value=None):
        val = dict.setdefault(self, key, value)
        if val is value:
            self._updated()
        return val

    def pop(self, *args):
        v = dict.pop(self, *args)
        self._updated()
        return v

    def popitem(self):
        v = dict.popitem(self)
        self._updated()
        return v


token_re = re.compile(
    r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?')
need_quote_re = re.compile(r'[^a-zA-Z0-9._-]')


class exists_property(object):
    """
    Represents a property that either is listed in the Cache-Control
    header, or is not listed (has no value)
    """
    def __init__(self, prop, type=None):
        self.prop = prop
        self.type = type

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        return self.prop in obj.properties

    def __set__(self, obj, value):
        if (self.type is not None
            and self.type != obj.type):
            raise AttributeError(
                "The property %s only applies to %s Cache-Control" % (
                    self.prop, self.type))

        if value:
            obj.properties[self.prop] = None
        else:
            if self.prop in obj.properties:
                del obj.properties[self.prop]

    def __delete__(self, obj):
        self.__set__(obj, False)


class value_property(object):
    """
    Represents a property that has a value in the Cache-Control header.

    When no value is actually given, the value of self.none is returned.
    """
    def __init__(self, prop, default=None, none=None, type=None):
        self.prop = prop
        self.default = default
        self.none = none
        self.type = type

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        if self.prop in obj.properties:
            value = obj.properties[self.prop]
            if value is None:
                return self.none
            else:
                return value
        else:
            return self.default

    def __set__(self, obj, value):
        if (self.type is not None
            and self.type != obj.type):
            raise AttributeError(
                "The property %s only applies to %s Cache-Control" % (
                    self.prop, self.type))
        if value == self.default:
            if self.prop in obj.properties:
                del obj.properties[self.prop]
        elif value is True:
            obj.properties[self.prop] = None # Empty value, but present
        else:
            obj.properties[self.prop] = value

    def __delete__(self, obj):
        if self.prop in obj.properties:
            del obj.properties[self.prop]


class CacheControl(object):

    """
    Represents the Cache-Control header.

    By giving a type of ``'request'`` or ``'response'`` you can
    control what attributes are allowed (some Cache-Control values
    only apply to requests or responses).
    """

    update_dict = UpdateDict

    def __init__(self, properties, type):
        self.properties = properties
        self.type = type

    @classmethod
    def parse(cls, header, updates_to=None, type=None):
        """
        Parse the header, returning a CacheControl object.

        The object is bound to the request or response object
        ``updates_to``, if that is given.
        """
        if updates_to:
            props = cls.update_dict()
            props.updated = updates_to
        else:
            props = {}
        for match in token_re.finditer(header):
            name = match.group(1)
            value = match.group(2) or match.group(3) or None
            if value:
                try:
                    value = int(value)
                except ValueError:
                    pass
            props[name] = value
        obj = cls(props, type=type)
        if updates_to:
            props.updated_args = (obj,)
        return obj

    def __repr__(self):
        return '<CacheControl %r>' % str(self)

    # Request values:
    # no-cache shared (below)
    # no-store shared (below)
    # max-age shared  (below)
    max_stale = value_property('max-stale', none='*', type='request')
    min_fresh = value_property('min-fresh', type='request')
    # no-transform shared (below)
    only_if_cached = exists_property('only-if-cached', type='request')

    # Response values:
    public = exists_property('public', type='response')
    private = value_property('private', none='*', type='response')
    no_cache = value_property('no-cache', none='*')
    no_store = exists_property('no-store')
    no_transform = exists_property('no-transform')
    must_revalidate = exists_property('must-revalidate', type='response')
    proxy_revalidate = exists_property('proxy-revalidate', type='response')
    max_age = value_property('max-age', none=-1)
    s_maxage = value_property('s-maxage', type='response')
    s_max_age = s_maxage
    stale_while_revalidate = value_property(
        'stale-while-revalidate', type='response')
    stale_if_error = value_property('stale-if-error', type='response')

    def __str__(self):
        return serialize_cache_control(self.properties)

    def copy(self):
        """
        Returns a copy of this object.
        """
        return self.__class__(self.properties.copy(), type=self.type)


def serialize_cache_control(properties):
    if isinstance(properties, CacheControl):
        properties = properties.properties
    parts = []
    for name, value in sorted(properties.items()):
        if value is None:
            parts.append(name)
            continue
        value = str(value)
        if need_quote_re.search(value):
            value = '"%s"' % value
        parts.append('%s=%s' % (name, value))
    return ', '.join(parts)