URL映射

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()函数中可以看到具名和非具名的两种路径的匹配设置。

This entry was posted in Python. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>