From 17cc2928d0affef009e0bcad332d1c7059dc363b Mon Sep 17 00:00:00 2001 From: narzeja Date: Sat, 15 Oct 2016 21:38:12 +0200 Subject: [PATCH] Blueprint support, with docs, example, and tests --- docs/blueprints.md | 56 +++++++++++++++++++++++++++++++++++++ examples/blueprints.py | 24 ++++++++++++++++ sanic/__init__.py | 1 + sanic/blueprints.py | 24 ++++++++++++++++ sanic/sanic.py | 20 ++++++++++++++ tests/test_blueprints.py | 60 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 185 insertions(+) create mode 100644 docs/blueprints.md create mode 100644 examples/blueprints.py create mode 100644 sanic/blueprints.py create mode 100644 tests/test_blueprints.py diff --git a/docs/blueprints.md b/docs/blueprints.md new file mode 100644 index 00000000..ba3173e1 --- /dev/null +++ b/docs/blueprints.md @@ -0,0 +1,56 @@ +# Blueprints + +Blueprints are objects that can be used for sub-routing within an application. +Instead of adding routes to the application object, blueprints define similar +methods for adding routes, which are then registered with the application in a +flexible and plugable manner. + +## Why? + +Blueprints are especially useful for larger applications, where your application +logic can be broken down into several groups or areas of responsibility. + +It is also useful for API versioning, where one blueprint may point at +`/v1/`, and another pointing at `/v2/`. + + +## 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`, this can be imported in your +main application later. + +```python +from sanic.response import json +from sanic import Blueprint + +bp = Blueprint('my_blueprint') + +@bp.route('/') +async def bp_root(): + 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.register_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=None, pattern=re.compile('^/$'), parameters=[])] +``` diff --git a/examples/blueprints.py b/examples/blueprints.py new file mode 100644 index 00000000..dd009ce6 --- /dev/null +++ b/examples/blueprints.py @@ -0,0 +1,24 @@ +from sanic import Sanic +from sanic import Blueprint +from sanic.response import json, text + + +app = Sanic(__name__) +blueprint = Blueprint('name', url_prefix='/my_blueprint') +blueprint2 = Blueprint('name2', url_prefix='/my_blueprint2') + + +@blueprint.route('/foo') +async def foo(request): + return json({'msg': 'hi from blueprint'}) + + +@blueprint2.route('/foo') +async def foo2(request): + return json({'msg': 'hi from blueprint2'}) + + +app.register_blueprint(blueprint) +app.register_blueprint(blueprint2) + +app.run(host="0.0.0.0", port=8000, debug=True) diff --git a/sanic/__init__.py b/sanic/__init__.py index 52dc810a..5b893df4 100644 --- a/sanic/__init__.py +++ b/sanic/__init__.py @@ -1 +1,2 @@ from .sanic import Sanic +from .blueprints import Blueprint diff --git a/sanic/blueprints.py b/sanic/blueprints.py new file mode 100644 index 00000000..dd009ce6 --- /dev/null +++ b/sanic/blueprints.py @@ -0,0 +1,24 @@ +from sanic import Sanic +from sanic import Blueprint +from sanic.response import json, text + + +app = Sanic(__name__) +blueprint = Blueprint('name', url_prefix='/my_blueprint') +blueprint2 = Blueprint('name2', url_prefix='/my_blueprint2') + + +@blueprint.route('/foo') +async def foo(request): + return json({'msg': 'hi from blueprint'}) + + +@blueprint2.route('/foo') +async def foo2(request): + return json({'msg': 'hi from blueprint2'}) + + +app.register_blueprint(blueprint) +app.register_blueprint(blueprint2) + +app.run(host="0.0.0.0", port=8000, debug=True) diff --git a/sanic/sanic.py b/sanic/sanic.py index f17cd4af..a9a1ee41 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -22,6 +22,8 @@ class Sanic: self.config = Config() self.request_middleware = [] self.response_middleware = [] + self.blueprints = {} + self._blueprint_order = [] # -------------------------------------------------------------------- # # Registration @@ -86,6 +88,24 @@ class Sanic: return middleware + def register_blueprint(self, blueprint, **options): + """ + Registers a blueprint on the application. + :param blueprint: Blueprint object + :param options: option dictionary with blueprint defaults + :return: Nothing + """ + if blueprint.name in self.blueprints: + assert self.blueprints[blueprint.name] is blueprint, \ + 'A blueprint\'s name collision occurred between %r and ' \ + '%r. Both share the same name "%s". ' % \ + (blueprint, self.blueprints[blueprint.name], blueprint.name) + else: + self.blueprints[blueprint.name] = blueprint + self._blueprint_order.append(blueprint) + blueprint.register(self, options) + + # -------------------------------------------------------------------- # # Request Handling # -------------------------------------------------------------------- # diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py new file mode 100644 index 00000000..a88f2d19 --- /dev/null +++ b/tests/test_blueprints.py @@ -0,0 +1,60 @@ +from json import loads as json_loads, dumps as json_dumps +from sanic import Sanic +from sanic import Blueprint +from sanic.response import json, text +from sanic.utils import sanic_endpoint_test +from sanic.exceptions import SanicException + + +# ------------------------------------------------------------ # +# GET +# ------------------------------------------------------------ # + +def test_bp(): + app = Sanic('test_text') + bp = Blueprint('test_text') + + @bp.route('/') + def handler(request): + return text('Hello') + + app.register_blueprint(bp) + request, response = sanic_endpoint_test(app) + + assert response.text == 'Hello' + +def test_bp_with_url_prefix(): + app = Sanic('test_text') + bp = Blueprint('test_text', url_prefix='/test1') + + @bp.route('/') + def handler(request): + return text('Hello') + + app.register_blueprint(bp) + request, response = sanic_endpoint_test(app, uri='/test1/') + + assert response.text == 'Hello' + + +def test_several_bp_with_url_prefix(): + app = Sanic('test_text') + bp = Blueprint('test_text', url_prefix='/test1') + bp2 = Blueprint('test_text2', url_prefix='/test2') + + @bp.route('/') + def handler(request): + return text('Hello') + + @bp2.route('/') + def handler2(request): + return text('Hello2') + + app.register_blueprint(bp) + app.register_blueprint(bp2) + request, response = sanic_endpoint_test(app, uri='/test1/') + assert response.text == 'Hello' + + request, response = sanic_endpoint_test(app, uri='/test2/') + assert response.text == 'Hello2' +