class based views implementation for sanic
This commit is contained in:
parent
fc19f2ea34
commit
9f2d73e2f1
44
docs/class_based_views.md
Normal file
44
docs/class_based_views.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# Class based views
|
||||||
|
|
||||||
|
Sanic has simple class based implementation. You should implement methods(get, post, put, patch, delete) for the class to every HTTP method you want to support. If someone try to use not implemented method, there will be 405 response.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
```python
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.views import MethodView
|
||||||
|
|
||||||
|
app = Sanic('some_name')
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
return text('I am post method')
|
||||||
|
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
return text('I am put method')
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
return text('I am patch method')
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
return text('I am delete method')
|
||||||
|
|
||||||
|
app.add_route(SimpleView(), '/')
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need any url params just mention them in method definition:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class NameView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, name, *args, **kwargs):
|
||||||
|
return text('Hello {}'.format(name))
|
||||||
|
|
||||||
|
app.add_route(NameView(), '/<name')
|
||||||
|
|
||||||
|
```
|
|
@ -29,4 +29,16 @@ async def person_handler(request, name):
|
||||||
async def folder_handler(request, folder_id):
|
async def folder_handler(request, folder_id):
|
||||||
return text('Folder - {}'.format(folder_id))
|
return text('Folder - {}'.format(folder_id))
|
||||||
|
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK')
|
||||||
|
app.add_route(handler1, '/test')
|
||||||
|
|
||||||
|
async def handler(request, name):
|
||||||
|
return text('Folder - {}'.format(name))
|
||||||
|
app.add_route(handler, '/folder/<name>')
|
||||||
|
|
||||||
|
async def person_handler(request, name):
|
||||||
|
return text('Person - {}'.format(name))
|
||||||
|
app.add_route(handler, '/person/<name:[A-z]>')
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -11,3 +11,4 @@ bottle
|
||||||
kyoukai
|
kyoukai
|
||||||
falcon
|
falcon
|
||||||
tornado
|
tornado
|
||||||
|
aiofiles
|
||||||
|
|
|
@ -91,6 +91,12 @@ class Blueprint:
|
||||||
return handler
|
return handler
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
def add_route(self, handler, uri, methods=None):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
self.record(lambda s: s.add_route(handler, uri, methods))
|
||||||
|
return handler
|
||||||
|
|
||||||
def listener(self, event):
|
def listener(self, event):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -60,6 +60,17 @@ class Sanic:
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def add_route(self, handler, uri, methods=None):
|
||||||
|
"""
|
||||||
|
A helper method to register class instance or functions as a handler to the application url routes.
|
||||||
|
:param handler: function or class instance
|
||||||
|
:param uri: path of the URL
|
||||||
|
:param methods: list or tuple of methods allowed
|
||||||
|
:return: function or class instance
|
||||||
|
"""
|
||||||
|
self.route(uri=uri, methods=methods)(handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def exception(self, *exceptions):
|
def exception(self, *exceptions):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,7 +16,7 @@ async def local_request(method, uri, cookies=None, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
||||||
loop=None, *request_args, **request_kwargs):
|
loop=None, debug=False, *request_args, **request_kwargs):
|
||||||
results = []
|
results = []
|
||||||
exceptions = []
|
exceptions = []
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
app.stop()
|
app.stop()
|
||||||
|
|
||||||
app.run(host=HOST, port=42101, after_start=_collect_response, loop=loop)
|
app.run(host=HOST, debug=debug, port=42101, after_start=_collect_response, loop=loop)
|
||||||
|
|
||||||
if exceptions:
|
if exceptions:
|
||||||
raise ValueError("Exception during request: {}".format(exceptions))
|
raise ValueError("Exception during request: {}".format(exceptions))
|
||||||
|
|
33
sanic/views.py
Normal file
33
sanic/views.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from .exceptions import InvalidUsage
|
||||||
|
|
||||||
|
|
||||||
|
class MethodView:
|
||||||
|
""" Simple class based implementation of view for the sanic.
|
||||||
|
You should implement methods(get, post, put, patch, delete) for the class to every HTTP method you want to support.
|
||||||
|
For example:
|
||||||
|
class DummyView(View):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
return text('I am put method')
|
||||||
|
etc.
|
||||||
|
If someone try use not implemented method, there will be 405 response
|
||||||
|
|
||||||
|
If you need any url params just mention them in method definition like:
|
||||||
|
class DummyView(View):
|
||||||
|
|
||||||
|
def get(self, request, my_param_here, *args, **kwargs):
|
||||||
|
return text('I am get method with %s' % my_param_here)
|
||||||
|
|
||||||
|
To add the view into the routing you could use
|
||||||
|
1) app.add_route(DummyView(), '/')
|
||||||
|
2) app.route('/')(DummyView())
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(self, request, *args, **kwargs):
|
||||||
|
handler = getattr(self, request.method.lower(), None)
|
||||||
|
if handler:
|
||||||
|
return handler(request, *args, **kwargs)
|
||||||
|
raise InvalidUsage('Method {} not allowed for URL {}'.format(request.method, request.url), status_code=405)
|
|
@ -84,7 +84,7 @@ def test_dynamic_route_int():
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_route_number():
|
def test_dynamic_route_number():
|
||||||
app = Sanic('test_dynamic_route_int')
|
app = Sanic('test_dynamic_route_number')
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ def test_dynamic_route_number():
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_route_regex():
|
def test_dynamic_route_regex():
|
||||||
app = Sanic('test_dynamic_route_int')
|
app = Sanic('test_dynamic_route_regex')
|
||||||
|
|
||||||
@app.route('/folder/<folder_id:[A-Za-z0-9]{0,4}>')
|
@app.route('/folder/<folder_id:[A-Za-z0-9]{0,4}>')
|
||||||
async def handler(request, folder_id):
|
async def handler(request, folder_id):
|
||||||
|
@ -145,7 +145,7 @@ def test_dynamic_route_unhashable():
|
||||||
|
|
||||||
|
|
||||||
def test_route_duplicate():
|
def test_route_duplicate():
|
||||||
app = Sanic('test_dynamic_route')
|
app = Sanic('test_route_duplicate')
|
||||||
|
|
||||||
with pytest.raises(RouteExists):
|
with pytest.raises(RouteExists):
|
||||||
@app.route('/test')
|
@app.route('/test')
|
||||||
|
@ -178,3 +178,181 @@ def test_method_not_allowed():
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, method='post', uri='/test')
|
request, response = sanic_endpoint_test(app, method='post', uri='/test')
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_static_add_route():
|
||||||
|
app = Sanic('test_static_add_route')
|
||||||
|
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
async def handler2(request):
|
||||||
|
return text('OK2')
|
||||||
|
|
||||||
|
app.add_route(handler1, '/test')
|
||||||
|
app.add_route(handler2, '/test2')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test2')
|
||||||
|
assert response.text == 'OK2'
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_add_route():
|
||||||
|
app = Sanic('test_dynamic_add_route')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
async def handler(request, name):
|
||||||
|
results.append(name)
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/folder/<name>')
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test123')
|
||||||
|
|
||||||
|
assert response.text == 'OK'
|
||||||
|
assert results[0] == 'test123'
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_add_route_string():
|
||||||
|
app = Sanic('test_dynamic_add_route_string')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
async def handler(request, name):
|
||||||
|
results.append(name)
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/folder/<name:string>')
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test123')
|
||||||
|
|
||||||
|
assert response.text == 'OK'
|
||||||
|
assert results[0] == 'test123'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/favicon.ico')
|
||||||
|
|
||||||
|
assert response.text == 'OK'
|
||||||
|
assert results[1] == 'favicon.ico'
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_add_route_int():
|
||||||
|
app = Sanic('test_dynamic_add_route_int')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
async def handler(request, folder_id):
|
||||||
|
results.append(folder_id)
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/folder/<folder_id:int>')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/12345')
|
||||||
|
assert response.text == 'OK'
|
||||||
|
assert type(results[0]) is int
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/asdf')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_add_route_number():
|
||||||
|
app = Sanic('test_dynamic_add_route_number')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
async def handler(request, weight):
|
||||||
|
results.append(weight)
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/weight/<weight:number>')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/weight/12345')
|
||||||
|
assert response.text == 'OK'
|
||||||
|
assert type(results[0]) is float
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/weight/1234.56')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/weight/1234-56')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_add_route_regex():
|
||||||
|
app = Sanic('test_dynamic_route_int')
|
||||||
|
|
||||||
|
async def handler(request, folder_id):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/folder/<folder_id:[A-Za-z0-9]{0,4}>')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test1')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test-123')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_add_route_unhashable():
|
||||||
|
app = Sanic('test_dynamic_add_route_unhashable')
|
||||||
|
|
||||||
|
async def handler(request, unhashable):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/folder/<unhashable:[A-Za-z0-9/]+>/end/')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test/asdf/end/')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test///////end/')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test/end/')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test/nope/')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_route_duplicate():
|
||||||
|
app = Sanic('test_add_route_duplicate')
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
async def handler1(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def handler2(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.add_route(handler1, '/test')
|
||||||
|
app.add_route(handler2, '/test')
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
async def handler1(request, dynamic):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def handler2(request, dynamic):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.add_route(handler1, '/test/<dynamic>/')
|
||||||
|
app.add_route(handler2, '/test/<dynamic>/')
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_route_method_not_allowed():
|
||||||
|
app = Sanic('test_add_route_method_not_allowed')
|
||||||
|
|
||||||
|
async def handler(request):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/test', methods=['GET'])
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, method='post', uri='/test')
|
||||||
|
assert response.status == 405
|
||||||
|
|
155
tests/test_views.py
Normal file
155
tests/test_views.py
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import text, HTTPResponse
|
||||||
|
from sanic.views import MethodView
|
||||||
|
from sanic.blueprints import Blueprint
|
||||||
|
from sanic.request import Request
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
|
||||||
|
def test_methods():
|
||||||
|
app = Sanic('test_methods')
|
||||||
|
|
||||||
|
class DummyView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
return text('I am post method')
|
||||||
|
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
return text('I am put method')
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
return text('I am patch method')
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
return text('I am delete method')
|
||||||
|
|
||||||
|
app.add_route(DummyView(), '/')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, method="get")
|
||||||
|
assert response.text == 'I am get method'
|
||||||
|
request, response = sanic_endpoint_test(app, method="post")
|
||||||
|
assert response.text == 'I am post method'
|
||||||
|
request, response = sanic_endpoint_test(app, method="put")
|
||||||
|
assert response.text == 'I am put method'
|
||||||
|
request, response = sanic_endpoint_test(app, method="patch")
|
||||||
|
assert response.text == 'I am patch method'
|
||||||
|
request, response = sanic_endpoint_test(app, method="delete")
|
||||||
|
assert response.text == 'I am delete method'
|
||||||
|
|
||||||
|
|
||||||
|
def test_unexisting_methods():
|
||||||
|
app = Sanic('test_unexisting_methods')
|
||||||
|
|
||||||
|
class DummyView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
app.add_route(DummyView(), '/')
|
||||||
|
request, response = sanic_endpoint_test(app, method="get")
|
||||||
|
assert response.text == 'I am get method'
|
||||||
|
request, response = sanic_endpoint_test(app, method="post")
|
||||||
|
assert response.text == 'Error: Method POST not allowed for URL /'
|
||||||
|
|
||||||
|
|
||||||
|
def test_argument_methods():
|
||||||
|
app = Sanic('test_argument_methods')
|
||||||
|
|
||||||
|
class DummyView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, my_param_here, *args, **kwargs):
|
||||||
|
return text('I am get method with %s' % my_param_here)
|
||||||
|
|
||||||
|
app.add_route(DummyView(), '/<my_param_here>')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test123')
|
||||||
|
|
||||||
|
assert response.text == 'I am get method with test123'
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_bp():
|
||||||
|
app = Sanic('test_with_bp')
|
||||||
|
bp = Blueprint('test_text')
|
||||||
|
|
||||||
|
class DummyView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
bp.add_route(DummyView(), '/')
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
|
||||||
|
assert response.text == 'I am get method'
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_bp_with_url_prefix():
|
||||||
|
app = Sanic('test_with_bp_with_url_prefix')
|
||||||
|
bp = Blueprint('test_text', url_prefix='/test1')
|
||||||
|
|
||||||
|
class DummyView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
bp.add_route(DummyView(), '/')
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test1/')
|
||||||
|
|
||||||
|
assert response.text == 'I am get method'
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_middleware():
|
||||||
|
app = Sanic('test_with_middleware')
|
||||||
|
|
||||||
|
class DummyView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
app.add_route(DummyView(), '/')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
@app.middleware
|
||||||
|
async def handler(request):
|
||||||
|
results.append(request)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
|
||||||
|
assert response.text == 'I am get method'
|
||||||
|
assert type(results[0]) is Request
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_middleware_response():
|
||||||
|
app = Sanic('test_with_middleware_response')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
@app.middleware('request')
|
||||||
|
async def process_response(request):
|
||||||
|
results.append(request)
|
||||||
|
|
||||||
|
@app.middleware('response')
|
||||||
|
async def process_response(request, response):
|
||||||
|
results.append(request)
|
||||||
|
results.append(response)
|
||||||
|
|
||||||
|
class DummyView(MethodView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
app.add_route(DummyView(), '/')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
|
||||||
|
assert response.text == 'I am get method'
|
||||||
|
assert type(results[0]) is Request
|
||||||
|
assert type(results[1]) is Request
|
||||||
|
assert issubclass(type(results[2]), HTTPResponse)
|
Loading…
Reference in New Issue
Block a user