Compare commits

...

30 Commits
0.5.1 ... 0.5.2

Author SHA1 Message Date
Eli Uriegas
1036242064 Merge pull request #661 from seemethere/increment_052
Increment to 0.5.2
2017-04-24 13:39:39 -05:00
Eli Uriegas
5fd62098bd Increment to 0.5.2 2017-04-24 11:37:04 -07:00
Raphael Deem
b3814ca89a Merge pull request #646 from r0fls/637
allow disabling keep alive
2017-04-24 00:43:16 -07:00
Raphael Deem
b75a321e4a Merge pull request #644 from kszucs/master
Example to use dask distibuted
2017-04-22 01:14:34 -07:00
Raphael Deem
9caa4fec4a Merge pull request #656 from r0fls/token-rework
update token attribute
2017-04-21 22:44:42 -07:00
Raphael Deem
a0cba1aee1 accept token directly in auth header 2017-04-21 22:36:45 -07:00
Raphael Deem
97018ad62f Merge pull request #655 from seemethere/fix_gunicorn_worker
Fix duplicate signal settings for gunicorn worker
2017-04-21 22:16:43 -07:00
Eli Uriegas
a7d17fae44 Fix duplicate signal settings for gunicorn worker 2017-04-21 17:06:52 -05:00
Raphael Deem
6ce0050979 Merge pull request #652 from 38elements/signal-stopped
Fix `this.signal.stopped` is `True` #639
2017-04-20 13:06:28 -07:00
38elements
bc035fca78 Remove unnecessary variables 2017-04-20 18:27:28 +09:00
38elements
df914a92e4 Fix this.signal.stopped is True #639 2017-04-19 11:19:01 +09:00
Szucs Krisztian
1b939a6823 work with distributed 1.16.1 2017-04-17 11:05:19 +02:00
Raphael Deem
81b6d988ec NO_KEEP_ALIVE -> KEEP_ALIVE 2017-04-16 22:43:49 -07:00
Raphael Deem
7e9b65feca Merge pull request #647 from r0fls/645
use absolute path in static root
2017-04-16 22:06:40 -07:00
Raphael Deem
6f098b3d21 add no_keep_alive setting to docs 2017-04-16 22:05:34 -07:00
Raphael Deem
5ddb0488f2 allow disabling keep alive 2017-04-16 22:03:20 -07:00
Raphael Deem
3e87314adf use absolute path in static root 2017-04-16 21:58:10 -07:00
Raphael Deem
f6d4a06661 Merge pull request #643 from aryeh/allow_unknown_status_codes
Allow unknown status codes
2017-04-16 18:42:59 -07:00
Raphael Deem
ff17fc95e6 Merge pull request #632 from messense/feature/path-route
Add path type for router
2017-04-16 18:39:10 -07:00
Eli Uriegas
c5a46f1cea Merge pull request #638 from seemethere/add_contributing_rules
Add new contributing rules
2017-04-16 13:15:01 -05:00
Eli Uriegas
0b072189c4 Merge pull request #641 from TomIsPrettyCool/master
Use render_async and a template env with the Jinja2 example.
2017-04-16 13:14:35 -05:00
Szucs Krisztian
5b22d1486a fix syntax error in comment 2017-04-16 18:13:00 +02:00
Szucs Krisztian
9eb48c2b0d dask distributed example 2017-04-16 18:11:24 +02:00
aryeh
ff0632001c prevent crash for unknown response codes
set text for unknown status code, otherwise when None is used exception occurs.
2017-04-16 10:07:29 -04:00
aryeh
28bd09a2ea Merge pull request #1 from channelcat/master
pull master
2017-04-16 09:48:59 -04:00
Tom Haines
c6aaa9b09c Use render_async and a template env with jinja2 2017-04-16 13:50:07 +01:00
Eli Uriegas
20d9ec1fd2 Fix the numbered list 2017-04-14 14:38:45 -05:00
Eli Uriegas
2c45c2d3c0 Add new contributing rules 2017-04-14 14:35:28 -05:00
messense
4c66cb1854 Fix static files router 2017-04-13 12:11:38 +08:00
messense
35b92e1511 Add path type for router 2017-04-13 11:34:35 +08:00
16 changed files with 229 additions and 30 deletions

62
CONTRIBUTING.md Normal file
View 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.

View File

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

View File

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

View 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)

View File

@@ -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 import response
from jinja2 import Template
template = Template('Hello {{ name }}!')
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 response.html(template.render(**data))
rendered_template = await template.render_async(**data)
return response.html(rendered_template)
app.run(host="0.0.0.0", port=8080, debug=True)

View File

@@ -1,6 +1,6 @@
from sanic.app import Sanic
from sanic.blueprints import Blueprint
__version__ = '0.5.1'
__version__ = '0.5.2'
__all__ = ['Sanic', 'Blueprint']

View File

@@ -16,7 +16,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
@@ -288,7 +288,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
@@ -674,11 +674,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

View File

@@ -6,7 +6,7 @@ SANIC_PREFIX = 'SANIC_'
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 = """
▄▄▄▄▄
@@ -31,6 +31,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()
@@ -98,11 +99,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

View File

@@ -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):

View File

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

View File

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

View File

@@ -70,7 +70,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):
request_max_size=None, request_class=None,
keep_alive=True):
self.loop = loop
self.transport = None
self.request = None
@@ -88,10 +89,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
@@ -322,7 +326,7 @@ 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):
signal=Signal(), request_class=None, keep_alive=True):
"""Start asynchronous HTTP Server on an individual process.
:param host: Address to host on
@@ -370,6 +374,7 @@ 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,
keep_alive=keep_alive,
)
server_coroutine = loop.create_server(

View File

@@ -56,7 +56,7 @@ def register(app, uri, file_or_directory, pattern,
# 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 = path.abspath(unquote(file_path))
if not file_path.startswith(root_path):
if not file_path.startswith(path.abspath(unquote(root_path))):
raise FileNotFound('File not found',
path=file_or_directory,
relative_url=file_uri)

View File

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

View File

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

View File

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