python routes Mapper的使用

最近在学openstack,发现openstack的好几个组件,都用到了routes.Mapper作为WSGI app的路由控制。nova就在用。nova在用的时候外面又包装了一个类,以后我们再说。底层还是调用了,mapper.connect()去注册路由,调用mapper.routematch去获得路由是否匹配。
下面我们就单纯的时候Mapper去做实验。

入门

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

# 注册一个/hi路由
mapper.connect("/hi")

# 查找/hi
m_result = mapper.routematch("/hi")
print(m_result)

# 查找/hi2
m2_result = mapper.routematch("/hi2")
print(m2_result)

# 查找/hi/boy
m3_result = mapper.routematch("/hi/boy")
print(m3_result)

代码不是很复杂,解释都在上面

下面是我们的运行结果

({}, <routes.route.Route object at 0x000000000380C2E8>)
None
None

routematch返回结果的解释:如果匹配到了,会返回一个二元元组,元组第一个元素为匹配到的字典,第二个元素一个Route对象。我们发现第一个元素是个空字典,这里面会有什么值呢,下面的实验会看到有值。

注册路由时附加key

新写一段代码,如下:

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

# 注册一个/hi路由 并把controller设为say_hi
mapper.connect("/hi", controller="say_hi")

# 查找/hi
m_result = mapper.routematch("/hi")
print(m_result)

这段代码其实,只是在调用connect时多传入了一个key叫做controller,我们看一下运行结果。

({'controller': u'say_hi'}, <routes.route.Route object at 0x00000000034FA2B0>)

我们看到返回的第一个元素已经不是一个空字典了,包含了一个key叫controller正是我们注册路由时,传入的一个键值对。
因为我们光匹配到路由,却不知道交给谁处理,这是没有任何意义的。所以你在nova的注册路由里,发现都传入了controller这个键值对,也出入了action这个键值对。实际传入时controller可能是个对象,可以处理路由。

注册带变量的路由

我们做一个api经常需要一些变量id之类的放到url中,下面这个例子我们就是注册一个带变量的路由

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

# 注册一个/hi路由 需要project_id 并把controller设为say_hi
mapper.connect("/hi/{project_id}", controller="say_hi")

# 查找/hi下project_id=abc
m_result = mapper.routematch("/hi/abc")
print(m_result)

直接看运行结果吧

({'controller': u'say_hi', 'project_id': u'abc'}, <routes.route.Route object at 0x000000000368A2E8>)

我们在match到结果中,可以拿到controller也能拿到url中的变量project_id。
Mapper提供了两种标识变量的写法,除了上面那种还有

mapper.connect("/hi/:project_id", controller="say_hi")

当然两者也可以混着写,但是不建议,因为不美观

mapper.connect("/hi/:project_id/{name}", controller="say_hi")

注册有限制条件的变量的路由

我们有时候,需要限制一些变量的格式,例如project_no只能是数字,project_id只能是数字字母下划线之类。Mapper也支持,这种路由的注册

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

# 注册一个/hi路由 需要纯小写字母组成的project_id 并把controller设为say_hi
mapper.connect("/hi/{project_id:[a-z]+}", controller="say_hi")

# 查找/hi下 project_id=abc
m_result = mapper.routematch("/hi/abc")
print(m_result)

# 查找/hi下 project_id=abc2
m2_result = mapper.routematch("/hi/abc2")
print(m2_result)

从上面的代码也能看出来,很简单{project_id:[a-z]+},就是变量名+冒号+正则表达式。不支持:project_id:[a-z]+的注意是区分大小写的。我们预测结果第一个匹配到,第二个匹配不到。
查看结果

({'controller': u'say_hi', 'project_id': u'abc'}, <routes.route.Route object at 0x0000000003D0A320>)
None

结果符合预期!
还有下面两种写法,例如

mapper.connect("/hi/{project_id}", controller="say_hi", requirements=dict(project_id=r"[a-z]+"))
mapper.connect("/hi/:project_id", controller="say_hi", requirements=dict(project_id=r"[a-z]+"))

