~ubuntu-branches/ubuntu/trusty/python-webob/trusty-proposed

« back to all changes in this revision

Viewing changes to webob/etag.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2013-01-07 07:52:32 UTC
  • mfrom: (1.3.5)
  • Revision ID: package-import@ubuntu.com-20130107075232-w6x8r94du3t48wj4
Tags: 1.2.3-0ubuntu1
* New upstream release:
  - Dropped debian/patches/01_lp_920197.patch: no longer needed.
  - debian/watch: Update to point to pypi.
  - debian/rules: Disable docs build.

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
Also If-Range parsing
5
5
"""
6
6
 
7
 
from webob.datetime_utils import *
8
 
from webob.util import header_docstring, warn_deprecation
9
 
 
10
 
__all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'NoIfRange', 'etag_property']
11
 
 
12
 
 
13
 
def etag_property(key, default, rfc_section):
 
7
from webob.datetime_utils import (
 
8
    parse_date,
 
9
    serialize_date,
 
10
    )
 
11
from webob.descriptors import _rx_etag
 
12
 
 
13
from webob.util import (
 
14
    header_docstring,
 
15
    warn_deprecation,
 
16
    )
 
17
 
 
18
__all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'etag_property']
 
19
 
 
20
def etag_property(key, default, rfc_section, strong=True):
14
21
    doc = header_docstring(key, rfc_section)
15
22
    doc += "  Converts it as a Etag."
16
23
    def fget(req):
17
24
        value = req.environ.get(key)
18
25
        if not value:
19
26
            return default
20
 
        elif value == '*':
21
 
            return AnyETag
22
27
        else:
23
 
            return ETagMatcher.parse(value)
 
28
            return ETagMatcher.parse(value, strong=strong)
24
29
    def fset(req, val):
25
30
        if val is None:
26
31
            req.environ[key] = None
33
38
def _warn_weak_match_deprecated():
34
39
    warn_deprecation("weak_match is deprecated", '1.2', 3)
35
40
 
 
41
def _warn_if_range_match_deprecated(*args, **kw): # pragma: no cover
 
42
    raise DeprecationWarning("IfRange.match[_response] API is deprecated")
 
43
 
36
44
 
37
45
class _AnyETag(object):
38
46
    """
45
53
    def __nonzero__(self):
46
54
        return False
47
55
 
 
56
    __bool__ = __nonzero__ # python 3
 
57
 
48
58
    def __contains__(self, other):
49
59
        return True
50
60
 
51
61
    def weak_match(self, other):
52
62
        _warn_weak_match_deprecated()
53
 
        return True
54
63
 
55
64
    def __str__(self):
56
65
        return '*'
68
77
    def __nonzero__(self):
69
78
        return False
70
79
 
 
80
    __bool__ = __nonzero__ # python 3
 
81
 
71
82
    def __contains__(self, other):
72
83
        return False
73
84
 
74
 
    def weak_match(self, other):
 
85
    def weak_match(self, other): # pragma: no cover
75
86
        _warn_weak_match_deprecated()
76
 
        return False
77
87
 
78
88
    def __str__(self):
79
89
        return ''
80
90
 
81
91
NoETag = _NoETag()
82
92
 
 
93
 
 
94
# TODO: convert into a simple tuple
 
95
 
83
96
class ETagMatcher(object):
84
 
    """
85
 
    Represents an ETag request.  Supports containment to see if an
86
 
    ETag matches.  You can also use
87
 
    ``etag_matcher.weak_contains(etag)`` to allow weak ETags to match
88
 
    (allowable for conditional GET requests, but not ranges or other
89
 
    methods).
90
 
    """
91
 
 
92
 
    def __init__(self, etags, weak_etags=()):
 
97
    def __init__(self, etags):
93
98
        self.etags = etags
94
 
        self.weak_etags = weak_etags
95
99
 
96
100
    def __contains__(self, other):
97
 
        return other in self.etags or other in self.weak_etags
 
101
        return other in self.etags
98
102
 
99
 
    def weak_match(self, other):
 
103
    def weak_match(self, other): # pragma: no cover
100
104
        _warn_weak_match_deprecated()
101
 
        if other.lower().startswith('w/'):
102
 
            other = other[2:]
103
 
        return other in self.etags or other in self.weak_etags
104
105
 
105
106
    def __repr__(self):
106
 
        return '<ETag %s>' % (
107
 
            ' or '.join(self.etags))
 
107
        return '<ETag %s>' % (' or '.join(self.etags))
108
108
 
109
109
    @classmethod
110
 
    def parse(cls, value):
 
110
    def parse(cls, value, strong=True):
111
111
        """
112
112
        Parse this from a header value
113
113
        """
114
 
        results = []
115
 
        weak_results = []
116
 
        while value:
117
 
            if value.lower().startswith('w/'):
118
 
                # Next item is weak
119
 
                weak = True
120
 
                value = value[2:]
121
 
            else:
122
 
                weak = False
123
 
            if value.startswith('"'):
124
 
                try:
125
 
                    etag, rest = value[1:].split('"', 1)
126
 
                except ValueError:
127
 
                    etag = value.strip(' ",')
128
 
                    rest = ''
129
 
                else:
130
 
                    rest = rest.strip(', ')
131
 
            else:
132
 
                if ',' in value:
133
 
                    etag, rest = value.split(',', 1)
134
 
                    rest = rest.strip()
135
 
                else:
136
 
                    etag = value
137
 
                    rest = ''
138
 
            if etag == '*':
