跳转到主要内容

Sanic的核心路由组件

项目描述

Sanic路由

背景

从v21.3版本开始,Sanic利用这种新的AST-style路由器在两个用例中进行了使用

  1. 路由路径;
  2. 路由信号。

因此,此软件包包含一个BaseRouter,需要对其进行子类化才能用于其特定需求。

大多数Sanic用户不需要关心这里的细节。

基本示例

简单实现

import logging

from sanic_routing import BaseRouter

logging.basicConfig(level=logging.DEBUG)


class Router(BaseRouter):
    def get(self, path, *args, **kwargs):
        return self.resolve(path, *args, **kwargs)


router = Router()

router.add("/<foo>", lambda: ...)
router.finalize()
router.tree.display()
logging.info(router.find_route_src)

route, handler, params = router.get("/matchme", method="BASE", extra=None)

上面的代码段使用router.tree.display()显示路由器如何决定将路由安排成树形结构。在这个简单示例中

<Node: level=0>
    <Node: part=__dynamic__:str, level=1, groups=[<RouteGroup: path=<foo:str> len=1>], dynamic=True>

我们可以看到路由器为我们生成的代码。它作为字符串在router.find_route_src中可用。

def find_route(path, method, router, basket, extra):
    parts = tuple(path[1:].split(router.delimiter))
    num = len(parts)
    
    # node=1 // part=__dynamic__:str
    if num == 1:  # CHECK 1
        try:
            basket['__matches__'][0] = str(parts[0])
        except ValueError:
            pass
        else:
            # Return 1
            return router.dynamic_routes[('<__dynamic__:str>',)][0], basket
    raise NotFound

FYI: 如果你使用的是Python 3.9,你可以在router.find_route_src_compiled中看到编译后的源代码表示。

它在做什么?

因此,在一般实现中,您需要

  1. 定义一个带有get方法的路由器;
  2. 添加一个或多个路由;
  3. 最终化路由器(router.finalize());
  4. 调用路由器的get方法。

注意:如果您不希望将源代码编译成可执行形式,则可以调用router.finalize(False)。如果您只想查看生成的输出,这将很有用。

每次您调用router.add时,您都会创建一个(1)新的Route实例。即使该路由使用了多个方法,它也只生成一个实例。如果您添加了一个具有类似路径结构(但可能有不同方法)的另一个Route,它们将组合成一个RouteGroup。还值得注意,RouteGroup是在第一次调用add()时创建的,但后续类似的路由将重用现有的分组实例。

当调用 finalize() 方法时,它会将定义的路由组按照层次结构排列成“节点”。单个节点是一个路径段。一个 Node 实例可以有一个或多个 RouteGroup,其中 Node 是该路径的终止点。

也许一个例子更容易理解

router.add("/path/to/<foo>", lambda: ...)
router.add("/path/to/<foo:int>", lambda: ...)
router.add("/path/to/different/<foo>", lambda: ...)
router.add("/path/to/different/<foo>", lambda: ..., methods=["one", "two"])

生成的 RouteGroup 实例(3个)

<RouteGroup: path=path/to/<foo:str> len=1>
<RouteGroup: path=path/to/<foo:int> len=1>
<RouteGroup: path=path/to/different/<foo:str> len=2>

生成的 Route 实例(4个)

<Route: path=path/to/<foo:str>>
<Route: path=path/to/<foo:int>>
<Route: path=path/to/different/<foo:str>>
<Route: path=path/to/different/<foo:str>>

节点树

<Node: level=0>
    <Node: part=path, level=1>
        <Node: part=to, level=2>
            <Node: part=different, level=3>
                <Node: part=__dynamic__:str, level=4, groups=[<RouteGroup: path=path/to/different/<foo:str> len=2>], dynamic=True>
            <Node: part=__dynamic__:int, level=3, groups=[<RouteGroup: path=path/to/<foo:int> len=1>], dynamic=True>
            <Node: part=__dynamic__:str, level=3, groups=[<RouteGroup: path=path/to/<foo:str> len=1>], dynamic=True>

以及生成的源代码

def find_route(path, method, router, basket, extra):
    parts = tuple(path[1:].split(router.delimiter))
    num = len(parts)
    
    # node=1 // part=path
    if num > 1:  # CHECK 1
        if parts[0] == "path":  # CHECK 4
            
            # node=1.1 // part=to
            if num > 2:  # CHECK 1
                if parts[1] == "to":  # CHECK 4
                    
                    # node=1.1.1 // part=different
                    if num > 3:  # CHECK 1
                        if parts[2] == "different":  # CHECK 4
                            
                            # node=1.1.1.1 // part=__dynamic__:str
                            if num == 4:  # CHECK 1
                                try:
                                    basket['__matches__'][3] = str(parts[3])
                                except ValueError:
                                    pass
                                else:
                                    if method in frozenset({'one', 'two'}):
                                        route_idx = 0
                                    elif method in frozenset({'BASE'}):
                                        route_idx = 1
                                    else:
                                        raise NoMethod
                                    # Return 1.1.1.1
                                    return router.dynamic_routes[('path', 'to', 'different', '<__dynamic__:str>')][route_idx], basket
                    
                    # node=1.1.2 // part=__dynamic__:int
                    if num >= 3:  # CHECK 1
                        try:
                            basket['__matches__'][2] = int(parts[2])
                        except ValueError:
                            pass
                        else:
                            if num == 3:  # CHECK 5
                                # Return 1.1.2
                                return router.dynamic_routes[('path', 'to', '<__dynamic__:int>')][0], basket
                    
                    # node=1.1.3 // part=__dynamic__:str
                    if num >= 3:  # CHECK 1
                        try:
                            basket['__matches__'][2] = str(parts[2])
                        except ValueError:
                            pass
                        else:
                            if num == 3:  # CHECK 5
                                # Return 1.1.3
                                return router.dynamic_routes[('path', 'to', '<__dynamic__:str>')][0], basket
    raise NotFound

特殊情况

上面的例子只显示了包含动态路径段的路由(例如:<foo>)。但还有一些其他的使用案例,处理方式不同

  1. 完全静态路径 - 这些是没有参数的路径(例如:/user/login)。这些基本上是与键/值存储进行匹配。
  2. 正则表达式路径 - 如果一个路由有单个正则表达式匹配,则整个路由将通过正则表达式进行匹配。通常,这发生在行内,与上面例子中的情况不太一样。
  3. 特殊正则表达式路径 - 路由器提供了一个特殊的 path 类型(例如:<foo:path>),它可以匹配扩展的分隔符。这也适用于使用路径分隔符的任何正则表达式。由于它们长度未知,因此不能在常规情况下进行匹配。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源代码发行版

sanic-routing-23.12.0.tar.gz (29.5 kB 查看哈希值)

上传时间 源代码

构建发行版

sanic_routing-23.12.0-py3-none-any.whl (25.5 kB 查看哈希值)

上传时间 Python 3

支持者

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面