Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #4566 -- Added caching speed-ups to reverse URL matching. Based…

… on a

patch from smoo.master@gmail.com.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@5516 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 5f5f1d913bbe25dd9a33a2759144160e1473c12a 1 parent dfea6bd
@malcolmt malcolmt authored
Showing with 53 additions and 30 deletions.
  1. +38 −30 django/core/urlresolvers.py
  2. +15 −0 django/utils/functional.py
View
68 django/core/urlresolvers.py
@@ -9,8 +9,12 @@
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
+from django.utils.functional import memoize
import re
+_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
+_callable_cache = {} # Maps view and url pattern names to their view functions.
+
class Resolver404(Http404):
pass
@@ -18,6 +22,21 @@ class NoReverseMatch(Exception):
# Don't make this raise an error when used in a template.
silent_variable_failure = True
+def get_callable(lookup_view):
+ if not callable(lookup_view):
+ mod_name, func_name = get_mod_func(lookup_view)
+ if func_name != '':
+ lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ return lookup_view
+get_callable = memoize(get_callable, _callable_cache)
+
+def get_resolver(urlconf):
+ if urlconf is None:
+ from django.conf import settings
+ urlconf = settings.ROOT_URLCONF
+ return RegexURLResolver(r'^/', urlconf)
+get_resolver = memoize(get_resolver, _resolver_cache)
+
def get_mod_func(callback):
# Converts 'django.views.news.stories.story_detail' to
# ['django.views.news.stories', 'story_detail']
@@ -129,9 +148,8 @@ def resolve(self, path):
def _get_callback(self):
if self._callback is not None:
return self._callback
- mod_name, func_name = get_mod_func(self._callback_str)
try:
- self._callback = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ self._callback = get_callable(self._callback_str)
except ImportError, e:
raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
except AttributeError, e:
@@ -160,6 +178,15 @@ def __init__(self, regex, urlconf_name, default_kwargs=None):
self.urlconf_name = urlconf_name
self.callback = None
self.default_kwargs = default_kwargs or {}
+ self.reverse_dict = {}
+
+ for pattern in reversed(self.urlconf_module.urlpatterns):
+ if isinstance(pattern, RegexURLResolver):
+ for key, value in pattern.reverse_dict.iteritems():
+ self.reverse_dict[key] = (pattern,) + value
+ else:
+ self.reverse_dict[pattern.callback] = (pattern,)
+ self.reverse_dict[pattern.name] = (pattern,)
def resolve(self, path):
tried = []
@@ -209,24 +236,12 @@ def resolve500(self):
return self._resolve_special('500')
def reverse(self, lookup_view, *args, **kwargs):
- if not callable(lookup_view):
- mod_name, func_name = get_mod_func(lookup_view)
- try:
- lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
- except (ImportError, AttributeError):
- if func_name != '':
- raise NoReverseMatch
- for pattern in self.urlconf_module.urlpatterns:
- if isinstance(pattern, RegexURLResolver):
- try:
- return pattern.reverse_helper(lookup_view, *args, **kwargs)
- except NoReverseMatch:
- continue
- elif pattern.callback == lookup_view or pattern.name == lookup_view:
- try:
- return pattern.reverse_helper(*args, **kwargs)
- except NoReverseMatch:
- continue
+ try:
+ lookup_view = get_callable(lookup_view)
+ except (ImportError, AttributeError):
+ raise NoReverseMatch
+ if lookup_view in self.reverse_dict:
+ return ''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]])
raise NoReverseMatch
def reverse_helper(self, lookup_view, *args, **kwargs):
@@ -235,17 +250,10 @@ def reverse_helper(self, lookup_view, *args, **kwargs):
return result + sub_match
def resolve(path, urlconf=None):
- if urlconf is None:
- from django.conf import settings
- urlconf = settings.ROOT_URLCONF
- resolver = RegexURLResolver(r'^/', urlconf)
- return resolver.resolve(path)
+ return get_resolver(urlconf).resolve(path)
def reverse(viewname, urlconf=None, args=None, kwargs=None):
args = args or []
kwargs = kwargs or {}
- if urlconf is None:
- from django.conf import settings
- urlconf = settings.ROOT_URLCONF
- resolver = RegexURLResolver(r'^/', urlconf)
- return '/' + resolver.reverse(viewname, *args, **kwargs)
+ return '/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs)
+
View
15 django/utils/functional.py
@@ -3,6 +3,21 @@ def _curried(*moreargs, **morekwargs):
return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
return _curried
+def memoize(func, cache):
+ """
+ Wrap a function so that results for any argument tuple are stored in
+ 'cache'. Note that the args to the function must be usable as dictionary
+ keys.
+ """
+ def wrapper(*args):
+ if args in cache:
+ return cache[args]
+
+ result = func(*args)
+ cache[args] = result
+ return result
+ return wrapper
+
class Promise:
"""
This is just a base class for the proxy class created in
Please sign in to comment.
Something went wrong with that request. Please try again.