Merge branch 'master' into master
This commit is contained in:
		
							
								
								
									
										62
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| # Contributing | ||||
|  | ||||
| Thank you for your interest! Sanic is always looking for contributors. If you | ||||
| don't feel comfortable contributing code, adding docstrings to the source files | ||||
| is very appreciated. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| To develop on sanic (and mainly to just run the tests) it is highly recommend to | ||||
| install from sources. | ||||
|  | ||||
| So assume you have already cloned the repo and are in the working directory with | ||||
| a virtual environment already set up, then run: | ||||
|  | ||||
| ```bash | ||||
| python setup.py develop && pip install -r requirements-dev.txt | ||||
| ``` | ||||
|  | ||||
| ## Running tests | ||||
|  | ||||
| To run the tests for sanic it is recommended to use tox like so: | ||||
|  | ||||
| ```bash | ||||
| tox | ||||
| ``` | ||||
|  | ||||
| See it's that simple! | ||||
|  | ||||
| ## Pull requests! | ||||
|  | ||||
| So the pull request approval rules are pretty simple: | ||||
| 1. All pull requests must pass unit tests | ||||
| 2. All pull requests must be reviewed and approved by at least  | ||||
| one current collaborator on the project | ||||
| 3. All pull requests must pass flake8 checks | ||||
| 4. If you decide to remove/change anything from any common interface | ||||
| a deprecation message should accompany it. | ||||
| 5. If you implement a new feature you should have at least one unit | ||||
| test to accompany it. | ||||
|  | ||||
| ## Documentation | ||||
|  | ||||
| Sanic's documentation is built | ||||
| using [sphinx](http://www.sphinx-doc.org/en/1.5.1/). Guides are written in | ||||
| Markdown and can be found in the `docs` folder, while the module reference is | ||||
| automatically generated using `sphinx-apidoc`. | ||||
|  | ||||
| To generate the documentation from scratch: | ||||
|  | ||||
| ```bash | ||||
| sphinx-apidoc -fo docs/_api/ sanic | ||||
| sphinx-build -b html docs docs/_build | ||||
| ``` | ||||
|  | ||||
| The HTML documentation will be created in the `docs/_build` folder. | ||||
|  | ||||
| ## Warning | ||||
|  | ||||
| One of the main goals of Sanic is speed. Code that lowers the performance of | ||||
| Sanic without significant gains in usability, security, or features may not be | ||||
| merged. Please don't let this intimidate you! If you have any concerns about an | ||||
| idea, open an issue for discussion and help. | ||||
| @@ -83,3 +83,4 @@ Out of the box there are just a few predefined values which can be overwritten w | ||||
|     | ----------------- | --------- | --------------------------------- | | ||||
|     | REQUEST_MAX_SIZE  | 100000000 | How big a request may be (bytes)  | | ||||
|     | REQUEST_TIMEOUT   | 60        | How long a request can take (sec) | | ||||
|     | KEEP_ALIVE        | True      | Disables keep-alive when False    | | ||||
|   | ||||
| @@ -4,10 +4,39 @@ Thank you for your interest! Sanic is always looking for contributors. If you | ||||
| don't feel comfortable contributing code, adding docstrings to the source files | ||||
| is very appreciated. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| To develop on sanic (and mainly to just run the tests) it is highly recommend to | ||||
| install from sources. | ||||
|  | ||||
| So assume you have already cloned the repo and are in the working directory with | ||||
| a virtual environment already set up, then run: | ||||
|  | ||||
| ```bash | ||||
| python setup.py develop && pip install -r requirements-dev.txt | ||||
| ``` | ||||
|  | ||||
| ## Running tests | ||||
|  | ||||
| * `python -m pip install pytest` | ||||
| * `python -m pytest tests` | ||||
| To run the tests for sanic it is recommended to use tox like so: | ||||
|  | ||||
| ```bash | ||||
| tox | ||||
| ``` | ||||
|  | ||||
| See it's that simple! | ||||
|  | ||||
| ## Pull requests! | ||||
|  | ||||
| So the pull request approval rules are pretty simple: | ||||
| 1. All pull requests must pass unit tests | ||||
| * All pull requests must be reviewed and approved by at least  | ||||
| one current collaborator on the project | ||||
| * All pull requests must pass flake8 checks | ||||
| * If you decide to remove/change anything from any common interface | ||||
| a deprecation message should accompany it. | ||||
| * If you implement a new feature you should have at least one unit | ||||
| test to accompany it. | ||||
|  | ||||
| ## Documentation | ||||
|  | ||||
|   | ||||
							
								
								
									
										41
									
								
								examples/dask_distributed.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								examples/dask_distributed.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| from sanic import Sanic | ||||
| from sanic import response | ||||
|  | ||||
| from tornado.platform.asyncio import BaseAsyncIOLoop, to_asyncio_future | ||||
| from distributed import LocalCluster, Client | ||||
|  | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
|  | ||||
| def square(x): | ||||
|     return x**2 | ||||
|  | ||||
|  | ||||
| @app.listener('after_server_start') | ||||
| async def setup(app, loop): | ||||
|     # configure tornado use asyncio's loop | ||||
|     ioloop = BaseAsyncIOLoop(loop) | ||||
|  | ||||
|     # init distributed client | ||||
|     app.client = Client('tcp://localhost:8786', loop=ioloop, start=False) | ||||
|     await to_asyncio_future(app.client._start()) | ||||
|  | ||||
|  | ||||
| @app.listener('before_server_stop') | ||||
| async def stop(app, loop): | ||||
|     await to_asyncio_future(app.client._shutdown()) | ||||
|  | ||||
|  | ||||
| @app.route('/<value:int>') | ||||
| async def test(request, value): | ||||
|     future = app.client.submit(square, value) | ||||
|     result = await to_asyncio_future(future._result()) | ||||
|     return response.text(f'The square of {value} is {result}') | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Distributed cluster should run somewhere else | ||||
|     with LocalCluster(scheduler_port=8786, nanny=False, n_workers=2, | ||||
|                       threads_per_worker=1) as cluster: | ||||
|         app.run(host="0.0.0.0", port=8000) | ||||
| @@ -1,9 +1,7 @@ | ||||
| """ | ||||
| Example intercepting uncaught exceptions using Sanic's error handler framework. | ||||
|  | ||||
| This may be useful for developers wishing to use Sentry, Airbrake, etc. | ||||
| or a custom system to log and monitor unexpected errors in production. | ||||
|  | ||||
| First we create our own class inheriting from Handler in sanic.exceptions, | ||||
| and pass in an instance of it when we create our Sanic instance. Inside this | ||||
| class' default handler, we can do anything including sending exceptions to | ||||
| @@ -39,7 +37,7 @@ server's error_handler to an instance of our CustomHandler | ||||
| """ | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
| from sanic import response | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
| @@ -52,7 +50,7 @@ async def test(request): | ||||
|     # Here, something occurs which causes an unexpected exception | ||||
|     # This exception will flow to our custom handler. | ||||
|     1 / 0 | ||||
|     return json({"test": True}) | ||||
|     return response.json({"test": True}) | ||||
|  | ||||
|  | ||||
| app.run(host="0.0.0.0", port=8000, debug=True) | ||||
| app.run(host="0.0.0.0", port=8000, debug=True) | ||||
| @@ -1,18 +1,27 @@ | ||||
| ## To use this example: | ||||
| # curl -d '{"name": "John Doe"}' localhost:8000 | ||||
| # Render templates in a Flask like way from a "template" directory in the project | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sanic.response import html | ||||
| from jinja2 import Template | ||||
|  | ||||
| template = Template('Hello {{ name }}!') | ||||
| from sanic import response | ||||
| from jinja2 import Evironment, PackageLoader, select_autoescape | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
| # Load the template environment with async support | ||||
| template_env = Environment( | ||||
|     loader=jinja2.PackageLoader('yourapplication', 'templates'), | ||||
|     autoescape=jinja2.select_autoescape(['html', 'xml']), | ||||
|     enable_async=True | ||||
| ) | ||||
|  | ||||
| # Load the template from file | ||||
| template = template_env.get_template("example_template.html") | ||||
|  | ||||
|  | ||||
| @app.route('/') | ||||
| async def test(request): | ||||
|     data = request.json | ||||
|     return html(template.render(**data)) | ||||
|     rendered_template = await template.render_async(**data) | ||||
|     return response.html(rendered_template) | ||||
|  | ||||
|  | ||||
| app.run(host="0.0.0.0", port=8000) | ||||
| app.run(host="0.0.0.0", port=8080, debug=True) | ||||
							
								
								
									
										26
									
								
								examples/modify_header_example.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								examples/modify_header_example.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| """ | ||||
| Modify header or status in response | ||||
| """ | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sanic import response | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
| @app.route('/') | ||||
| def handle_request(request): | ||||
|     return response.json( | ||||
|         {'message': 'Hello world!'}, | ||||
|         headers={'X-Served-By': 'sanic'}, | ||||
|         status=200 | ||||
|     ) | ||||
|      | ||||
| @app.route('/unauthorized') | ||||
| def handle_request(request): | ||||
|     return response.json( | ||||
|         {'message': 'You are not authorized'}, | ||||
|         headers={'X-Served-By': 'sanic'}, | ||||
|         status=404 | ||||
|     ) | ||||
|  | ||||
| app.run(host="0.0.0.0", port=8000, debug=True) | ||||
| @@ -1,6 +1,5 @@ | ||||
| from sanic import Sanic | ||||
| from sanic.response import text | ||||
| import json | ||||
| from sanic import response | ||||
| import logging | ||||
|  | ||||
| logging_format = "[%(asctime)s] %(process)d-%(levelname)s " | ||||
| @@ -18,6 +17,6 @@ sanic = Sanic() | ||||
| @sanic.route("/") | ||||
| def test(request): | ||||
|     log.info("received request; responding with 'hey'") | ||||
|     return text("hey") | ||||
|     return response.text("hey") | ||||
|  | ||||
| sanic.run(host="0.0.0.0", port=8000) | ||||
|   | ||||
							
								
								
									
										85
									
								
								examples/plotly_example/plotlyjs_example.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								examples/plotly_example/plotlyjs_example.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| from sanic import Sanic | ||||
|  | ||||
| from sanic_session import InMemorySessionInterface | ||||
| from sanic_jinja2 import SanicJinja2 | ||||
|  | ||||
| import json | ||||
| import plotly | ||||
|  | ||||
| import pandas as pd | ||||
| import numpy as np | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
| jinja = SanicJinja2(app) | ||||
| session = InMemorySessionInterface(cookie_name=app.name, prefix=app.name) | ||||
|  | ||||
| @app.middleware('request') | ||||
| async def print_on_request(request): | ||||
| 	print(request.headers) | ||||
| 	await session.open(request) | ||||
|  | ||||
| @app.middleware('response') | ||||
| async def print_on_response(request, response): | ||||
| 	await session.save(request, response) | ||||
|  | ||||
|  | ||||
|  | ||||
| @app.route('/') | ||||
| async def index(request): | ||||
|     rng = pd.date_range('1/1/2011', periods=7500, freq='H') | ||||
|     ts = pd.Series(np.random.randn(len(rng)), index=rng) | ||||
|  | ||||
|     graphs = [ | ||||
|         dict( | ||||
|             data=[ | ||||
|                 dict( | ||||
|                     x=[1, 2, 3], | ||||
|                     y=[10, 20, 30], | ||||
|                     type='scatter' | ||||
|                 ), | ||||
|             ], | ||||
|             layout=dict( | ||||
|                 title='first graph' | ||||
|             ) | ||||
|         ), | ||||
|  | ||||
|         dict( | ||||
|             data=[ | ||||
|                 dict( | ||||
|                     x=[1, 3, 5], | ||||
|                     y=[10, 50, 30], | ||||
|                     type='bar' | ||||
|                 ), | ||||
|             ], | ||||
|             layout=dict( | ||||
|                 title='second graph' | ||||
|             ) | ||||
|         ), | ||||
|  | ||||
|         dict( | ||||
|             data=[ | ||||
|                 dict( | ||||
|                     x=ts.index,  # Can use the pandas data structures directly | ||||
|                     y=ts | ||||
|                 ) | ||||
|             ] | ||||
|         ) | ||||
|     ] | ||||
|  | ||||
|     # Add "ids" to each of the graphs to pass up to the client | ||||
|     # for templating | ||||
|     ids = ['graph-{}'.format(i) for i, _ in enumerate(graphs)] | ||||
|  | ||||
|     # Convert the figures to JSON | ||||
|     # PlotlyJSONEncoder appropriately converts pandas, datetime, etc | ||||
|     # objects to their JSON equivalents | ||||
|     graphJSON = json.dumps(graphs, cls=plotly.utils.PlotlyJSONEncoder) | ||||
|  | ||||
|     return jinja.render('index.html', request, | ||||
|                            ids=ids, | ||||
|                            graphJSON=graphJSON) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     app.run(host='0.0.0.0', port=8000, debug=True) | ||||
							
								
								
									
										5
									
								
								examples/plotly_example/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								examples/plotly_example/requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| pandas==0.19.2 | ||||
| plotly==2.0.7 | ||||
| sanic==0.5.0 | ||||
| sanic-jinja2==0.5.1 | ||||
| sanic-session==0.1.3 | ||||
							
								
								
									
										0
									
								
								examples/plotly_example/templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								examples/plotly_example/templates/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -1,6 +1,6 @@ | ||||
| from sanic import Sanic | ||||
| import asyncio | ||||
| from sanic.response import text | ||||
| from sanic import Sanic | ||||
| from sanic import response | ||||
| from sanic.config import Config | ||||
| from sanic.exceptions import RequestTimeout | ||||
|  | ||||
| @@ -11,11 +11,11 @@ app = Sanic(__name__) | ||||
| @app.route('/') | ||||
| async def test(request): | ||||
|     await asyncio.sleep(3) | ||||
|     return text('Hello, world!') | ||||
|     return response.text('Hello, world!') | ||||
|  | ||||
|  | ||||
| @app.exception(RequestTimeout) | ||||
| def timeout(request, exception): | ||||
|     return text('RequestTimeout from error_handler.', 408) | ||||
|     return response.text('RequestTimeout from error_handler.', 408) | ||||
|  | ||||
| app.run(host='0.0.0.0', port=8000) | ||||
| app.run(host='0.0.0.0', port=8000) | ||||
| @@ -5,7 +5,7 @@ motor==1.1 | ||||
| sanic==0.2.0 | ||||
| """ | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
| from sanic import response | ||||
|  | ||||
|  | ||||
| app = Sanic('motor_mongodb') | ||||
| @@ -25,7 +25,7 @@ async def get(request): | ||||
|     for doc in docs: | ||||
|         doc['id'] = str(doc['_id']) | ||||
|         del doc['_id'] | ||||
|     return json(docs) | ||||
|     return response.json(docs) | ||||
|  | ||||
|  | ||||
| @app.route('/post', methods=['POST']) | ||||
| @@ -34,8 +34,8 @@ async def new(request): | ||||
|     print(doc) | ||||
|     db = get_db() | ||||
|     object_id = await db.test_col.save(doc) | ||||
|     return json({'object_id': str(object_id)}) | ||||
|     return response.json({'object_id': str(object_id)}) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     app.run(host='127.0.0.1', port=8000) | ||||
|     app.run(host='0.0.0.0', port=8000, debug=True) | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
| from sanic import response | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
|  | ||||
| @app.route("/") | ||||
| async def test(request): | ||||
|     return json({"test": True}) | ||||
|     return response.json({"test": True}) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import os | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sanic.log import log | ||||
| from sanic.response import json, text, file | ||||
| from sanic import response | ||||
| from sanic.exceptions import ServerError | ||||
|  | ||||
| app = Sanic(__name__) | ||||
| @@ -10,17 +10,17 @@ app = Sanic(__name__) | ||||
|  | ||||
| @app.route("/") | ||||
| async def test_async(request): | ||||
|     return json({"test": True}) | ||||
|     return response.json({"test": True}) | ||||
|  | ||||
|  | ||||
| @app.route("/sync", methods=['GET', 'POST']) | ||||
| def test_sync(request): | ||||
|     return json({"test": True}) | ||||
|     return response.json({"test": True}) | ||||
|  | ||||
|  | ||||
| @app.route("/dynamic/<name>/<id:int>") | ||||
| def test_params(request, name, id): | ||||
|     return text("yeehaww {} {}".format(name, id)) | ||||
|     return response.text("yeehaww {} {}".format(name, id)) | ||||
|  | ||||
|  | ||||
| @app.route("/exception") | ||||
| @@ -31,11 +31,11 @@ def exception(request): | ||||
| async def test_await(request): | ||||
|     import asyncio | ||||
|     await asyncio.sleep(5) | ||||
|     return text("I'm feeling sleepy") | ||||
|     return response.text("I'm feeling sleepy") | ||||
|  | ||||
| @app.route("/file") | ||||
| async def test_file(request): | ||||
|     return await file(os.path.abspath("setup.py")) | ||||
|     return await response.file(os.path.abspath("setup.py")) | ||||
|  | ||||
|  | ||||
| # ----------------------------------------------- # | ||||
| @@ -44,7 +44,7 @@ async def test_file(request): | ||||
|  | ||||
| @app.exception(ServerError) | ||||
| async def test(request, exception): | ||||
|     return json({"exception": "{}".format(exception), "status": exception.status_code}, status=exception.status_code) | ||||
|     return response.json({"exception": "{}".format(exception), "status": exception.status_code}, status=exception.status_code) | ||||
|  | ||||
|  | ||||
| # ----------------------------------------------- # | ||||
| @@ -53,17 +53,17 @@ async def test(request, exception): | ||||
|  | ||||
| @app.route("/json") | ||||
| def post_json(request): | ||||
|     return json({"received": True, "message": request.json}) | ||||
|     return response.json({"received": True, "message": request.json}) | ||||
|  | ||||
|  | ||||
| @app.route("/form") | ||||
| def post_json(request): | ||||
|     return json({"received": True, "form_data": request.form, "test": request.form.get('test')}) | ||||
|     return response.json({"received": True, "form_data": request.form, "test": request.form.get('test')}) | ||||
|  | ||||
|  | ||||
| @app.route("/query_string") | ||||
| def query_string(request): | ||||
|     return json({"parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string}) | ||||
|     return response.json({"parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string}) | ||||
|  | ||||
|  | ||||
| # ----------------------------------------------- # | ||||
|   | ||||
							
								
								
									
										18
									
								
								examples/url_for_example.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								examples/url_for_example.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| from sanic import Sanic | ||||
| from sanic import response | ||||
|  | ||||
| app = Sanic(__name__) | ||||
|  | ||||
| @app.route('/') | ||||
| async def index(request): | ||||
|     # generate a URL for the endpoint `post_handler` | ||||
|     url = app.url_for('post_handler', post_id=5) | ||||
|     # the URL is `/posts/5`, redirect to it | ||||
|     return response.redirect(url) | ||||
|  | ||||
| @app.route('/posts/<post_id>') | ||||
| async def post_handler(request, post_id): | ||||
|     return response.text('Post - {}'.format(post_id)) | ||||
|      | ||||
| if __name__ == '__main__': | ||||
|     app.run(host="0.0.0.0", port=8000, debug=True) | ||||
| @@ -1,4 +1,4 @@ | ||||
| from sanic.response import text | ||||
| from sanic import response | ||||
| from sanic import Sanic | ||||
| from sanic.blueprints import Blueprint | ||||
|  | ||||
| @@ -15,25 +15,25 @@ bp = Blueprint("bp", host="bp.example.com") | ||||
|                       "somethingelse.com", | ||||
|                       "therestofyourdomains.com"]) | ||||
| async def hello(request): | ||||
|     return text("Some defaults") | ||||
|     return response.text("Some defaults") | ||||
|  | ||||
| @app.route('/', host="example.com") | ||||
| async def hello(request): | ||||
|     return text("Answer") | ||||
|     return response.text("Answer") | ||||
|  | ||||
| @app.route('/', host="sub.example.com") | ||||
| async def hello(request): | ||||
|     return text("42") | ||||
|     return response.text("42") | ||||
|  | ||||
| @bp.route("/question") | ||||
| async def hello(request): | ||||
|     return text("What is the meaning of life?") | ||||
|     return response.text("What is the meaning of life?") | ||||
|  | ||||
| @bp.route("/answer") | ||||
| async def hello(request): | ||||
|     return text("42") | ||||
|     return response.text("42") | ||||
|  | ||||
| app.register_blueprint(bp) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     app.run(host="0.0.0.0", port=8000) | ||||
|     app.run(host="0.0.0.0", port=8000) | ||||
| @@ -17,7 +17,7 @@ from sanic.handlers import ErrorHandler | ||||
| from sanic.log import log | ||||
| from sanic.response import HTTPResponse, StreamingHTTPResponse | ||||
| from sanic.router import Router | ||||
| from sanic.server import serve, serve_multiple, HttpProtocol | ||||
| from sanic.server import serve, serve_multiple, HttpProtocol, Signal | ||||
| from sanic.static import register as static_register | ||||
| from sanic.testing import SanicTestClient | ||||
| from sanic.views import CompositionView | ||||
| @@ -293,7 +293,7 @@ class Sanic: | ||||
|                            attach_to=middleware_or_request) | ||||
|  | ||||
|     # Static Files | ||||
|     def static(self, uri, file_or_directory, pattern='.+', | ||||
|     def static(self, uri, file_or_directory, pattern=r'/?.+', | ||||
|                use_modified_since=True, use_content_range=False): | ||||
|         """Register a root to serve files from. The input can either be a | ||||
|         file or a directory. See | ||||
| @@ -687,11 +687,13 @@ class Sanic: | ||||
|             'port': port, | ||||
|             'sock': sock, | ||||
|             'ssl': ssl, | ||||
|             'signal': Signal(), | ||||
|             'debug': debug, | ||||
|             'request_handler': self.handle_request, | ||||
|             'error_handler': self.error_handler, | ||||
|             'request_timeout': self.config.REQUEST_TIMEOUT, | ||||
|             'request_max_size': self.config.REQUEST_MAX_SIZE, | ||||
|             'keep_alive': self.config.KEEP_ALIVE, | ||||
|             'loop': loop, | ||||
|             'register_sys_signals': register_sys_signals, | ||||
|             'backlog': backlog, | ||||
|   | ||||
| @@ -116,7 +116,7 @@ if type(_addr) is str and not os.path.exists(_addr): | ||||
|  | ||||
|  | ||||
| class Config(dict): | ||||
|     def __init__(self, defaults=None, load_env=True): | ||||
|     def __init__(self, defaults=None, load_env=True, keep_alive=True): | ||||
|         super().__init__(defaults or {}) | ||||
|         self.LOGO = """ | ||||
|                  ▄▄▄▄▄ | ||||
| @@ -141,6 +141,7 @@ class Config(dict): | ||||
| """ | ||||
|         self.REQUEST_MAX_SIZE = 100000000  # 100 megababies | ||||
|         self.REQUEST_TIMEOUT = 60  # 60 seconds | ||||
|         self.KEEP_ALIVE = keep_alive | ||||
|  | ||||
|         if load_env: | ||||
|             self.load_environment_vars() | ||||
| @@ -208,11 +209,11 @@ class Config(dict): | ||||
|                 self[key] = getattr(obj, key) | ||||
|  | ||||
|     def load_environment_vars(self): | ||||
|         """ | ||||
|         Looks for any SANIC_ prefixed environment variables and applies | ||||
|         them to the configuration if present. | ||||
|         """ | ||||
|         for k, v in os.environ.items(): | ||||
|             """ | ||||
|             Looks for any SANIC_ prefixed environment variables and applies | ||||
|             them to the configuration if present. | ||||
|             """ | ||||
|             if k.startswith(SANIC_PREFIX): | ||||
|                 _, config_key = k.split(SANIC_PREFIX, 1) | ||||
|                 self[config_key] = v | ||||
|   | ||||
| @@ -78,9 +78,10 @@ class Request(dict): | ||||
|         :return: token related to request | ||||
|         """ | ||||
|         auth_header = self.headers.get('Authorization') | ||||
|         if auth_header is not None: | ||||
|             return auth_header.split()[1] | ||||
|         return auth_header | ||||
|         if 'Token ' in auth_header: | ||||
|             return auth_header.partition('Token ')[-1] | ||||
|         else: | ||||
|             return auth_header | ||||
|  | ||||
|     @property | ||||
|     def form(self): | ||||
|   | ||||
| @@ -210,7 +210,7 @@ class HTTPResponse(BaseHTTPResponse): | ||||
|         # Speeds up response rate 6% over pulling from all | ||||
|         status = COMMON_STATUS_CODES.get(self.status) | ||||
|         if not status: | ||||
|             status = ALL_STATUS_CODES.get(self.status) | ||||
|             status = ALL_STATUS_CODES.get(self.status, b'UNKNOWN RESPONSE') | ||||
|  | ||||
|         return (b'HTTP/%b %d %b\r\n' | ||||
|                 b'Connection: %b\r\n' | ||||
|   | ||||
| @@ -16,6 +16,7 @@ REGEX_TYPES = { | ||||
|     'int': (int, r'\d+'), | ||||
|     'number': (float, r'[0-9\\.]+'), | ||||
|     'alpha': (str, r'[A-Za-z]+'), | ||||
|     'path': (str, r'[^/].*?'), | ||||
| } | ||||
|  | ||||
| ROUTER_CACHE_SIZE = 1024 | ||||
| @@ -71,7 +72,8 @@ class Router: | ||||
|         self.routes_always_check = [] | ||||
|         self.hosts = set() | ||||
|  | ||||
|     def parse_parameter_string(self, parameter_string): | ||||
|     @classmethod | ||||
|     def parse_parameter_string(cls, parameter_string): | ||||
|         """Parse a parameter string into its constituent name, type, and | ||||
|         pattern | ||||
|  | ||||
| @@ -161,10 +163,10 @@ class Router: | ||||
|             parameters.append(parameter) | ||||
|  | ||||
|             # Mark the whole route as unhashable if it has the hash key in it | ||||
|             if re.search('(^|[^^]){1}/', pattern): | ||||
|             if re.search(r'(^|[^^]){1}/', pattern): | ||||
|                 properties['unhashable'] = True | ||||
|             # Mark the route as unhashable if it matches the hash key | ||||
|             elif re.search(pattern, '/'): | ||||
|             elif re.search(r'/', pattern): | ||||
|                 properties['unhashable'] = True | ||||
|  | ||||
|             return '({})'.format(pattern) | ||||
|   | ||||
| @@ -73,7 +73,8 @@ class HttpProtocol(asyncio.Protocol): | ||||
|  | ||||
|     def __init__(self, *, loop, request_handler, error_handler, | ||||
|                  signal=Signal(), connections=set(), request_timeout=60, | ||||
|                  request_max_size=None, request_class=None, has_log=True): | ||||
|                  request_max_size=None, request_class=None, has_log=True, | ||||
|                  keep_alive=True): | ||||
|         self.loop = loop | ||||
|         self.transport = None | ||||
|         self.request = None | ||||
| @@ -92,10 +93,13 @@ class HttpProtocol(asyncio.Protocol): | ||||
|         self._timeout_handler = None | ||||
|         self._last_request_time = None | ||||
|         self._request_handler_task = None | ||||
|         self._keep_alive = keep_alive | ||||
|  | ||||
|     @property | ||||
|     def keep_alive(self): | ||||
|         return self.parser.should_keep_alive() and not self.signal.stopped | ||||
|         return (self._keep_alive | ||||
|                 and not self.signal.stopped | ||||
|                 and self.parser.should_keep_alive()) | ||||
|  | ||||
|     # -------------------------------------------- # | ||||
|     # Connection | ||||
| @@ -357,7 +361,8 @@ def serve(host, port, request_handler, error_handler, before_start=None, | ||||
|           request_timeout=60, ssl=None, sock=None, request_max_size=None, | ||||
|           reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100, | ||||
|           register_sys_signals=True, run_async=False, connections=None, | ||||
|           signal=Signal(), request_class=None, has_log=True): | ||||
|           signal=Signal(), request_class=None, has_log=True, keep_alive=True): | ||||
|           signal=Signal(), request_class=None, keep_alive=True): | ||||
|     """Start asynchronous HTTP Server on an individual process. | ||||
|  | ||||
|     :param host: Address to host on | ||||
| @@ -406,7 +411,8 @@ def serve(host, port, request_handler, error_handler, before_start=None, | ||||
|         request_timeout=request_timeout, | ||||
|         request_max_size=request_max_size, | ||||
|         request_class=request_class, | ||||
|         has_log=has_log | ||||
|         has_log=has_log, | ||||
|         keep_alive=keep_alive, | ||||
|     ) | ||||
|  | ||||
|     server_coroutine = loop.create_server( | ||||
|   | ||||
| @@ -48,14 +48,18 @@ def register(app, uri, file_or_directory, pattern, | ||||
|         # Merge served directory and requested file if provided | ||||
|         # Strip all / that in the beginning of the URL to help prevent python | ||||
|         # from herping a derp and treating the uri as an absolute path | ||||
|         file_path = file_or_directory | ||||
|         root_path = file_path = file_or_directory | ||||
|         if file_uri: | ||||
|             file_path = path.join( | ||||
|                 file_or_directory, sub('^[/]*', '', file_uri)) | ||||
|  | ||||
|         # URL decode the path sent by the browser otherwise we won't be able to | ||||
|         # match filenames which got encoded (filenames with spaces etc) | ||||
|         file_path = unquote(file_path) | ||||
|         file_path = path.abspath(unquote(file_path)) | ||||
|         if not file_path.startswith(path.abspath(unquote(root_path))): | ||||
|             raise FileNotFound('File not found', | ||||
|                                path=file_or_directory, | ||||
|                                relative_url=file_uri) | ||||
|         try: | ||||
|             headers = {} | ||||
|             # Check if the client has been sent this file before | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import sys | ||||
| import signal | ||||
| import asyncio | ||||
| import logging | ||||
|  | ||||
| try: | ||||
|     import ssl | ||||
| except ImportError: | ||||
| @@ -50,8 +51,8 @@ class GunicornWorker(base.Worker): | ||||
|             debug=is_debug, | ||||
|             protocol=protocol, | ||||
|             ssl=self.ssl_context, | ||||
|             run_async=True | ||||
|         ) | ||||
|             run_async=True) | ||||
|         self._server_settings['signal'] = self.signal | ||||
|         self._server_settings.pop('sock') | ||||
|         trigger_events(self._server_settings.get('before_start', []), | ||||
|                        self.loop) | ||||
| @@ -97,7 +98,6 @@ class GunicornWorker(base.Worker): | ||||
|             self.servers.append(await serve( | ||||
|                 sock=sock, | ||||
|                 connections=self.connections, | ||||
|                 signal=self.signal, | ||||
|                 **self._server_settings | ||||
|             )) | ||||
|  | ||||
|   | ||||
| @@ -141,6 +141,16 @@ def test_token(): | ||||
|         return text('OK') | ||||
|  | ||||
|     # uuid4 generated token. | ||||
|     token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf' | ||||
|     headers = { | ||||
|         'content-type': 'application/json', | ||||
|         'Authorization': '{}'.format(token) | ||||
|     } | ||||
|  | ||||
|     request, response = app.test_client.get('/', headers=headers) | ||||
|  | ||||
|     assert request.token == token | ||||
|  | ||||
|     token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf' | ||||
|     headers = { | ||||
|         'content-type': 'application/json', | ||||
| @@ -151,6 +161,18 @@ def test_token(): | ||||
|  | ||||
|     assert request.token == token | ||||
|  | ||||
|     token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf' | ||||
|     headers = { | ||||
|         'content-type': 'application/json', | ||||
|         'Authorization': 'Bearer Token {}'.format(token) | ||||
|     } | ||||
|  | ||||
|     request, response = app.test_client.get('/', headers=headers) | ||||
|  | ||||
|     assert request.token == token | ||||
|  | ||||
|  | ||||
|  | ||||
| # ------------------------------------------------------------ # | ||||
| #  POST | ||||
| # ------------------------------------------------------------ # | ||||
|   | ||||
| @@ -238,6 +238,30 @@ def test_dynamic_route_regex(): | ||||
|     assert response.status == 200 | ||||
|  | ||||
|  | ||||
| def test_dynamic_route_path(): | ||||
|     app = Sanic('test_dynamic_route_path') | ||||
|  | ||||
|     @app.route('/<path:path>/info') | ||||
|     async def handler(request, path): | ||||
|         return text('OK') | ||||
|  | ||||
|     request, response = app.test_client.get('/path/1/info') | ||||
|     assert response.status == 200 | ||||
|  | ||||
|     request, response = app.test_client.get('/info') | ||||
|     assert response.status == 404 | ||||
|  | ||||
|     @app.route('/<path:path>') | ||||
|     async def handler1(request, path): | ||||
|         return text('OK') | ||||
|  | ||||
|     request, response = app.test_client.get('/info') | ||||
|     assert response.status == 200 | ||||
|  | ||||
|     request, response = app.test_client.get('/whatever/you/set') | ||||
|     assert response.status == 200 | ||||
|  | ||||
|  | ||||
| def test_dynamic_route_unhashable(): | ||||
|     app = Sanic('test_dynamic_route_unhashable') | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Raphael Deem
					Raphael Deem