123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- # -*- coding: utf-8 -*-
- """
- kodiswift.urls
- ---------------
-
- This module contains URLRule class for dealing with url patterns.
-
- :copyright: (c) 2012 by Jonathan Beluch
- :license: GPLv3, see LICENSE for more details.
- """
- from __future__ import absolute_import
-
- import re
- from urllib import urlencode, unquote_plus, quote_plus
-
- from kodiswift.common import pickle_dict, unpickle_dict
-
- __all__ = ['UrlRule', 'AmbiguousUrlException', 'NotFoundException']
-
-
- class AmbiguousUrlException(Exception):
- pass
-
-
- class NotFoundException(Exception):
- pass
-
-
- class UrlRule(object):
- """A class the encapsulates a URL
- """
-
- def __init__(self, url_rule, view_func, name, options):
- """Stores the various properties related to a routing URL rule.
-
- It also provides a few methods to create URLs from the rule or to
- match a given URL against a rule.
-
- Args:
- url_rule: The relative url pattern for the rule.
- It may include <var_name> to denote where dynamic variables
- should be matched.
- view_func: The function that should be bound to this rule.
- This should be an actual function object.
- name: The name of the url rule. This is used in the reverse
- process of creating urls for a given rule.
- options: A dict containing any default values for the url rule.
-
- Warnings:
- view_func: The function signature should match any variable
- names in the provided url_rule.
- """
- self._name = name
- self._url_rule = url_rule
- self._view_func = view_func
- self._options = options or {}
- self._keywords = re.findall(r'<(.+?)>', url_rule)
-
- # change <> to {} for use with str.format()
- self._url_format = self._url_rule.replace('<', '{').replace('>', '}')
-
- # Make a regex pattern for matching incoming URLs
- rule = self._url_rule
- if rule != '/':
- # Except for a path of '/', the trailing slash is optional.
- rule = self._url_rule.rstrip('/') + '/?'
- p = rule.replace('<', '(?P<').replace('>', '>[^/]+?)')
-
- try:
- self._regex = re.compile('^' + p + '$')
- except re.error:
- raise ValueError('There was a problem creating this URL rule. '
- 'Ensure you do not have any unpaired angle '
- 'brackets: "<" or ">"')
-
- def __eq__(self, other):
- if isinstance(other, UrlRule):
- return (
- (self._name, self._url_rule, self._view_func, self._options) ==
- (other._name, other._url_rule, other._view_func, other._options)
- )
- else:
- raise NotImplementedError
-
- def __ne__(self, other):
- return not self == other
-
- def match(self, path):
- """Attempts to match a url to the given path.
-
- If successful, a tuple is returned. The first item is the matched
- function and the second item is a dictionary containing items to be
- passed to the function parsed from the provided path.
-
- Args:
- path (str): The URL path.
-
- Raises:
- NotFoundException: If the provided path does not match this
- url rule.
- """
- m = self._regex.search(path)
- if not m:
- raise NotFoundException
-
- # urlunencode the values
- items = dict((key, unquote_plus(val))
- for key, val in m.groupdict().items())
-
- # unpickle any items if present
- items = unpickle_dict(items)
-
- # We need to update our dictionary with default values provided in
- # options if the keys don't already exist.
- [items.setdefault(key, val) for key, val in self._options.items()]
- return self._view_func, items
-
- def _make_path(self, items):
- """Returns a relative path for the given dictionary of items.
-
- Uses this url rule's url pattern and replaces instances of <var_name>
- with the appropriate value from the items dict.
- """
- for key, val in items.items():
- if not isinstance(val, basestring):
- raise TypeError('Value "%s" for key "%s" must be an instance'
- ' of basestring' % (val, key))
- items[key] = quote_plus(val)
-
- try:
- path = self._url_format.format(**items)
- except AttributeError:
- # Old version of python
- path = self._url_format
- for key, val in items.items():
- path = path.replace('{%s}' % key, val)
- return path
-
- def _make_qs(self, items):
- """Returns a query string for the given dictionary of items. All keys
- and values in the provided items will be urlencoded. If necessary, any
- python objects will be pickled before being urlencoded.
- """
- return urlencode(pickle_dict(items))
-
- def make_path_qs(self, items):
- """Returns a relative path complete with query string for the given
- dictionary of items.
-
- Any items with keys matching this rule's url pattern will be inserted
- into the path. Any remaining items will be appended as query string
- parameters.
-
- All items will be urlencoded. Any items which are not instances of
- basestring, or int/long will be pickled before being urlencoded.
-
- .. warning:: The pickling of items only works for key/value pairs which
- will be in the query string. This behavior should only be
- used for the simplest of python objects. It causes the
- URL to get very lengthy (and unreadable) and Kodi has a
- hard limit on URL length. See the caching section if you
- need to persist a large amount of data between requests.
- """
- # Convert any ints and longs to strings
- for key, val in items.items():
- if isinstance(val, (int, long)):
- items[key] = str(val)
-
- # First use our defaults passed when registering the rule
- url_items = dict((key, val) for key, val in self._options.items()
- if key in self._keywords)
-
- # Now update with any items explicitly passed to url_for
- url_items.update((key, val) for key, val in items.items()
- if key in self._keywords)
-
- # Create the path
- path = self._make_path(url_items)
-
- # Extra arguments get tacked on to the query string
- qs_items = dict((key, val) for key, val in items.items()
- if key not in self._keywords)
- qs = self._make_qs(qs_items)
-
- if qs:
- return '?'.join([path, qs])
- return path
-
- @property
- def regex(self):
- """The regex for matching paths against this url rule."""
- return self._regex
-
- @property
- def view_func(self):
- """The bound function"""
- return self._view_func
-
- @property
- def url_format(self):
- """The url pattern"""
- return self._url_format
-
- @property
- def name(self):
- """The name of this url rule."""
- return self._name
-
- @property
- def keywords(self):
- """The list of path keywords for this url rule."""
- return self._keywords
|