Merge pull request #1 from channelcat/master

pull master
This commit is contained in:
aryeh 2017-04-16 09:48:59 -04:00 committed by GitHub
commit 28bd09a2ea
24 changed files with 229 additions and 72 deletions

View File

@ -1,5 +1,5 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic import response
import aiohttp import aiohttp
@ -9,20 +9,18 @@ async def fetch(session, url):
""" """
Use session object to perform 'get' request on url Use session object to perform 'get' request on url
""" """
async with session.get(url) as response: async with session.get(url) as result:
return await response.json() return await result.json()
@app.route("/") @app.route('/')
async def test(request): async def handle_request(request):
"""
Download and serve example JSON
"""
url = "https://api.github.com/repos/channelcat/sanic" url = "https://api.github.com/repos/channelcat/sanic"
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
response = await fetch(session, url) result = await fetch(session, url)
return json(response) return response.json(result)
app.run(host="0.0.0.0", port=8000, workers=2) if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000, workers=2)

View File

@ -1,9 +1,7 @@
""" """
Example intercepting uncaught exceptions using Sanic's error handler framework. Example intercepting uncaught exceptions using Sanic's error handler framework.
This may be useful for developers wishing to use Sentry, Airbrake, etc. This may be useful for developers wishing to use Sentry, Airbrake, etc.
or a custom system to log and monitor unexpected errors in production. or a custom system to log and monitor unexpected errors in production.
First we create our own class inheriting from Handler in sanic.exceptions, 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 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 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 import Sanic
from sanic.response import json from sanic import response
app = Sanic(__name__) app = Sanic(__name__)
@ -52,7 +50,7 @@ async def test(request):
# Here, something occurs which causes an unexpected exception # Here, something occurs which causes an unexpected exception
# This exception will flow to our custom handler. # This exception will flow to our custom handler.
1 / 0 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)

View File

@ -2,7 +2,7 @@
# curl -d '{"name": "John Doe"}' localhost:8000 # curl -d '{"name": "John Doe"}' localhost:8000
from sanic import Sanic from sanic import Sanic
from sanic.response import html from sanic import response
from jinja2 import Template from jinja2 import Template
template = Template('Hello {{ name }}!') template = Template('Hello {{ name }}!')
@ -12,7 +12,7 @@ app = Sanic(__name__)
@app.route('/') @app.route('/')
async def test(request): async def test(request):
data = request.json data = request.json
return html(template.render(**data)) return response.html(template.render(**data))
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8080, debug=True)

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

View File

@ -1,6 +1,5 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import text from sanic import response
import json
import logging import logging
logging_format = "[%(asctime)s] %(process)d-%(levelname)s " logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
@ -18,6 +17,6 @@ sanic = Sanic()
@sanic.route("/") @sanic.route("/")
def test(request): def test(request):
log.info("received request; responding with 'hey'") log.info("received request; responding with 'hey'")
return text("hey") return response.text("hey")
sanic.run(host="0.0.0.0", port=8000) sanic.run(host="0.0.0.0", port=8000)

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

View 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

View File

@ -0,0 +1,17 @@
from sanic import Sanic
from sanic import response
app = Sanic(__name__)
@app.route('/')
def handle_request(request):
return response.redirect('/redirect')
@app.route('/redirect')
async def test(request):
return response.json({"Redirected": True})
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

View File

@ -1,6 +1,6 @@
from sanic import Sanic
import asyncio import asyncio
from sanic.response import text from sanic import Sanic
from sanic import response
from sanic.config import Config from sanic.config import Config
from sanic.exceptions import RequestTimeout from sanic.exceptions import RequestTimeout
@ -11,11 +11,11 @@ app = Sanic(__name__)
@app.route('/') @app.route('/')
async def test(request): async def test(request):
await asyncio.sleep(3) await asyncio.sleep(3)
return text('Hello, world!') return response.text('Hello, world!')
@app.exception(RequestTimeout) @app.exception(RequestTimeout)
def timeout(request, exception): 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)

View File

