Merge branch 'narzeja-feature_blueprints'
This commit is contained in:
commit
2fde4d96f6
|
@ -42,6 +42,7 @@ app.run(host="0.0.0.0", port=8000)
|
||||||
* [Routing](docs/routing.md)
|
* [Routing](docs/routing.md)
|
||||||
* [Middleware](docs/middleware.md)
|
* [Middleware](docs/middleware.md)
|
||||||
* [Exceptions](docs/exceptions.md)
|
* [Exceptions](docs/exceptions.md)
|
||||||
|
* [Blueprints](docs/blueprints.md)
|
||||||
* [Contributing](docs/contributing.md)
|
* [Contributing](docs/contributing.md)
|
||||||
* [License](LICENSE)
|
* [License](LICENSE)
|
||||||
|
|
||||||
|
@ -50,7 +51,6 @@ app.run(host="0.0.0.0", port=8000)
|
||||||
* File output
|
* File output
|
||||||
* Examples of integrations with 3rd-party modules
|
* Examples of integrations with 3rd-party modules
|
||||||
* RESTful router
|
* RESTful router
|
||||||
* Blueprints?
|
|
||||||
|
|
||||||
## Limitations:
|
## Limitations:
|
||||||
* No wheels for uvloop and httptools on Windows :(
|
* No wheels for uvloop and httptools on Windows :(
|
||||||
|
|
82
docs/blueprints.md
Normal file
82
docs/blueprints.md
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# 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=[])]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Middleware
|
||||||
|
Blueprints must be registered with the application.
|
||||||
|
|
||||||
|
```python
|
||||||
|
@bp.middleware
|
||||||
|
async def halt_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')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
Blueprints must be registered with the application.
|
||||||
|
|
||||||
|
```python
|
||||||
|
@bp.exception(NotFound)
|
||||||
|
def ignore_404s(request, exception):
|
||||||
|
return text("Yep, I totally found the page: {}".format(request.url))
|
||||||
|
```
|
24
examples/blueprints.py
Normal file
24
examples/blueprints.py
Normal 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)
|
|
@ -1 +1,2 @@
|
||||||
from .sanic import Sanic
|
from .sanic import Sanic
|
||||||
|
from .blueprints import Blueprint
|
||||||
|
|
99
sanic/blueprints.py
Normal file
99
sanic/blueprints.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
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_route(self, handler, uri, methods):
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
def add_exception(self, handler, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Registers exceptions to sanic
|
||||||
|
"""
|
||||||
|
self.app.exception(*args, **kwargs)(handler)
|
||||||
|
|
||||||
|
def add_middleware(self, middleware, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Registers middleware to sanic
|
||||||
|
"""
|
||||||
|
if args or kwargs:
|
||||||
|
self.app.middleware(*args, **kwargs)(middleware)
|
||||||
|
else:
|
||||||
|
self.app.middleware(middleware)
|
||||||
|
|
||||||
|
|
||||||
|
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.record(lambda s: s.add_route(handler, uri, methods))
|
||||||
|
return handler
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def middleware(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_middleware(middleware):
|
||||||
|
self.record(lambda s: s.add_middleware(middleware, *args, **kwargs))
|
||||||
|
return middleware
|
||||||
|
|
||||||
|
# Detect which way this was called, @middleware or @middleware('AT')
|
||||||
|
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
|
||||||
|
args = []
|
||||||
|
return register_middleware(args[0])
|
||||||
|
else:
|
||||||
|
return register_middleware
|
||||||
|
|
||||||
|
def exception(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
def decorator(handler):
|
||||||
|
self.record(lambda s: s.add_exception(handler, *args, **kwargs))
|
||||||
|
return handler
|
||||||
|
return decorator
|
||||||
|
|
|
@ -21,6 +21,8 @@ class Sanic:
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
self.request_middleware = []
|
self.request_middleware = []
|
||||||
self.response_middleware = []
|
self.response_middleware = []
|
||||||
|
self.blueprints = {}
|
||||||
|
self._blueprint_order = []
|
||||||
|
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
# Registration
|
# Registration
|
||||||
|
@ -85,6 +87,23 @@ class Sanic:
|
||||||
|
|
||||||
return middleware
|
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
|
# Request Handling
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
111
tests/test_blueprints.py
Normal file
111
tests/test_blueprints.py
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.blueprints import Blueprint
|
||||||
|
from sanic.response import json, text
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
from sanic.exceptions import NotFound, ServerError, InvalidUsage
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ #
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_middleware():
|
||||||
|
app = Sanic('test_middleware')
|
||||||
|
blueprint = Blueprint('test_middleware')
|
||||||
|
|
||||||
|
@blueprint.middleware('response')
|
||||||
|
async def process_response(request, response):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def handler(request):
|
||||||
|
return text('FAIL')
|
||||||
|
|
||||||
|
app.register_blueprint(blueprint)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
def test_bp_exception_handler():
|
||||||
|
app = Sanic('test_middleware')
|
||||||
|
blueprint = Blueprint('test_middleware')
|
||||||
|
|
||||||
|
@blueprint.route('/1')
|
||||||
|
def handler_1(request):
|
||||||
|
raise InvalidUsage("OK")
|
||||||
|
|
||||||
|
@blueprint.route('/2')
|
||||||
|
def handler_2(request):
|
||||||
|
raise ServerError("OK")
|
||||||
|
|
||||||
|
@blueprint.route('/3')
|
||||||
|
def handler_3(request):
|
||||||
|
raise NotFound("OK")
|
||||||
|
|
||||||
|
@blueprint.exception(NotFound, ServerError)
|
||||||
|
def handler_exception(request, exception):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
app.register_blueprint(blueprint)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/1')
|
||||||
|
assert response.status == 400
|
||||||
|
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/2')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/3')
|
||||||
|
assert response.status == 200
|
Loading…
Reference in New Issue
Block a user