From 17cc2928d0affef009e0bcad332d1c7059dc363b Mon Sep 17 00:00:00 2001 From: narzeja Date: Sat, 15 Oct 2016 21:38:12 +0200 Subject: [PATCH 1/2] 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' + From fb79f747d4353893c65df0f08a8338bf7e5c239a Mon Sep 17 00:00:00 2001 From: narzeja Date: Sat, 15 Oct 2016 21:53:51 +0200 Subject: [PATCH 2/2] basic blueprint functionality --- sanic/blueprints.py | 74 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index dd009ce6..585863bb 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -1,24 +1,68 @@ -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') +class BlueprintSetup(): + """ + """ + + def __init__(self, blueprint, app, options): + self.app = app + self.blueprint = blueprint + self.options = options + + url_prefix = self.options.get('url_prefix') + if url_prefix is None: + url_prefix = self.blueprint.url_prefix + + #: The prefix that should be used for all URLs defined on the + #: blueprint. + self.url_prefix = url_prefix + + def add_url_rule(self, uri, methods=None, handler=None, **options): + """A helper method to register a handler to the application url routes. + + """ + if self.url_prefix: + uri = self.url_prefix + uri + + self.app.router.add(uri, methods, handler) -@blueprint.route('/foo') -async def foo(request): - return json({'msg': 'hi from blueprint'}) +class Blueprint(): + def __init__(self, name, url_prefix=None): + self.name = name + self.url_prefix = url_prefix + self.deferred_functions = [] + + def record(self, func): + """Registers a callback function that is invoked when the blueprint is + registered on the application. + + """ + self.deferred_functions.append(func) -@blueprint2.route('/foo') -async def foo2(request): - return json({'msg': 'hi from blueprint2'}) + def make_setup_state(self, app, options): + """ + """ + return BlueprintSetup(self, app, options) + def register(self, app, options): + """ + """ + state = self.make_setup_state(app, options) + for deferred in self.deferred_functions: + deferred(state) -app.register_blueprint(blueprint) -app.register_blueprint(blueprint2) + def route(self, uri, methods=None): + """ + """ + def decorator(handler): + self.add_url_rule(uri=uri, methods=methods, handler=handler) + return handler + return decorator -app.run(host="0.0.0.0", port=8000, debug=True) + def add_url_rule(self, uri, methods=None, handler=None): + """ + """ + self.record(lambda s: + s.add_url_rule(uri, methods, handler))