@ -1,5 +1,5 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic import response
from multiprocessing import Event from multiprocessing import Event
from signal import signal, SIGINT from signal import signal, SIGINT
import asyncio import asyncio
@ -9,10 +9,10 @@ app = Sanic(__name__)
@app.route("/") @app.route("/")
async def test(request): async def test(request):
return json({"answer": "42"}) return response.json({"answer": "42"})
asyncio.set_event_loop(uvloop.new_event_loop()) asyncio.set_event_loop(uvloop.new_event_loop())
server = app.create_server(host="0.0.0.0", port=8001) server = app.create_server(host="0.0.0.0", port=8000)
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
task = asyncio.ensure_future(server) task = asyncio.ensure_future(server)
signal(SIGINT, lambda s, f: loop.stop()) signal(SIGINT, lambda s, f: loop.stop())

View File

@ -5,7 +5,7 @@ motor==1.1
sanic==0.2.0 sanic==0.2.0
""" """
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic import response
app = Sanic('motor_mongodb') app = Sanic('motor_mongodb')
@ -25,7 +25,7 @@ async def get(request):
for doc in docs: for doc in docs:
doc['id'] = str(doc['_id']) doc['id'] = str(doc['_id'])
del doc['_id'] del doc['_id']
return json(docs) return response.json(docs)
@app.route('/post', methods=['POST']) @app.route('/post', methods=['POST'])
@ -34,8 +34,8 @@ async def new(request):
print(doc) print(doc)
db = get_db() db = get_db()
object_id = await db.test_col.save(doc) 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__": if __name__ == "__main__":
app.run(host='127.0.0.1', port=8000) app.run(host='0.0.0.0', port=8000, debug=True)

View File

@ -1,12 +1,12 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic import response
app = Sanic(__name__) app = Sanic(__name__)
@app.route("/") @app.route("/")
async def test(request): async def test(request):
return json({"test": True}) return response.json({"test": True})
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,7 +2,7 @@ import os
from sanic import Sanic from sanic import Sanic
from sanic.log import log from sanic.log import log
from sanic.response import json, text, file from sanic import response
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
app = Sanic(__name__) app = Sanic(__name__)
@ -10,17 +10,17 @@ app = Sanic(__name__)
@app.route("/") @app.route("/")
async def test_async(request): async def test_async(request):
return json({"test": True}) return response.json({"test": True})
@app.route("/sync", methods=['GET', 'POST']) @app.route("/sync", methods=['GET', 'POST'])
def test_sync(request): def test_sync(request):
return json({"test": True}) return response.json({"test": True})
@app.route("/dynamic/<name>/<id:int>") @app.route("/dynamic/<name>/<id:int>")
def test_params(request, name, id): def test_params(request, name, id):
return text("yeehaww {} {}".format(name, id)) return response.text("yeehaww {} {}".format(name, id))
@app.route("/exception") @app.route("/exception")
@ -31,11 +31,11 @@ def exception(request):
async def test_await(request): async def test_await(request):
import asyncio import asyncio
await asyncio.sleep(5) await asyncio.sleep(5)
return text("I'm feeling sleepy") return response.text("I'm feeling sleepy")
@app.route("/file") @app.route("/file")
async def test_file(request): 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) @app.exception(ServerError)
async def test(request, exception): 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") @app.route("/json")
def post_json(request): def post_json(request):
return json({"received": True, "message": request.json}) return response.json({"received": True, "message": request.json})
@app.route("/form") @app.route("/form")
def post_json(request): 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") @app.route("/query_string")
def query_string(request): 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})
# ----------------------------------------------- # # ----------------------------------------------- #

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

View File

@ -1,4 +1,4 @@
from sanic.response import text from sanic import response
from sanic import Sanic from sanic import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
@ -15,25 +15,25 @@ bp = Blueprint("bp", host="bp.example.com")
"somethingelse.com", "somethingelse.com",
"therestofyourdomains.com"]) "therestofyourdomains.com"])
async def hello(request): async def hello(request):
return text("Some defaults") return response.text("Some defaults")
@app.route('/', host="example.com") @app.route('/', host="example.com")
async def hello(request): async def hello(request):
return text("Answer") return response.text("Answer")
@app.route('/', host="sub.example.com") @app.route('/', host="sub.example.com")
async def hello(request): async def hello(request):
return text("42") return response.text("42")
@bp.route("/question") @bp.route("/question")
async def hello(request): async def hello(request):
return text("What is the meaning of life?") return response.text("What is the meaning of life?")
@bp.route("/answer") @bp.route("/answer")
async def hello(request): async def hello(request):
return text("42") return response.text("42")
app.register_blueprint(bp) app.register_blueprint(bp)
if __name__ == '__main__': if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -1,5 +1,6 @@
aiofiles aiofiles
aiohttp==1.3.5 aiohttp==1.3.5
chardet<=2.3.0
beautifulsoup4 beautifulsoup4
coverage coverage
httptools httptools