就是前面只有变量。后面加个requirements,字典里限定变量的格式。

注册带请求方式的路由

一般情况下,我们需要不同的请求方式,交给不同的方法去处理,routes可不可以处理,答案是可以的,请看下面

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

mapper.connect("/hi", controller="say_hi", conditions=dict(method=["GET"]), action="list")
mapper.connect("/hi", controller="say_hi", conditions=dict(method=["POST"]), action="new")

m_result = mapper.routematch("/hi")
print(m_result)

m2_result = mapper.routematch(environ={"REQUEST_METHOD": "GET", "PATH_INFO": "/hi"})
print(m2_result)

m3_result = mapper.routematch(environ={"REQUEST_METHOD": "POST", "PATH_INFO": "/hi"})
print(m3_result)

注册路由的时候,加入一个conditions,值为一个字典,自带中包含一个method的key,对应的值为一个列表,就是允许的请求方式。这些都是固定的不能更改的。
我们第一次匹配是mapper.routematch("/hi"),是我们上面熟悉的方式,其实routematch可以接收两个参数第一个url,是一个字符串,第二个参数叫做environ,熟悉WSGI的一定知道这是啥,不熟悉的话,它其实就是个字典,包含一些请求信息。可以看出无法从url中传递请求方式,所以只传入url的情况下,默认请求方式是GET。
所以后面两个测试是通过environ传递的值。不了解WSGI的话,只需要知道REQUEST_METHOD这个对应的值就是请求方式,PATH_INFO对应的就是请求URL就可以了。想用这个测试,该对应的值就好了。不过要说明environ要包含很多键值对,此处为了测试方便,只写了两个用到的key。所以不要认为就这两个就是个标准的environ了。
下面看结果

({'action': u'list', 'controller': u'say_hi'}, <routes.route.Route object at 0x00000000041AB2E8>)
({'action': u'list', 'controller': u'say_hi'}, <routes.route.Route object at 0x00000000041AB2E8>)
({'action': u'new', 'controller': u'say_hi'}, <routes.route.Route object at 0x00000000041AB278>)

我们从action区分,前两个匹配到GET,第三个匹配到POST,很符合语气。

进阶用法submapper

submapper用法一

我们有一堆的路由需要同一个controller处理,但是是不同的action我们可以怎么做呢?

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

sub_mapper = mapper.submapper(controller="handle_hobby")
sub_mapper.connect(None, "/hi/boy/{name}", action="game")
sub_mapper.connect(None, "/hi/girl/{name}", action="bag")

m_result = mapper.routematch("/hi/boy/jack")
print(m_result)

m2_result = mapper.routematch("/hi/girl/alina")
print(m2_result)

先用mapper生成一个submapper,传入一个公共属性,例如controller。
然后再用sub_mapper去注册子路由。
上面我们发现,在调用connect时第一个参数传入了None,第二个参数传入的才是路由。这里第一个参数,代表的是这条路由规则的名称,name。我们直接用mapper的时候并没有传name,是因为它在处理的时候,如果只传入了一个无名参数,会默认加上一个,None作为路由的名称。如果传入的参数超过一个,第一个作为路由名称,第二个作为路由规则。
我们看一下结果

({'action': u'game', 'controller': u'handle_hobby', 'name': u'jack'}, <routes.route.Route object at 0x00000000034CA278>)
({'action': u'bag', 'controller': u'handle_hobby', 'name': u'alina'}, <routes.route.Route object at 0x00000000034CA320>)

我们看到了,两个不同的action,相同的controller。

submapper用法二

我们一般应用中,如果需要同样的controller,也一般会有同样的url前缀,submapper也可以设置url前缀。就是设置一下path_prefix。下面是个示例代码

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

sub_mapper = mapper.submapper(path_prefix="/hi", controller="handle_hobby")
sub_mapper.connect("/boy/{name}", action="game")
sub_mapper.connect("/girl/{name}", action="bag")

m_result = mapper.routematch("/hi/boy/jack")
print(m_result)

m2_result = mapper.routematch("/hi/girl/alina")
print(m2_result)