139
 
                return AnyETag
140
 
            if etag:
141
 
                if weak:
142
 
                    weak_results.append(etag)
143
 
                else:
144
 
                    results.append(etag)
145
 
            value = rest
146
 
        return cls(results, weak_results)
 
114
        if value == '*':
 
115
            return AnyETag
 
116
        if not value:
 
117
            return cls([])
 
118
        matches = _rx_etag.findall(value)
 
119
        if not matches:
 
120
            return cls([value])
 
121
        elif strong:
 
122
            return cls([t for w,t in matches if not w])
 
123
        else:
 
124
            return cls([t for w,t in matches])
147
125
 
148
126
    def __str__(self):
149
 
        items = map('"%s"'.__mod__, self.etags)
150
 
        for weak in self.weak_etags:
151
 
            items.append('W/"%s"' % weak)
152
 
        return ', '.join(items)
 
127
        return ', '.join(map('"%s"'.__mod__, self.etags))
 
128
 
153
129
 
154
130
class IfRange(object):
155
 
    """
156
 
    Parses and represents the If-Range header, which can be
157
 
    an ETag *or* a date
158
 
    """
159
 
    def __init__(self, etag=None, date=None):
 
131
    def __init__(self, etag):
160
132
        self.etag = etag
161
 
        self.date = date
162
 
 
163
 
    def __repr__(self):
164
 
        if self.etag is None:
165
 
            etag = '*'
166
 
        else:
167
 
            etag = str(self.etag)
168
 
        if self.date is None:
169
 
            date = '*'
170
 
        else:
171
 
            date = serialize_date(self.date)
172
 
        return '<%s etag=%s, date=%s>' % (
173
 
            self.__class__.__name__,
174
 
            etag, date)
175
 
 
176
 
    def __str__(self):
177
 
        if self.etag is not None:
178
 
            return str(self.etag)
179
 
        elif self.date:
180
 
            return serialize_date(self.date)
181
 
        else:
182
 
            return ''
183
 
 
184
 
    def match(self, etag=None, last_modified=None):
185
 
        """
186
 
        Return True if the If-Range header matches the given etag or last_modified
187
 
        """
188
 
        if self.date is not None:
189
 
            if last_modified is None:
190
 
                # Conditional with nothing to base the condition won't work
191
 
                return False
192
 
            return last_modified <= self.date
193
 
        elif self.etag is not None:
194
 
            if not etag:
195
 
                return False
196
 
            return etag in self.etag
197
 
        return True
198
 
 
199
 
    def match_response(self, response):
200
 
        """
201
 
        Return True if this matches the given ``webob.Response`` instance.
202
 
        """
203
 
        return self.match(etag=response.etag, last_modified=response.last_modified)
204
133
 
205
134
    @classmethod
206
135
    def parse(cls, value):
207
136
        """
208
137
        Parse this from a header value.
209
138
        """
210
 
        date = etag = None
211
139
        if not value:
212
 
            etag = NoETag()
213
 
        elif value and value.endswith(' GMT'):
 
140
            return cls(AnyETag)
 
141
        elif value.endswith(' GMT'):
214
142
            # Must be a date
215
 
            date = parse_date(value)
 
143
            return IfRangeDate(parse_date(value))
216
144
        else:
217
 
            etag = ETagMatcher.parse(value)
218
 
        return cls(etag=etag, date=date)
219
 
 
220
 
class _NoIfRange(object):
221
 
    """
222
 
    Represents a missing If-Range header
223
 
    """
224
 
 
225
 
    def __repr__(self):
226
 
        return '<Empty If-Range>'
227
 
 
228
 
    def __str__(self):
229
 
        return ''
 
145
            return cls(ETagMatcher.parse(value))
 
146
 
 
147
    def __contains__(self, resp):
 
148
        """
 
149
        Return True if the If-Range header matches the given etag or last_modified
 
150
        """
 
151
        return resp.etag_strong in self.etag
230
152
 
231
153
    def __nonzero__(self):
232
 
        return False
233
 
 
234
 
    def match(self, etag=None, last_modified=None):
235
 
        return True
236
 
 
237
 
    def match_response(self, response):
238
 
        return True
239
 
 
240
 
NoIfRange = _NoIfRange()
241
 
 
242
 
 
 
154
        return bool(self.etag)
 
155
 
 
156
    def __repr__(self):
 
157
        return '%s(%r)' % (
 
158
            self.__class__.__name__,
 
159
            self.etag
 
160
        )
 
161
 
 
162
    def __str__(self):
 
163
        return str(self.etag) if self.etag else ''
 
164
 
 
165
    match = match_response = _warn_if_range_match_deprecated
 
166
 
 
167
    __bool__ = __nonzero__ # python 3
 
168
 
 
169
class IfRangeDate(object):
 
170
    def __init__(self, date):
 
171
        self.date = date
 
172
 
 
173
    def __contains__(self, resp):
 
174
        last_modified = resp.last_modified
 
175
        #if isinstance(last_modified, str):
 
176
        #    last_modified = parse_date(last_modified)
 
177
        return last_modified and (last_modified <= self.date)
 
178
 
 
179
    def __repr__(self):
 
180
        return '%s(%r)' % (
 
181
            self.__class__.__name__,
 
182
            self.date
 
183
            #serialize_date(self.date)
 
184
        )
 
185
 
 
186
    def __str__(self):
 
187
        return serialize_date(self.date)
 
188
 
 
189
    match = match_response = _warn_if_range_match_deprecated