Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Represents the Cache-Control header 

3""" 

4import re 

5 

6class UpdateDict(dict): 

7 """ 

8 Dict that has a callback on all updates 

9 """ 

10 # these are declared as class attributes so that 

11 # we don't need to override constructor just to 

12 # set some defaults 

13 updated = None 

14 updated_args = None 

15 

16 def _updated(self): 

17 """ 

18 Assign to new_dict.updated to track updates 

19 """ 

20 updated = self.updated 

21 if updated is not None: 

22 args = self.updated_args 

23 if args is None: 

24 args = (self,) 

25 updated(*args) 

26 

27 def __setitem__(self, key, item): 

28 dict.__setitem__(self, key, item) 

29 self._updated() 

30 

31 def __delitem__(self, key): 

32 dict.__delitem__(self, key) 

33 self._updated() 

34 

35 def clear(self): 

36 dict.clear(self) 

37 self._updated() 

38 

39 def update(self, *args, **kw): 

40 dict.update(self, *args, **kw) 

41 self._updated() 

42 

43 def setdefault(self, key, value=None): 

44 val = dict.setdefault(self, key, value) 

45 if val is value: 

46 self._updated() 

47 return val 

48 

49 def pop(self, *args): 

50 v = dict.pop(self, *args) 

51 self._updated() 

52 return v 

53 

54 def popitem(self): 

55 v = dict.popitem(self) 

56 self._updated() 

57 return v 

58 

59 

60token_re = re.compile( 

61 r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?') 

62need_quote_re = re.compile(r'[^a-zA-Z0-9._-]') 

63 

64 

65class exists_property(object): 

66 """ 

67 Represents a property that either is listed in the Cache-Control 

68 header, or is not listed (has no value) 

69 """ 

70 def __init__(self, prop, type=None): 

71 self.prop = prop 

72 self.type = type 

73 

74 def __get__(self, obj, type=None): 

75 if obj is None: 

76 return self 

77 return self.prop in obj.properties 

78 

79 def __set__(self, obj, value): 

80 if (self.type is not None 

81 and self.type != obj.type): 

82 raise AttributeError( 

83 "The property %s only applies to %s Cache-Control" % ( 

84 self.prop, self.type)) 

85 

86 if value: 

87 obj.properties[self.prop] = None 

88 else: 

89 if self.prop in obj.properties: 

90 del obj.properties[self.prop] 

91 

92 def __delete__(self, obj): 

93 self.__set__(obj, False) 

94 

95 

96class value_property(object): 

97 """ 

98 Represents a property that has a value in the Cache-Control header. 

99 

100 When no value is actually given, the value of self.none is returned. 

101 """ 

102 def __init__(self, prop, default=None, none=None, type=None): 

103 self.prop = prop 

104 self.default = default 

105 self.none = none 

106 self.type = type 

107 

108 def __get__(self, obj, type=None): 

109 if obj is None: 

110 return self 

111 if self.prop in obj.properties: 

112 value = obj.properties[self.prop] 

113 if value is None: 

114 return self.none 

115 else: 

116 return value 

117 else: 

118 return self.default 

119 

120 def __set__(self, obj, value): 

121 if (self.type is not None 

122 and self.type != obj.type): 

123 raise AttributeError( 

124 "The property %s only applies to %s Cache-Control" % ( 

125 self.prop, self.type)) 

126 if value == self.default: 

127 if self.prop in obj.properties: 

128 del obj.properties[self.prop] 

129 elif value is True: 

130 obj.properties[self.prop] = None # Empty value, but present 

131 else: 

132 obj.properties[self.prop] = value 

133 

134 def __delete__(self, obj): 

135 if self.prop in obj.properties: 

136 del obj.properties[self.prop] 

137 

138 

139class CacheControl(object): 

140 

141 """ 

142 Represents the Cache-Control header. 

143 

144 By giving a type of ``'request'`` or ``'response'`` you can 

145 control what attributes are allowed (some Cache-Control values 

146 only apply to requests or responses). 

147 """ 

148 

149 update_dict = UpdateDict 

150 

151 def __init__(self, properties, type): 

152 self.properties = properties 

153 self.type = type 

154 

155 @classmethod 

156 def parse(cls, header, updates_to=None, type=None): 

157 """ 

158 Parse the header, returning a CacheControl object. 

159 

160 The object is bound to the request or response object 

161 ``updates_to``, if that is given. 

162 """ 

163 if updates_to: 

164 props = cls.update_dict() 

165 props.updated = updates_to 

166 else: 

167 props = {} 

168 for match in token_re.finditer(header): 

169 name = match.group(1) 

170 value = match.group(2) or match.group(3) or None 

171 if value: 

172 try: 

173 value = int(value) 

174 except ValueError: 

175 pass 

176 props[name] = value 

177 obj = cls(props, type=type) 

178 if updates_to: 

179 props.updated_args = (obj,) 

180 return obj 

181 

182 def __repr__(self): 

183 return '<CacheControl %r>' % str(self) 

184 

185 # Request values: 

186 # no-cache shared (below) 

187 # no-store shared (below) 

188 # max-age shared (below) 

189 max_stale = value_property('max-stale', none='*', type='request') 

190 min_fresh = value_property('min-fresh', type='request') 

191 # no-transform shared (below) 

192 only_if_cached = exists_property('only-if-cached', type='request') 

193 

194 # Response values: 

195 public = exists_property('public', type='response') 

196 private = value_property('private', none='*', type='response') 

197 no_cache = value_property('no-cache', none='*') 

198 no_store = exists_property('no-store') 

199 no_transform = exists_property('no-transform') 

200 must_revalidate = exists_property('must-revalidate', type='response') 

201 proxy_revalidate = exists_property('proxy-revalidate', type='response') 

202 max_age = value_property('max-age', none=-1) 

203 s_maxage = value_property('s-maxage', type='response') 

204 s_max_age = s_maxage 

205 stale_while_revalidate = value_property( 

206 'stale-while-revalidate', type='response') 

207 stale_if_error = value_property('stale-if-error', type='response') 

208 

209 def __str__(self): 

210 return serialize_cache_control(self.properties) 

211 

212 def copy(self): 

213 """ 

214 Returns a copy of this object. 

215 """ 

216 return self.__class__(self.properties.copy(), type=self.type) 

217 

218 

219def serialize_cache_control(properties): 

220 if isinstance(properties, CacheControl): 

221 properties = properties.properties 

222 parts = [] 

223 for name, value in sorted(properties.items()): 

224 if value is None: 

225 parts.append(name) 

226 continue 

227 value = str(value) 

228 if need_quote_re.search(value): 

229 value = '"%s"' % value 

230 parts.append('%s=%s' % (name, value)) 

231 return ', '.join(parts)