Sanic 蓝图#

蓝图是可以在应用程序内用于子路由的对象。与其将路由添加到应用程序实例,不如蓝图定义了类似的方法来添加路由,然后以灵活和可插拔的方式与应用程序注册。

蓝图在较大的应用程序中特别有用,因为你的应用程序逻辑可以被分解成几个组或责任区域。

创建和注册蓝图#

首先,你必须创建蓝图。它有一与 Sanic() 应用程序实例非常相似的 API,并且有许多相同的装饰器。

# ./my_blueprint.py
from sanic.response import json
from sanic import Blueprint

bp = Blueprint("my_blueprint")

@bp.route("/")
async def bp_root(request):
    return json({"my": "blueprint"})

将此蓝图注册到 app:

from sanic import Sanic
from my_blueprint import bp

app = Sanic(__name__)
app.blueprint(bp)

蓝图还有相同的 websocket() 装饰器和 add_websocket_route 方法来实现 websockets

从 v21.12 开始,蓝图可以在向其添加对象之前或之后注册。以前,只有在注册时附加到蓝图的对象才会加载到应用程序实例中。

app.blueprint(bp)

@bp.route("/")
async def bp_root(request):
    ...

蓝图副本#

蓝图及其附加的所有内容可以使用 copy() 方法复制到新实例。唯一的必需参数是给它传递一个新名称。然而,你也可以使用这个来覆盖旧蓝图中的任何值。

v1 = Blueprint("Version1", version=1)

@v1.route("/something")
def something(request):
    pass

v2 = v1.copy("Version2", version=2)

app.blueprint(v1)
app.blueprint(v2)

可得到的路由有:

/v1/something
/v2/something

蓝图组#

蓝图也可以作为列表或元组的一部分进行注册,其中注册者将递归地循环遍历蓝图的任何子序列并相应地进行注册。Blueprint.group() 方法提供了简化此过程的功能,允许“模拟”后端目录结构,模仿前端所看到的内容。考虑这个(相当牵强的)例子:

api/
├──content/
 ├──authors.py
 ├──static.py
 └──__init__.py
├──info.py
└──__init__.py
app.py
第一个蓝图
# api/content/authors.py
from sanic import Blueprint

authors = Blueprint("content_authors", url_prefix="/authors")
第二个蓝图
# api/content/static.py
from sanic import Blueprint

static = Blueprint("content_static", url_prefix="/static")
蓝图组
# api/content/__init__.py
from sanic import Blueprint
from .static import static
from .authors import authors

content = Blueprint.group(static, authors, url_prefix="/content")
第三个蓝图
# api/info.py
from sanic import Blueprint

info = Blueprint("info", url_prefix="/info")
第二个蓝图组
# api/__init__.py
from sanic import Blueprint
from .content import content
from .info import info

api = Blueprint.group(content, info, url_prefix="/api")
应用主接口
# app.py
from sanic import Sanic
from .api import api

app = Sanic(__name__)
app.blueprint(api)

蓝图组前缀和可组合性#

如上面的代码所示,当你创建一组蓝图时,你可以通过将 url_prefix 参数传递给 Blueprint.group 方法来扩展组中所有蓝图的 URL 前缀。这对于为你的 API 创建模拟目录结构很有用。

此外,还有 name_prefix 参数可以用来使蓝图可重用和可组合。当将单个蓝图应用于多个组时,这是特别必要的。通过这样做,蓝图将以每个组的唯一名称进行注册,这允许蓝图多次注册,并且其路由都能正确地以唯一标识符命名。

比如,如下代码创建路由:

  • TestApp.group-a_bp1.route1

  • TestApp.group-a_bp2.route2

  • TestApp.group-b_bp1.route1

  • TestApp.group-b_bp2.route2

bp1 = Blueprint("bp1", url_prefix="/bp1")
bp2 = Blueprint("bp2", url_prefix="/bp2")

bp1.add_route(lambda _: ..., "/", name="route1")
bp2.add_route(lambda _: ..., "/", name="route2")

group_a = Blueprint.group(
    bp1, bp2, url_prefix="/group-a", name_prefix="group-a"
)
group_b = Blueprint.group(
    bp1, bp2, url_prefix="/group-b", name_prefix="group-b"
)

app = Sanic("TestApp")
app.blueprint(group_a)
app.blueprint(group_b)

蓝图中间件#

蓝图还可以有专门注册给其端点的中间件。、

@bp.middleware
async def print_on_request(request):
    print("I am a spy")

@bp.middleware("request")
async def halt_request(request):
    return text("I halted the request")

@bp.middleware("response")
async def halt_response(request, response):
    return text("I halted the response")

同样地,使用蓝图组,可以将中间件应用于一组嵌套蓝图的整个组。

bp1 = Blueprint("bp1", url_prefix="/bp1")
bp2 = Blueprint("bp2", url_prefix="/bp2")

@bp1.middleware("request")
async def bp1_only_middleware(request):
    print("applied on Blueprint : bp1 Only")

@bp1.route("/")
async def bp1_route(request):
    return text("bp1")

@bp2.route("/<param>")
async def bp2_route(request, param):
    return text(param)

group = Blueprint.group(bp1, bp2)

@group.middleware("request")
async def group_middleware(request):
    print("common middleware applied for both bp1 and bp2")

# Register Blueprint group under the app
app.blueprint(group)

蓝图异常处理#

@bp.exception(NotFound)
def ignore_404s(request, exception):
    return text("Yep, I totally found the page: {}".format(request.url))

蓝图可组合#

一个蓝图可以注册到多个组中,每个 BlueprintGroup 本身也可以被注册并进一步嵌套。这创造了无限可能的蓝图组合。

看看这个例子,看看两个处理程序实际上是如何作为五个(5)不同的路由挂载的。

app = Sanic(__name__)
blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1")
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
group = Blueprint.group(
    blueprint_1,
    blueprint_2,
    version=1,
    version_prefix="/api/v",
    url_prefix="/grouped",
    strict_slashes=True,
)
primary = Blueprint.group(group, url_prefix="/primary")

@blueprint_1.route("/")
def blueprint_1_default_route(request):
    return text("BP1_OK")

@blueprint_2.route("/")
def blueprint_2_default_route(request):
    return text("BP2_OK")

app.blueprint(group)
app.blueprint(primary)
app.blueprint(blueprint_1)

# The mounted paths:
# /api/v1/grouped/bp1/
# /api/v1/grouped/bp2/
# /api/v1/primary/grouped/bp1
# /api/v1/primary/grouped/bp2
# /bp1