View File

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

View File

@ -26,7 +26,7 @@ from sanic.websocket import WebSocketProtocol, ConnectionClosed
class Sanic: class Sanic:
def __init__(self, name=None, router=None, error_handler=None, def __init__(self, name=None, router=None, error_handler=None,
load_env=True): load_env=True, request_class=None):
# Only set up a default log handler if the # Only set up a default log handler if the
# end-user application didn't set anything up. # end-user application didn't set anything up.
if not logging.root.handlers and log.level == logging.NOTSET: if not logging.root.handlers and log.level == logging.NOTSET:
@ -44,6 +44,7 @@ class Sanic:
self.name = name self.name = name
self.router = router or Router() self.router = router or Router()
self.request_class = request_class
self.error_handler = error_handler or ErrorHandler() self.error_handler = error_handler or ErrorHandler()
self.config = Config(load_env=load_env) self.config = Config(load_env=load_env)
self.request_middleware = deque() self.request_middleware = deque()
@ -668,6 +669,7 @@ class Sanic:
server_settings = { server_settings = {
'protocol': protocol, 'protocol': protocol,
'request_class': self.request_class,
'host': host, 'host': host,
'port': port, 'port': port,
'sock': sock, 'sock': sock,

View File

@ -129,7 +129,7 @@ class StreamingHTTPResponse(BaseHTTPResponse):
data = self._encode_body(data) data = self._encode_body(data)
self.transport.write( self.transport.write(
b"%b\r\n%b\r\n" % (str(len(data)).encode(), data)) b"%x\r\n%b\r\n" % (len(data), data))
async def stream( async def stream(
self, version="1.1", keep_alive=False, keep_alive_timeout=None): self, version="1.1", keep_alive=False, keep_alive_timeout=None):

View File

@ -64,12 +64,13 @@ class HttpProtocol(asyncio.Protocol):
'parser', 'request', 'url', 'headers', 'parser', 'request', 'url', 'headers',
# request config # request config
'request_handler', 'request_timeout', 'request_max_size', 'request_handler', 'request_timeout', 'request_max_size',
'request_class',
# connection management # connection management
'_total_request_size', '_timeout_handler', '_last_communication_time') '_total_request_size', '_timeout_handler', '_last_communication_time')
def __init__(self, *, loop, request_handler, error_handler, def __init__(self, *, loop, request_handler, error_handler,
signal=Signal(), connections=set(), request_timeout=60, signal=Signal(), connections=set(), request_timeout=60,
request_max_size=None): request_max_size=None, request_class=None):
self.loop = loop self.loop = loop
self.transport = None self.transport = None
self.request = None self.request = None
@ -82,11 +83,16 @@ class HttpProtocol(asyncio.Protocol):
self.error_handler = error_handler self.error_handler = error_handler
self.request_timeout = request_timeout self.request_timeout = request_timeout
self.request_max_size = request_max_size self.request_max_size = request_max_size
self.request_class = request_class or Request
self._total_request_size = 0 self._total_request_size = 0
self._timeout_handler = None self._timeout_handler = None
self._last_request_time = None self._last_request_time = None
self._request_handler_task = None self._request_handler_task = None
@property
def keep_alive(self):
return self.parser.should_keep_alive() and not self.signal.stopped
# -------------------------------------------- # # -------------------------------------------- #
# Connection # Connection
# -------------------------------------------- # # -------------------------------------------- #
@ -151,7 +157,7 @@ class HttpProtocol(asyncio.Protocol):
self.headers.append((name.decode().casefold(), value.decode())) self.headers.append((name.decode().casefold(), value.decode()))
def on_headers_complete(self): def on_headers_complete(self):
self.request = Request( self.request = self.request_class(
url_bytes=self.url, url_bytes=self.url,
headers=CIDict(self.headers), headers=CIDict(self.headers),
version=self.parser.get_http_version(), version=self.parser.get_http_version(),
@ -180,9 +186,7 @@ class HttpProtocol(asyncio.Protocol):
Writes response content synchronously to the transport. Writes response content synchronously to the transport.
""" """
try: try:
keep_alive = ( keep_alive = self.keep_alive
self.parser.should_keep_alive() and not self.signal.stopped)
self.transport.write( self.transport.write(
response.output( response.output(
self.request.version, keep_alive, self.request.version, keep_alive,
@ -216,9 +220,7 @@ class HttpProtocol(asyncio.Protocol):
""" """
try: try:
keep_alive = ( keep_alive = self.keep_alive
self.parser.should_keep_alive() and not self.signal.stopped)
response.transport = self.transport response.transport = self.transport
await response.stream( await response.stream(
self.request.version, keep_alive, self.request_timeout) self.request.version, keep_alive, self.request_timeout)
@ -320,7 +322,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
request_timeout=60, ssl=None, sock=None, request_max_size=None, request_timeout=60, ssl=None, sock=None, request_max_size=None,
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100, reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100,
register_sys_signals=True, run_async=False, connections=None, register_sys_signals=True, run_async=False, connections=None,
signal=Signal()): signal=Signal(), request_class=None):
"""Start asynchronous HTTP Server on an individual process. """Start asynchronous HTTP Server on an individual process.
:param host: Address to host on :param host: Address to host on
@ -345,6 +347,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
:param reuse_port: `True` for multiple workers :param reuse_port: `True` for multiple workers
:param loop: asyncio compatible event loop :param loop: asyncio compatible event loop
:param protocol: subclass of asyncio protocol class :param protocol: subclass of asyncio protocol class
:param request_class: Request class to use
:return: Nothing :return: Nothing
""" """
if not run_async: if not run_async:
@ -366,6 +369,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
error_handler=error_handler, error_handler=error_handler,
request_timeout=request_timeout, request_timeout=request_timeout,
request_max_size=request_max_size, request_max_size=request_max_size,
request_class=request_class,
) )
server_coroutine = loop.create_server( server_coroutine = loop.create_server(

View File

@ -48,14 +48,18 @@ def register(app, uri, file_or_directory, pattern,
# Merge served directory and requested file if provided # Merge served directory and requested file if provided
# Strip all / that in the beginning of the URL to help prevent python # 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 # 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: if file_uri:
file_path = path.join( file_path = path.join(
file_or_directory, sub('^[/]*', '', file_uri)) file_or_directory, sub('^[/]*', '', file_uri))
# URL decode the path sent by the browser otherwise we won't be able to # URL decode the path sent by the browser otherwise we won't be able to
# match filenames which got encoded (filenames with spaces etc) # 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(root_path):
raise FileNotFound('File not found',
path=file_or_directory,
relative_url=file_uri)
try: try:
headers = {} headers = {}
# Check if the client has been sent this file before # Check if the client has been sent this file before

View File

@ -1,3 +1,5 @@
import traceback
from sanic.log import log from sanic.log import log
HOST = '127.0.0.1' HOST = '127.0.0.1'
@ -50,6 +52,8 @@ class SanicTestClient:
**request_kwargs) **request_kwargs)
results[-1] = response results[-1] = response
except Exception as e: except Exception as e:
log.error(
'Exception:\n{}'.format(traceback.format_exc()))
exceptions.append(e) exceptions.append(e)
self.app.stop() self.app.stop()

View File

@ -64,7 +64,3 @@ except DistutilsPlatformError as exception:
print("Installing without uJSON or uvLoop") print("Installing without uJSON or uvLoop")
setup_kwargs['install_requires'] = requirements setup_kwargs['install_requires'] = requirements
setup(**setup_kwargs) setup(**setup_kwargs)
# Installation was successful
print(u"\n\n\U0001F680 "
"Sanic version {} installation suceeded.\n".format(version))