comparison piecrust/routing.py @ 790:4cbe057a8b6a

routing: Add some backwards compatibility support for parameter types. This makes it so old routes are still correctly formatting years/months/days which were previously hard-coded to int4/int2 types.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 05 Sep 2016 22:12:37 -0700
parents f6f9a284a5f3
children 504d6817352d
comparison
equal deleted inserted replaced
789:b8e760b3413e 790:4cbe057a8b6a
64 64
65 # Get the straight-forward regex for matching this URI pattern. 65 # Get the straight-forward regex for matching this URI pattern.
66 p = route_esc_re.sub(self._uriPatternRepl, 66 p = route_esc_re.sub(self._uriPatternRepl,
67 re.escape(self.uri_pattern)) + '$' 67 re.escape(self.uri_pattern)) + '$'
68 self.uri_re = re.compile(p) 68 self.uri_re = re.compile(p)
69
70 # Get the types of the route parameters.
71 self.param_types = {}
72 for m in route_re.finditer(self.uri_pattern):
73 qual = m.group('qual')
74 if qual:
75 self.param_types[str(m.group('name'))] = qual
76 69
77 # If the URI pattern has a 'path'-type component, we'll need to match 70 # If the URI pattern has a 'path'-type component, we'll need to match
78 # the versions for which that component is empty. So for instance if 71 # the versions for which that component is empty. So for instance if
79 # we have `/foo/%path:bar%`, we may need to match `/foo` (note the 72 # we have `/foo/%path:bar%`, we may need to match `/foo` (note the
80 # lack of a trailing slash). We have to build a special pattern (in 73 # lack of a trailing slash). We have to build a special pattern (in
94 87
95 # Determine the parameters for the route function. 88 # Determine the parameters for the route function.
96 self.func_name = self._validateFuncName(cfg.get('func')) 89 self.func_name = self._validateFuncName(cfg.get('func'))
97 self.func_parameters = [] 90 self.func_parameters = []
98 self.func_has_variadic_parameter = False 91 self.func_has_variadic_parameter = False
92 self.param_types = {}
99 variadic_param_idx = -1 93 variadic_param_idx = -1
100 for m in route_re.finditer(self.uri_pattern): 94 for m in route_re.finditer(self.uri_pattern):
101 name = m.group('name') 95 name = m.group('name')
96 self.func_parameters.append(name)
97
98 qual = m.group('qual')
99 if not qual:
100 qual = self._getBackwardCompatibleParamType(name)
101 if qual:
102 self.param_types[name] = qual
103
102 if m.group('var'): 104 if m.group('var'):
103 self.func_has_variadic_parameter = True 105 self.func_has_variadic_parameter = True
104 variadic_param_idx = len(self.func_parameters) 106 variadic_param_idx = len(self.func_parameters)
105
106 self.func_parameters.append(name)
107 107
108 if (variadic_param_idx >= 0 and 108 if (variadic_param_idx >= 0 and
109 variadic_param_idx != len(self.func_parameters) - 1): 109 variadic_param_idx != len(self.func_parameters) - 1):
110 raise Exception( 110 raise Exception(
111 "Only the last route URL parameter can be variadic. " 111 "Only the last route URL parameter can be variadic. "
268 return self.getUri(metadata) 268 return self.getUri(metadata)
269 269
270 def _uriFormatRepl(self, m): 270 def _uriFormatRepl(self, m):
271 qual = m.group('qual') 271 qual = m.group('qual')
272 name = m.group('name') 272 name = m.group('name')
273
274 # Backwards compatibility... this will print a warning later.
275 if qual is None:
276 if name == 'year':
277 qual = 'int4'
278 elif name in ['month', 'day']:
279 qual = 'int2'
280
273 if qual == 'int4': 281 if qual == 'int4':
274 return '%%(%s)04d' % name 282 return '%%(%s)04d' % name
275 elif qual == 'int2': 283 elif qual == 'int2':
276 return '%%(%s)02d' % name 284 return '%%(%s)02d' % name
285 elif qual and qual != 'path':
286 raise Exception("Unknown route parameter type: %s" % qual)
277 return '%%(%s)s' % name 287 return '%%(%s)s' % name
278 288
279 def _uriPatternRepl(self, m): 289 def _uriPatternRepl(self, m):
280 name = m.group('name') 290 name = m.group('name')
281 qual = m.group('qual') 291 qual = m.group('qual')
292
293 # Backwards compatibility... this will print a warning later.
294 if qual is None:
295 if name == 'year':
296 qual = 'int4'
297 elif name in ['month', 'day']:
298 qual = 'int2'
299
282 if qual == 'path' or m.group('var'): 300 if qual == 'path' or m.group('var'):
283 return r'(?P<%s>[^\?]*)' % name 301 return r'(?P<%s>[^\?]*)' % name
284 elif qual == 'int4': 302 elif qual == 'int4':
285 return r'(?P<%s>\d{4})' % name 303 return r'(?P<%s>\d{4})' % name
286 elif qual == 'int2': 304 elif qual == 'int2':
287 return r'(?P<%s>\d{2})' % name 305 return r'(?P<%s>\d{2})' % name
306 elif qual and qual != 'path':
307 raise Exception("Unknown route parameter type: %s" % qual)
288 return r'(?P<%s>[^/\?]+)' % name 308 return r'(?P<%s>[^/\?]+)' % name
289 309
290 def _uriNoPathRepl(self, m): 310 def _uriNoPathRepl(self, m):
291 name = m.group('name') 311 name = m.group('name')
292 qualifier = m.group('qual') 312 qualifier = m.group('qual')
301 if param_type in ['int', 'int2', 'int4']: 321 if param_type in ['int', 'int2', 'int4']:
302 try: 322 try:
303 return int(val) 323 return int(val)
304 except ValueError: 324 except ValueError:
305 raise Exception( 325 raise Exception(
306 "Expected route parameter '%s' to be of type " 326 "Expected route parameter '%s' to be of type "
307 "'%s', but was: %s" % 327 "'%s', but was: %s" %
308 (name, param_type, val)) 328 (name, param_type, val))
309 if param_type == 'path': 329 if param_type == 'path':
310 return val 330 return val
311 raise Exception("Unknown route parameter type: %s" % param_type) 331 raise Exception("Unknown route parameter type: %s" % param_type)
332
333 def _getBackwardCompatibleParamType(self, name):
334 # Print a warning only if we're not in a worker process.
335 print_warning = not self.app.config.has('baker/worker_id')
336
337 if name in ['year']:
338 if print_warning:
339 logger.warning(
340 "Route parameter '%%%s%%' has no type qualifier. "
341 "You probably meant '%%int4:%s%%' so we'll use that." %
342 (name, name))
343 return 'int4'
344 if name in ['month', 'day']:
345 if print_warning:
346 logger.warning(
347 "Route parameter '%%%s%%' has no type qualifier. "
348 "You probably meant '%%int2:%s%%' so we'll use that." %
349 (name, name))
350 return 'int2'
351 return None
312 352
313 def _validateFuncName(self, name): 353 def _validateFuncName(self, name):
314 if not name: 354 if not name:
315 return None 355 return None
316 i = name.find('(') 356 i = name.find('(')