url映射是目前流行的框架都提供的基本功能,参考了django的url映射的源码,用python实现url映射的主要逻辑还是比较简单的。主要是由两个部分组成:
- 将处理函数的命名空间解析成模块,使用__import__,导入模块,获取处理函数的引用(见get_callable());
- 匹配访问路径,提取参数,使用正则表达式进行匹配。
# add url_base as namespace to handlers in url_patterns
def include(url_base, url_patterns):
for index, src_pattern in enumerate(url_patterns):
pattern = list(src_pattern)
pattern[1] = "%s.%s" % (url_base, pattern[1])
url_patterns[index] = tuple(pattern)
return url_patterns
# get module name and func name
def get_mod_func(handler):
try:
dot = handler.rindex(".")
except ValueError:
return handler, ''
return handler[:dot], handler[dot+1:]
# analyze handler path, return handler ref
def get_callable(handler):
if not callable(handler):
mod_name, func_name = get_mod_func(handler)
mod = __import__(mod_name, globals(), locals(), [func_name])
if func_name != '':
func = getattr(mod, func_name)
if not callable(func):
raise AttributeError('%s.%s is not callable.' % (mod_name, func_name))
return func
return handler
# resolve path info, return handler and args
# path : path_info
# url_config : url_patterns, ref test()
# default_kwargs : default args passed to handler
def resolve(path, url_config, default_kwargs={}):
import re
for config in url_config:
pattern, handler_name = config[0], config[1]
other_args = config[2:]
handler = get_callable(handler_name)
match = re.compile(pattern).search(path)
if match:
kwargs = match.groupdict()
# If there are any named groups, use those as kwargs, ignoring
# non-named groups. Otherwise, pass all non-named arguments as
# positional arguments.
if kwargs:
args = ()
for arg in other_args: kwargs.update(arg)
else:
args = match.groups() + other_args
kwargs.update(default_kwargs)
return handler, args, kwargs
else:
raise
def test():
url_config = [
(r"^/(?P<username>\w+)/blog/(?P<blogid>\d+)/?$", "user.test.blogview", {'test':True, 'arg0':'hello world'}),
(r"^/user/(\d+)/?$", "lib.test2.userview", 'fuckgfw', 'fuckgov')
]
url_config = include("app", url_config)
handler, args, kwargs = resolve("/stephenchan/blog/890604/", url_config)
handler(*args, **kwargs)
handler, args, kwargs = resolve("/user/406098", url_config)
handler(*args, **kwargs)
if __name__ == '__main__':
test()
测试的目录结构如下:
|-- app | |-- __init__.py | |-- __init__.pyc | |-- lib | | |-- __init__.py | | |-- __init__.pyc | | |-- test2.py | | `-- test2.pyc | `-- user | |-- __init__.py | |-- __init__.pyc | |-- test.py | `-- test.pyc
两个测试的函数逻辑很简单:
# app.lib.test2.userview
def userview(uid, arg1, arg2):
print """
userid = %s
arg1 = %s
arg2 = %s
"""%(uid, arg1, arg2)
# app.user.test.blogview
def blogview(username, blogid, test=False, arg0=""):
print """
username = %s
blogid = %s
test = %s
arg0 = %s
""" % (username, blogid, test, arg0)
由于在匹配的结果中python正则表达式对象在matchObject.groups()返回的结果中包括了matchObject.groupdict()的结果,因此在匹配一个路径的时候难以将具名和非具名的参数分离,即具名参数和非具名参数只能以互斥的关系存在。test()函数中可以看到具名和非具名的两种路径的匹配设置。