Skip to content

Commit 2b9abce

Browse files
author
mtredinnick
committed
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
1 parent f5f99a6 commit 2b9abce

File tree

2 files changed

+53
-30
lines changed

2 files changed

+53
-30
lines changed

django/core/urlresolvers.py

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,34 @@
99

1010
from django.http import Http404
1111
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
12+
from django.utils.functional import memoize
1213
import re
1314

15+
_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
16+
_callable_cache = {} # Maps view and url pattern names to their view functions.
17+
1418
class Resolver404(Http404):
1519
pass
1620

1721
class NoReverseMatch(Exception):
1822
# Don't make this raise an error when used in a template.
1923
silent_variable_failure = True
2024

25+
def get_callable(lookup_view):
26+
if not callable(lookup_view):
27+
mod_name, func_name = get_mod_func(lookup_view)
28+
if func_name != '':
29+
lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
30+
return lookup_view
31+
get_callable = memoize(get_callable, _callable_cache)
32+
33+
def get_resolver(urlconf):
34+
if urlconf is None:
35+
from django.conf import settings
36+
urlconf = settings.ROOT_URLCONF
37+
return RegexURLResolver(r'^/', urlconf)
38+
get_resolver = memoize(get_resolver, _resolver_cache)
39+
2140
def get_mod_func(callback):
2241
# Converts 'django.views.news.stories.story_detail' to
2342
# ['django.views.news.stories', 'story_detail']
@@ -129,9 +148,8 @@ def resolve(self, path):
129148
def _get_callback(self):
130149
if self._callback is not None:
131150
return self._callback
132-
mod_name, func_name = get_mod_func(self._callback_str)
133151
try:
134-
self._callback = getattr(__import__(mod_name, {}, {}, ['']), func_name)
152+
self._callback = get_callable(self._callback_str)
135153
except ImportError, e:
136154
raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
137155
except AttributeError, e:
@@ -160,6 +178,15 @@ def __init__(self, regex, urlconf_name, default_kwargs=None):
160178
self.urlconf_name = urlconf_name
161179
self.callback = None
162180
self.default_kwargs = default_kwargs or {}
181+
self.reverse_dict = {}
182+
183+
for pattern in reversed(self.urlconf_module.urlpatterns):
184+
if isinstance(pattern, RegexURLResolver):
185+
for key, value in pattern.reverse_dict.iteritems():
186+
self.reverse_dict[key] = (pattern,) + value
187+
else:
188+
self.reverse_dict[pattern.callback] = (pattern,)
189+
self.reverse_dict[pattern.name] = (pattern,)
163190

164191
def resolve(self, path):
165192
tried = []
@@ -209,24 +236,12 @@ def resolve500(self):
209236
return self._resolve_special('500')
210237

211238
def reverse(self, lookup_view, *args, **kwargs):
212-
if not callable(lookup_view):
213-
mod_name, func_name = get_mod_func(lookup_view)
214-
try:
215-
lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
216-
except (ImportError, AttributeError):
217-
if func_name != '':
218-
raise NoReverseMatch
219-
for pattern in self.urlconf_module.urlpatterns:
220-
if isinstance(pattern, RegexURLResolver):
221-
try:
222-
return pattern.reverse_helper(lookup_view, *args, **kwargs)
223-
except NoReverseMatch:
224-
continue
225-
elif pattern.callback == lookup_view or pattern.name == lookup_view:
226-
try:
227-
return pattern.reverse_helper(*args, **kwargs)
228-
except NoReverseMatch:
229-
continue
239+
try:
240+
lookup_view = get_callable(lookup_view)
241+
except (ImportError, AttributeError):
242+
raise NoReverseMatch
243+
if lookup_view in self.reverse_dict:
244+
return ''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]])
230245
raise NoReverseMatch
231246

232247
def reverse_helper(self, lookup_view, *args, **kwargs):
@@ -235,17 +250,10 @@ def reverse_helper(self, lookup_view, *args, **kwargs):
235250
return result + sub_match
236251

237252
def resolve(path, urlconf=None):
238-
if urlconf is None:
239-
from django.conf import settings
240-
urlconf = settings.ROOT_URLCONF
241-
resolver = RegexURLResolver(r'^/', urlconf)
242-
return resolver.resolve(path)
253+
return get_resolver(urlconf).resolve(path)
243254

244255
def reverse(viewname, urlconf=None, args=None, kwargs=None):
245256
args = args or []
246257
kwargs = kwargs or {}
247-
if urlconf is None:
248-
from django.conf import settings
249-
urlconf = settings.ROOT_URLCONF
250-
resolver = RegexURLResolver(r'^/', urlconf)
251-
return '/' + resolver.reverse(viewname, *args, **kwargs)
258+
return '/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs)
259+

django/utils/functional.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@ def _curried(*moreargs, **morekwargs):
33
return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
44
return _curried
55

6+
def memoize(func, cache):
7+
"""
8+
Wrap a function so that results for any argument tuple are stored in
9+
'cache'. Note that the args to the function must be usable as dictionary
10+
keys.
11+
"""
12+
def wrapper(*args):
13+
if args in cache:
14+
return cache[args]
15+
16+
result = func(*args)
17+
cache[args] = result
18+
return result
19+
return wrapper
20+
621
class Promise:
722
"""
823
This is just a base class for the proxy class created in

0 commit comments

Comments
 (0)