可能有些人有疑问,上面刚说过,submapper在调用connect时必须传入一个name,为啥我上面看着像只传了路由规则,不想传了name。实际是什么样子呢。实际是,我传入的是name,路由规则是自动用的name的值。可能又有人疑问,为啥我在上面的用法一没有这么用。这么用有一个限制就是,必须设置path_prefix。如果没设置path_prefix。路由规则不会用name的值,会是None,运行的时候就会报错了。好奇小朋友可以试一下或者看一下源码就知道了。
看一下运行结果:

({'action': u'game', 'controller': u'handle_hobby', 'name': u'jack'}, <routes.route.Route object at 0x000000000422A278>)
({'action': u'bag', 'controller': u'handle_hobby', 'name': u'alina'}, <routes.route.Route object at 0x000000000422A320>)

这里需要注意的是:path_prefix和注册子路由的时候连接使用空字符连接的。也就是我path_prefix是/hi的情况,如果想拼接出/hi/boy/{name},子路由规则必须是/boy/{name}。如果写成boy/{name},就只能匹配到/hiboy/{name}。

submapper用法三

我们在上面提到了,我们可以调用connect时通过设置conditions来限制请求方式。一般情况下,我们一个路由会有多种请求方式,但一般我们交给同一个controller的不同action去处理。这里我们就可以通过submapper来更简洁的实现

# !/usr/bin/env python
# coding: utf-8

from routes.mapper import Mapper

# 实例化一个Mapper对象
mapper = Mapper()

sub_mapper = mapper.submapper(path_prefix="/hi", controller="handle_hi")

sub_mapper.action(action='show', method='GET')
sub_mapper.action(action='create', method='POST')


m_result = mapper.routematch(environ={"REQUEST_METHOD": "GET", "PATH_INFO": "/hi"})
print(m_result)

m2_result = mapper.routematch(environ={"REQUEST_METHOD": "POST", "PATH_INFO": "/hi"})
print(m2_result)

调用submapper的action方法,传入action和method即可。
看一下运行结果

({'action': u'show', 'controller': u'handle_hi', 'format': None}, <routes.route.Route object at 0x00000000043BA278>)
({'action': u'create', 'controller': u'handle_hi', 'format': None}, <routes.route.Route object at 0x00000000043BAC18>)

他还有更简洁的方法

sub_mapper.index()  # = sub_mapper.action(action='index', method='GET')
sub_mapper.create()  # = sub_mapper.action(action='create', method='POST')
sub_mapper.show()  # = sub_mapper.action(action='show', method='GET')
sub_mapper.update()  # = sub_mapper.action(action='update', method='PUT')
sub_mapper.delete()  # = sub_mapper.action(action='delete', method='DELETE')

他等于内置了action和method。

测试时match不到路由

有时候你会发现明明应该match到的,就是match不到。例如

mapper = Mapper()

mapper.connect("/a", controller="handle_a")
m_result = mapper.routematch("/a")
print(m_result)
mapper.connect("/b", controller="handle_b")
m2_result = mapper.routematch("/b")
print(m2_result)

运行结果是:

({'controller': u'handle_a'}, <routes.route.Route object at 0x0000000003F56588>)
None

明明一样为啥/a能match到,/b不能match到。
Mapper在匹配路由前,会生成一个正则表达式,用于判断是否匹配到路由。
其实是你提交的路由(connect),只会保存起来,Mapper不会立刻生成或重新生成正则表达式,而是当你第一次调用routematch的时候再扫描所有保存的路由,进行正则表达式的生成,以备匹配路由。使用_created_regs这个标识为标识。一旦调用了route这个match_created_regs变成True。在这之后再调用connect其实只是保存了,但是不会再重新生成正则表达式,因此就不会匹配不上新添的路由。
两种解决方案:
1、测试的时候,写完所有connect再去用routematch,这样测试是没问题的
2、或者mapper = Mapper(always_scan=True)这样没次调用routematch的时候都会去重新生成正则表达式,但是只推荐自己学习的时候使用,生产环境可别用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值