Merge pull request #167 from AntonDnepr/class-based-views
Class based views
This commit is contained in:
commit
cce47a633a
|
@ -52,6 +52,7 @@ app.run(host="0.0.0.0", port=8000)
|
|||
* [Middleware](docs/middleware.md)
|
||||
* [Exceptions](docs/exceptions.md)
|
||||
* [Blueprints](docs/blueprints.md)
|
||||
* [Class Based Views](docs/class_based_views.md)
|
||||
* [Cookies](docs/cookies.md)
|
||||
* [Static Files](docs/static_files.md)
|
||||
* [Deploying](docs/deploying.md)
|
||||
|
|
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 tries to use a method that has not been implemented, there will be 405 response.
|
||||
|
||||
## Examples
|
||||
```python
|
||||
from sanic import Sanic
|
||||
from sanic.views import HTTPMethodView
|
||||
|
||||
app = Sanic('some_name')
|
||||
|
||||
|
||||
class SimpleView(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
return text('I am get method')
|
||||
|
||||
def post(self, request):
|
||||
return text('I am post method')
|
||||
|
||||
def put(self, request):
|
||||
return text('I am put method')
|
||||
|
||||
def patch(self, request):
|
||||
return text('I am patch method')
|
||||
|
||||
def delete(self, request):
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request, name):
|
||||
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):
|
||||
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
|
||||
falcon
|
||||
tornado
|
||||
aiofiles
|
||||
|
|
|
@ -91,6 +91,12 @@ class Blueprint:
|
|||
return handler
|
||||
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):
|
||||
"""
|
||||
"""
|
||||
|
|
|
@ -60,6 +60,19 @@ class Sanic:
|
|||
|
||||
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
|
||||
def exception(self, *exceptions):
|
||||
"""
|
||||
|
|
|
@ -16,7 +16,8 @@ async def local_request(method, uri, cookies=None, *args, **kwargs):
|
|||
|
||||
|
||||
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 = []
|
||||
exceptions = []
|
||||
|
||||
|
@ -34,7 +35,8 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
|||
exceptions.append(e)
|
||||
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:
|
||||
raise ValueError("Exception during request: {}".format(exceptions))
|
||||
|
|
36
sanic/views.py
Normal file
36
sanic/views.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from .exceptions import InvalidUsage
|
||||
|
||||
|
||||
class HTTPMethodView:
|
||||
""" 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():
|
||||
app = Sanic('test_dynamic_route_int')
|
||||
app = Sanic('test_dynamic_route_number')
|
||||
|
||||
results = []
|
||||
|
||||
|
@ -105,7 +105,7 @@ def test_dynamic_route_number():
|
|||
|
||||
|
||||
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}>')
|
||||
async def handler(request, folder_id):
|
||||
|
@ -145,7 +145,7 @@ def test_dynamic_route_unhashable():
|
|||
|
||||
|
||||
def test_route_duplicate():
|
||||
app = Sanic('test_dynamic_route')
|
||||
app = Sanic('test_route_duplicate')
|
||||
|
||||
with pytest.raises(RouteExists):
|
||||
@app.route('/test')
|
||||
|
@ -178,3 +178,181 @@ def test_method_not_allowed():
|
|||
|
||||
request, response = sanic_endpoint_test(app, method='post', uri='/test')
|
||||
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 HTTPMethodView
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
return text('I am get method')
|
||||
|
||||
def post(self, request):
|
||||
return text('I am post method')
|
||||
|
||||
def put(self, request):
|
||||
return text('I am put method')
|
||||
|
||||
def patch(self, request):
|
||||
return text('I am patch method')
|
||||
|
||||
def delete(self, request):
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request, my_param_here):
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
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(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
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