# 藍圖
藍圖概念來自**Flask**,意思是可在應用中,用來做子路由的對象。相對于前面章節講到的增加路由方式,藍圖也定義了類似的方法,但采用插件方式更易于擴展。在大型應用中,藍圖尤其有用,能方便地把服務解耦成各自獨立的模塊。
## 第一個藍圖
下面示范了一個簡單的藍圖,注冊了訪問`/`路由的handler。假設藍圖文件為`my_blueprint.py`,后面需要`import`到主應用中。
```python
from sanic import response, Blueprint
bp = Blueprint('my_blueprint')
@bp.route('/')
async def bp_root(request):
return response.json({'my': 'blueprint'})
```
## 注冊藍圖
藍圖需要注冊到主程序中。
```python
from sanic import Sanic
import my_blueprint
app = Sanic(__name__)
app.blueprint(my_blueprint.bp)
app.run(host='0.0.0.0', port=8000, debug=True)
```
## 使用藍圖
使用方式如同前面章節描述的主應用實例,一樣的方式添加中間件,添加路由。
### 中間件
同樣可以注冊全局中間件。
```python
@bp.middleware('request')
async def print_bp_request(request):
print('blue request middle')
@bp.middleware('response')
async def print_bp_reponse(request, response):
print('blue response middle')
```
也可以注冊視圖內的中間件(詳見[視圖](./class_based_views.md))
```python
class TestView(views.HTTPMethodView):
@staticmethod
@bp.middleware('request')
async def print_bp_view_request(request):
print('blue view request middle')
@staticmethod
@bp.middleware('response')
async def print_bp_view_response(request, response):
print('blue view response middle')
async def get(self, request):
return response.json({'type': 'view class'})
bp.add_route(TestView.as_view(), '/view')
```
### 異常
藍圖內可以定義自己的異常。
```python
@bp.exception(NotFound)
def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url))
```
### 開始和結束
藍圖也可以像主應用一樣使用監聽器。如果運行在多進程模式下,會在某個進城創建后觸發該事件。可用的事件如下,事件觸發方式與主應用一致:
- before_server_start
- after_server_start
- before_server_stop
- after_server_stop
```python
bp = Blueprint('my_blueprint')
@bp.listener('before_server_start')
async def setup_connection(app, loop):
global database
database = mysql.connect(host='127.0.0.1'...)
@bp.listener('after_server_stop')
async def close_connection(app, loop):
await database.close()
```
### 用例:API變換
藍圖可以方便地實現API變換,比如一個藍圖指向`/v1/<routes>`,另一個指向`/v2/<routes>`。當藍圖創建時,可以使用`url_prefix`參數,用來指名當前藍圖下所有路由的前綴。這個特性可以用來實現API變換。
```python
from sanic import response, Blueprint
blueprint_v1 = Blueprint('v1', url_prefix='/v1')
blueprint_v2 = Blueprint('v2', url_prefix='/v2')
@blueprint_v1.route('/')
async def api_v1_root(request):
return response.text('Welcome to version 1 of our documentation')
@blueprint_v2.route('/')
async def api_v2_root(request):
return response.text('Welcome to version 2 of our documentation')
```
當在應用中注冊藍圖后,`/v1`和`/v2`路由會分別指向特定的藍圖,可以用來給不同的子站點應用對應的API。
```python
# main.py
from sanic import Sanic
from blueprints import blueprint_v1, blueprint_v2
app = Sanic(__name__)
app.blueprint(blueprint_v1, url_prefix='/v1')
app.blueprint(blueprint_v2, url_prefix='/v2')
app.run(host='0.0.0.0', port=8000, debug=True)
````
如果main.py的注冊程序和my_blurprint.py初始化中都有url_prefix參數,那么main的會覆蓋掉Blueprint初始化的設定。app.py源碼中也可以證明
```python
def blueprint(self, blueprint, **options):
if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint with the name "%s" is already registered. ' \
'Blueprint names must be unique.' % \
(blueprint.name,)
else:
self.blueprints[blueprint.name] = blueprint
self._blueprint_order.append(blueprint)
# 覆蓋掉blurprint實例的設定
blueprint.register(self, options)
```
### 用`url_for`創建URL
如果需要在藍圖中創建路由,記得端點格式是`<blueprint_name>.<handler_name>`,例如:
```python
@blueprint_v1.route('/')
async def root(request):
url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
return redirect(url)
@blueprint_v1.route('/post/<post_id>')
async def post_handler(request, post_id):
return text('Post {} in Blueprint V1'.format(post_id))
```