Merge branch 'feature_blueprints' of https://github.com/narzeja/sanic into narzeja-feature_blueprints

This commit is contained in:
Channel Cat 2016-10-16 08:21:35 +00:00
commit 9b716e97ee
7 changed files with 229 additions and 1 deletions

View File

@ -42,6 +42,7 @@ app.run(host="0.0.0.0", port=8000)
* [Routing](docs/routing.md)
* [Middleware](docs/middleware.md)
* [Exceptions](docs/exceptions.md)
* [Blueprints](docs/blueprints.md)
* [Contributing](docs/contributing.md)
* [License](LICENSE)
@ -50,7 +51,6 @@ app.run(host="0.0.0.0", port=8000)
* File output
* Examples of integrations with 3rd-party modules
* RESTful router
* Blueprints?
## Limitations:
* No wheels for uvloop and httptools on Windows :(

56
docs/blueprints.md Normal file
View File

@ -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/<routes>`, and another pointing at `/v2/<routes>`.
## 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=<function bp_root at 0x7f908382f9d8>, methods=None, pattern=re.compile('^/$'), parameters=[])]
```

24
examples/blueprints.py Normal file
View File

@ -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('name', 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)

View File

@ -1 +1,2 @@
from .sanic import Sanic
from .blueprints import Blueprint

68
sanic/blueprints.py Normal file
View File

@ -0,0 +1,68 @@
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)
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)
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)
def route(self, uri, methods=None):
"""
"""
def decorator(handler):
self.add_url_rule(uri=uri, methods=methods, handler=handler)
return handler
return decorator
def add_url_rule(self, uri, methods=None, handler=None):
"""
"""
self.record(lambda s:
s.add_url_rule(uri, methods, handler))

View File

@ -21,6 +21,8 @@ class Sanic:
self.config = Config()
self.request_middleware = []
self.response_middleware = []
self.blueprints = {}
self._blueprint_order = []
# -------------------------------------------------------------------- #
# Registration
@ -85,6 +87,23 @@ 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 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)
blueprint.register(self, options)
# -------------------------------------------------------------------- #
# Request Handling
# -------------------------------------------------------------------- #

60
tests/test_blueprints.py Normal file
View File

@ -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'