236 if value is None and self.default : |
236 if value is None and self.default : |
237 return LabelValue(self, self.default) |
237 return LabelValue(self, self.default) |
238 |
238 |
239 # only non-empty values! |
239 # only non-empty values! |
240 elif value : |
240 elif value : |
|
241 # test |
|
242 if not self.type.test(value) : |
|
243 return False |
|
244 |
241 # convert with type |
245 # convert with type |
242 try : |
246 value = self.type.parse(value) |
243 value = self.type(value) |
|
244 |
|
245 except Exception, e : |
|
246 raise URLError("Bad value %r for type %s: %s: %s" % (value, self.type.__name__, type(e).__name__, e)) |
|
247 |
247 |
248 return LabelValue(self, value) |
248 return LabelValue(self, value) |
249 |
249 |
250 def __str__ (self) : |
250 def __str__ (self) : |
251 return '{%s%s%s}' % ( |
251 return '{%s%s%s}' % ( |
252 self.key, |
252 self.key, |
253 ':%s' % (self.type.__name__ ) if self.type != str else '', |
253 ':%s' % (self.type, ), # XXX: omit if default |
254 '=%s' % (self.default, ) if self.default else '', |
254 '=%s' % (self.default, ) if self.default else '', |
255 ) |
255 ) |
256 |
256 |
|
257 class URLType (object) : |
|
258 """ |
|
259 Handles the type-ness of values in the URL |
|
260 """ |
|
261 |
|
262 def _init_name (self, name) : |
|
263 """ |
|
264 Initialize our .name attribute, called by URLConfig |
|
265 """ |
|
266 |
|
267 self.name = name |
|
268 |
|
269 def test (self, value) : |
|
270 """ |
|
271 Tests if the given value is valid for this type. |
|
272 |
|
273 Defaults to calling parse(), and returning False on errors, True otherwise |
|
274 """ |
|
275 |
|
276 try : |
|
277 self.parse(value) |
|
278 |
|
279 except : |
|
280 return False |
|
281 |
|
282 else : |
|
283 return True |
|
284 |
|
285 def parse (self, value) : |
|
286 """ |
|
287 Parse the given value, which was tested earlier with test(), and return the value object |
|
288 """ |
|
289 |
|
290 abstract |
|
291 |
|
292 |
|
293 def build (self, obj) : |
|
294 """ |
|
295 Reverse of parse(), return an url-value built from the given object |
|
296 """ |
|
297 |
|
298 abstract |
|
299 |
|
300 def __str__ (self) : |
|
301 """ |
|
302 Return a short string giving the name of this type, defaults to self.name |
|
303 """ |
|
304 |
|
305 return self.name |
|
306 |
|
307 class URLStringType (URLType) : |
|
308 """ |
|
309 The default URLType, just plain strings. |
|
310 |
|
311 Note that this does not accept empty strings as valid |
|
312 """ |
|
313 |
|
314 def __init__ (self) : |
|
315 super(URLStringType, self).__init__('str') |
|
316 |
|
317 def parse (self, value) : |
|
318 """ |
|
319 Identitiy |
|
320 """ |
|
321 |
|
322 return value |
|
323 |
|
324 def build (self, obj) : |
|
325 if not obj : |
|
326 raise ValueError("String must not be empty") |
|
327 |
|
328 return str(obj) |
|
329 |
|
330 class URLIntegerType (URLType) : |
|
331 """ |
|
332 A URLType for simple integers |
|
333 """ |
|
334 |
|
335 def __init__ (self, negative=True, zero=True, max=None) : |
|
336 """ |
|
337 Pass in negative=False to disallow negative numbers, zero=False to disallow zero, or non-zero max |
|
338 to specifiy maximum value |
|
339 """ |
|
340 |
|
341 super(URLIntegerType, self).__init__('int') |
|
342 |
|
343 self.negative = negative |
|
344 self.zero = zero |
|
345 self.max = max |
|
346 |
|
347 def _validate (self, value) : |
|
348 """ |
|
349 Test to make sure value fits our criteria |
|
350 """ |
|
351 |
|
352 # negative? |
|
353 if self.negative and value < 0 : |
|
354 raise ValueError("value is negative") |
|
355 |
|
356 # zero? |
|
357 if self.zero and value == 0 : |
|
358 raise ValueError("value is zero") |
|
359 |
|
360 # max? |
|
361 if self.max is not None and value > max : |
|
362 raise ValueError("value is too large: %d" % value) |
|
363 |
|
364 return value |
|
365 |
|
366 def parse (self, value) : |
|
367 """ |
|
368 Convert str -> int |
|
369 """ |
|
370 |
|
371 return self._validate(int(value)) |
|
372 |
|
373 def build (self, obj) : |
|
374 """ |
|
375 Convert int -> str |
|
376 """ |
|
377 |
|
378 return unicode(self._validate(obj)) |
|
379 |
257 class URLConfig (object) : |
380 class URLConfig (object) : |
258 """ |
381 """ |
259 Global configuration relevant to all URLs |
382 Global configuration relevant to all URLs. This can be used to construct a set of URLs and then create an |
|
383 URLTree out of them. Simply call the url_config() instace with the normal URL arguments (except, of course, |
|
384 config), and finally just pass the url_config to URLTree (it's iterable). |
260 """ |
385 """ |
261 |
386 |
262 # built-in type codes |
387 # built-in type codes |
263 BUILTIN_TYPES = { |
388 BUILTIN_TYPES = { |
264 # default |
|
265 None : str, |
|
266 |
|
267 # string |
389 # string |
268 'str' : str, |
390 'str' : URLStringType(), |
269 |
391 |
270 # integer |
392 # integer |
271 'int' : int, |
393 'int' : URLIntegerType(), |
272 } |
394 } |
273 |
395 |
274 def __init__ (self, type_dict=None) : |
396 # init names |
|
397 for name, type in BUILTIN_TYPES.iteritems() : |
|
398 type._init_name(name) |
|
399 |
|
400 def __init__ (self, type_dict=None, default_type='str') : |
275 """ |
401 """ |
276 Create an URLConfig for use with URL |
402 Create an URLConfig for use with URL |
277 |
403 |
278 If type_dict is given, it should be a mapping of type names -> callables, and they will be available for |
404 If type_dict is given, it should be a dict of { type_names: URLType }, and they will be available for |
279 type specifications in addition to the defaults. |
405 type specifications in addition to the defaults. |
280 """ |
406 """ |
281 |
407 |
282 # build our type_dict |
408 # build our type_dict |
283 self.type_dict = self.BUILTIN_TYPES.copy() |
409 self.type_dict = self.BUILTIN_TYPES.copy() |
284 |
410 |
285 # apply the given type_dict |
411 # apply the given type_dict |
286 if type_dict : |
412 if type_dict : |
|
413 # initialize names |
|
414 for name, type in type_dict.iteritems() : |
|
415 type._init_name(name) |
|
416 |
|
417 # merge |
287 self.type_dict.update(type_dict) |
418 self.type_dict.update(type_dict) |
288 |
419 |
|
420 # init |
|
421 self.default_type = default_type |
|
422 self.urls = [] |
|
423 |
|
424 def get_type (self, type_name=None) : |
|
425 """ |
|
426 Lookup an URLType by type_name, None for default |
|
427 """ |
|
428 |
|
429 # default type? |
|
430 if not type_name : |
|
431 type_name = self.default_type |
|
432 |
|
433 # lookup + return |
|
434 return self.type_dict[type_name] |
|
435 |
|
436 def __call__ (self, *args, **kwargs) : |
|
437 """ |
|
438 Return new URL object with this config and the given args, adding it to our list of urls |
|
439 """ |
|
440 |
|
441 # build |
|
442 url = URL(self, *args, **kwargs) |
|
443 |
|
444 # store |
|
445 self.urls.append(url) |
|
446 |
|
447 # return |
|
448 return url |
|
449 |
|
450 def __iter__ (self) : |
|
451 """ |
|
452 Returns all defined URLs |
|
453 """ |
|
454 |
|
455 return iter(self.urls) |
|
456 |
289 class URL (object) : |
457 class URL (object) : |
290 """ |
458 """ |
291 Represents a specific URL |
459 Represents a specific URL |
292 """ |
460 """ |
293 |
461 |
294 |
462 |
295 def __init__ (self, config, url_mask, handler, type_dict=None, **defaults) : |
463 def __init__ (self, config, url_mask, handler, **defaults) : |
296 """ |
464 """ |
297 Create an URL using the given URLConfig, with the given url mask, handler, and default values. |
465 Create an URL using the given URLConfig, with the given url mask, handler, and default values. |
298 """ |
466 """ |
299 |
467 |
300 # store |
468 # store |