diff --git a/docs/sanic/blueprints.md b/docs/sanic/blueprints.md deleted file mode 100644 index 6edbf1a8..00000000 --- a/docs/sanic/blueprints.md +++ /dev/null @@ -1,286 +0,0 @@ -# Blueprints - -Blueprints are objects that can be used for sub-routing within an application. -Instead of adding routes to the application instance, blueprints define similar -methods for adding routes, which are then registered with the application in a -flexible and pluggable manner. - -Blueprints are especially useful for larger applications, where your -application logic can be broken down into several groups or areas of -responsibility. - -## My First Blueprint - -The following shows a very simple blueprint that registers a handler-function at -the root `/` of your application. - -Suppose you save this file as `my_blueprint.py`, which can be imported into your -main application later. - -```python -from sanic.response import json -from sanic import Blueprint - -bp = Blueprint('my_blueprint') - -@bp.route('/') -async def bp_root(request): - return json({'my': 'blueprint'}) - -``` - -## Registering blueprints - -Blueprints must be registered with the application. - -```python -from sanic import Sanic -from my_blueprint import bp - -app = Sanic(__name__) -app.blueprint(bp) - -app.run(host='0.0.0.0', port=8000, debug=True) -``` - -This will add the blueprint to the application and register any routes defined -by that blueprint. In this example, the registered routes in the `app.router` -will look like: - -```python -[Route(handler=, methods=frozenset({'GET'}), pattern=re.compile('^/$'), parameters=[], name='my_blueprint.bp_root', uri='/')] -``` - -## Blueprint groups and nesting - -Blueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The `Blueprint.group` method is provided to simplify this process, allowing a 'mock' backend directory structure mimicking what's seen from the front end. Consider this (quite contrived) example: - -``` -api/ -├──content/ -│ ├──authors.py -│ ├──static.py -│ └──__init__.py -├──info.py -└──__init__.py -app.py -``` - -Initialization of this app's blueprint hierarchy could go as follows: - -```python -# api/content/authors.py -from sanic import Blueprint - -authors = Blueprint('content_authors', url_prefix='/authors') -``` -```python -# api/content/static.py -from sanic import Blueprint - -static = Blueprint('content_static', url_prefix='/static') -``` -```python -# api/content/__init__.py -from sanic import Blueprint - -from .static import static -from .authors import authors - -content = Blueprint.group(static, authors, url_prefix='/content') -``` -```python -# api/info.py -from sanic import Blueprint - -info = Blueprint('info', url_prefix='/info') -``` -```python -# api/__init__.py -from sanic import Blueprint - -from .content import content -from .info import info - -api = Blueprint.group(content, info, url_prefix='/api') -``` - -And registering these blueprints in `app.py` can now be done like so: - -```python -# app.py -from sanic import Sanic - -from .api import api - -app = Sanic(__name__) - -app.blueprint(api) -``` - -## Using Blueprints - -Blueprints have almost the same functionality as an application instance. - -### WebSocket routes - -WebSocket handlers can be registered on a blueprint using the `@bp.websocket` -decorator or `bp.add_websocket_route` method. - -### Blueprint Middleware - -Using blueprints allows you to also register middleware globally. - -```python -@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') -``` - -### Blueprint Group Middleware -Using this middleware will ensure that you can apply a common middleware to all the blueprints that form the -current blueprint group under consideration. - -```python -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('/') -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) -``` - -### Exceptions - -Exceptions can be applied exclusively to blueprints globally. - -```python -@bp.exception(NotFound) -def ignore_404s(request, exception): - return text("Yep, I totally found the page: {}".format(request.url)) -``` - -### Static files - -Static files can be served globally, under the blueprint prefix. - -```python - -# suppose bp.name == 'bp' - -bp.static('/web/path', '/folder/to/serve') -# also you can pass name parameter to it for url_for -bp.static('/web/path', '/folder/to/server', name='uploads') -app.url_for('static', name='bp.uploads', filename='file.txt') == '/bp/web/path/file.txt' - -``` - -## Start and stop - -Blueprints can run functions during the start and stop process of the server. -If running in multiprocessor mode (more than 1 worker), these are triggered -after the workers fork. - -Available events are: - -- `before_server_start`: Executed before the server begins to accept connections -- `after_server_start`: Executed after the server begins to accept connections -- `before_server_stop`: Executed before the server stops accepting connections -- `after_server_stop`: Executed after the server is stopped and all requests are complete - -```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() -``` - -## Use-case: API versioning - -Blueprints can be very useful for API versioning, where one blueprint may point -at `/v1/`, and another pointing at `/v2/`. - -When a blueprint is initialised, it can take an optional `version` argument, -which will be prepended to all routes defined on the blueprint. This feature -can be used to implement our API versioning scheme. - -```python -# blueprints.py -from sanic.response import text -from sanic import Blueprint - -blueprint_v1 = Blueprint('v1', url_prefix='/api', version="v1") -blueprint_v2 = Blueprint('v2', url_prefix='/api', version="v2") - -@blueprint_v1.route('/') -async def api_v1_root(request): - return text('Welcome to version 1 of our documentation') - -@blueprint_v2.route('/') -async def api_v2_root(request): - return text('Welcome to version 2 of our documentation') -``` - -When we register our blueprints on the app, the routes `/v1/api` and `/v2/api` will now -point to the individual blueprints, which allows the creation of *sub-sites* -for each API version. - -```python -# main.py -from sanic import Sanic -from blueprints import blueprint_v1, blueprint_v2 - -app = Sanic(__name__) -app.blueprint(blueprint_v1) -app.blueprint(blueprint_v2) - -app.run(host='0.0.0.0', port=8000, debug=True) -``` - -## URL Building with `url_for` - -If you wish to generate a URL for a route inside of a blueprint, remember that the endpoint name -takes the format `.`. For example: - -```python -@blueprint_v1.route('/') -async def root(request): - url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/api/post/5' - return redirect(url) - - -@blueprint_v1.route('/post/') -async def post_handler(request, post_id): - return text('Post {} in Blueprint V1'.format(post_id)) -``` diff --git a/docs/sanic/blueprints.rst b/docs/sanic/blueprints.rst new file mode 100644 index 00000000..710467f8 --- /dev/null +++ b/docs/sanic/blueprints.rst @@ -0,0 +1,301 @@ +Blueprints +========== + +Blueprints are objects that can be used for sub-routing within an application. +Instead of adding routes to the application instance, blueprints define similar +methods for adding routes, which are then registered with the application in a +flexible and pluggable manner. + +Blueprints are especially useful for larger applications, where your +application logic can be broken down into several groups or areas of +responsibility. + +My First Blueprint +------------------ + +The following shows a very simple blueprint that registers a handler-function at +the root `/` of your application. + +Suppose you save this file as `my_blueprint.py`, which can be imported into your +main application later. + +.. code-block:: python + + from sanic.response import json + from sanic import Blueprint + + bp = Blueprint('my_blueprint') + + @bp.route('/') + async def bp_root(request): + return json({'my': 'blueprint'}) + +Registering blueprints +---------------------- + +Blueprints must be registered with the application. + +.. code-block:: python + + from sanic import Sanic + from my_blueprint import bp + + app = Sanic(__name__) + app.blueprint(bp) + + app.run(host='0.0.0.0', port=8000, debug=True) + +This will add the blueprint to the application and register any routes defined +by that blueprint. In this example, the registered routes in the `app.router` +will look like: + +.. code-block:: python + + [Route(handler=, methods=frozenset({'GET'}), pattern=re.compile('^/$'), parameters=[], name='my_blueprint.bp_root', uri='/')] + +Blueprint groups and nesting +---------------------------- + +Blueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The `Blueprint.group` method is provided to simplify this process, allowing a 'mock' backend directory structure mimicking what's seen from the front end. Consider this (quite contrived) example: + +| api/ +| ├──content/ +| │ ├──authors.py +| │ ├──static.py +| │ └──__init__.py +| ├──info.py +| └──__init__.py +| app.py + +Initialization of this app's blueprint hierarchy could go as follows: + +.. code-block:: python + + # api/content/authors.py + from sanic import Blueprint + + authors = Blueprint('content_authors', url_prefix='/authors') + +.. code-block:: python + + # api/content/static.py + from sanic import Blueprint + + static = Blueprint('content_static', url_prefix='/static') + + +.. code-block:: python + + # api/content/__init__.py + from sanic import Blueprint + + from .static import static + from .authors import authors + + content = Blueprint.group(static, authors, url_prefix='/content') + +.. code-block:: python + + # api/info.py + from sanic import Blueprint + + info = Blueprint('info', url_prefix='/info') + +.. code-block:: python + + # api/__init__.py + from sanic import Blueprint + + from .content import content + from .info import info + + api = Blueprint.group(content, info, url_prefix='/api') + +And registering these blueprints in `app.py` can now be done like so: + +.. code-block:: python + + # app.py + from sanic import Sanic + + from .api import api + + app = Sanic(__name__) + + app.blueprint(api) + +Using Blueprints +---------------- + +Blueprints have almost the same functionality as an application instance. + +WebSocket routes +~~~~~~~~~~~~~~~~ + +WebSocket handlers can be registered on a blueprint using the `@bp.websocket` +decorator or `bp.add_websocket_route` method. + +Blueprint Middleware +~~~~~~~~~~~~~~~~~~~~ + +Using blueprints allows you to also register middleware globally. + +.. code-block:: python + + @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') + + +Blueprint Group Middleware +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using this middleware will ensure that you can apply a common middleware to all the blueprints that form the +current blueprint group under consideration. + +.. code-block:: python + + 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('/') + 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) + +Exceptions +~~~~~~~~~~ + +Exceptions can be applied exclusively to blueprints globally. + +.. code-block:: python + + @bp.exception(NotFound) + def ignore_404s(request, exception): + return text("Yep, I totally found the page: {}".format(request.url)) + +Static files +~~~~~~~~~~~~ + +Static files can be served globally, under the blueprint prefix. + +.. code-block:: python + + # suppose bp.name == 'bp' + + bp.static('/web/path', '/folder/to/serve') + # also you can pass name parameter to it for url_for + bp.static('/web/path', '/folder/to/server', name='uploads') + app.url_for('static', name='bp.uploads', filename='file.txt') == '/bp/web/path/file.txt' + +Start and stop +-------------- + +Blueprints can run functions during the start and stop process of the server. +If running in multiprocessor mode (more than 1 worker), these are triggered +after the workers fork. + +Available events are: + +- `before_server_start`: Executed before the server begins to accept connections +- `after_server_start`: Executed after the server begins to accept connections +- `before_server_stop`: Executed before the server stops accepting connections +- `after_server_stop`: Executed after the server is stopped and all requests are complete + +.. code-block:: 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() + +Use-case: API versioning +------------------------ + +Blueprints can be very useful for API versioning, where one blueprint may point +at `/v1/`, and another pointing at `/v2/`. + +When a blueprint is initialised, it can take an optional `version` argument, +which will be prepended to all routes defined on the blueprint. This feature +can be used to implement our API versioning scheme. + +.. code-block:: python + + # blueprints.py + from sanic.response import text + from sanic import Blueprint + + blueprint_v1 = Blueprint('v1', url_prefix='/api', version="v1") + blueprint_v2 = Blueprint('v2', url_prefix='/api', version="v2") + + @blueprint_v1.route('/') + async def api_v1_root(request): + return text('Welcome to version 1 of our documentation') + + @blueprint_v2.route('/') + async def api_v2_root(request): + return text('Welcome to version 2 of our documentation') + +When we register our blueprints on the app, the routes `/v1/api` and `/v2/api` will now +point to the individual blueprints, which allows the creation of *sub-sites* +for each API version. + +.. code-block:: python + + # main.py + from sanic import Sanic + from blueprints import blueprint_v1, blueprint_v2 + + app = Sanic(__name__) + app.blueprint(blueprint_v1) + app.blueprint(blueprint_v2) + + app.run(host='0.0.0.0', port=8000, debug=True) + +URL Building with `url_for` +--------------------------- + +If you wish to generate a URL for a route inside of a blueprint, remember that the endpoint name +takes the format `.`. For example: + +.. code-block:: python + + @blueprint_v1.route('/') + async def root(request): + url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/api/post/5' + return redirect(url) + + + @blueprint_v1.route('/post/') + async def post_handler(request, post_id): + return text('Post {} in Blueprint V1'.format(post_id))