diff --git a/examples/aiohttp_example.py b/examples/aiohttp_example.py index a8fb20f0..853ea959 100644 --- a/examples/aiohttp_example.py +++ b/examples/aiohttp_example.py @@ -1,5 +1,5 @@ from sanic import Sanic -from sanic.response import json +from sanic import response import aiohttp @@ -9,20 +9,18 @@ async def fetch(session, url): """ Use session object to perform 'get' request on url """ - async with session.get(url) as response: - return await response.json() + async with session.get(url) as result: + return await result.json() -@app.route("/") -async def test(request): - """ - Download and serve example JSON - """ +@app.route('/') +async def handle_request(request): url = "https://api.github.com/repos/channelcat/sanic" - + async with aiohttp.ClientSession() as session: - response = await fetch(session, url) - return json(response) + result = await fetch(session, url) + 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) diff --git a/examples/exception_monitoring.py b/examples/exception_monitoring.py index 37d5c89d..76d16d90 100644 --- a/examples/exception_monitoring.py +++ b/examples/exception_monitoring.py @@ -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) \ No newline at end of file diff --git a/examples/jinja_example.py b/examples/jinja_example.py index 1f9bb1ba..ba8b3354 100644 --- a/examples/jinja_example.py +++ b/examples/jinja_example.py @@ -2,7 +2,7 @@ # curl -d '{"name": "John Doe"}' localhost:8000 from sanic import Sanic -from sanic.response import html +from sanic import response from jinja2 import Template template = Template('Hello {{ name }}!') @@ -12,7 +12,7 @@ app = Sanic(__name__) @app.route('/') async def test(request): 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) \ No newline at end of file diff --git a/examples/modify_header_example.py b/examples/modify_header_example.py new file mode 100644 index 00000000..bb5efe8e --- /dev/null +++ b/examples/modify_header_example.py @@ -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) diff --git a/examples/override_logging.py b/examples/override_logging.py index 117d63bf..e4d529e8 100644 --- a/examples/override_logging.py +++ b/examples/override_logging.py @@ -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) diff --git a/examples/plotly_example/plotlyjs_example.py b/examples/plotly_example/plotlyjs_example.py new file mode 100644 index 00000000..f536067c --- /dev/null +++ b/examples/plotly_example/plotlyjs_example.py @@ -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) \ No newline at end of file diff --git a/examples/plotly_example/requirements.txt b/examples/plotly_example/requirements.txt new file mode 100644 index 00000000..91875907 --- /dev/null +++ b/examples/plotly_example/requirements.txt @@ -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 \ No newline at end of file diff --git a/examples/plotly_example/templates/index.html b/examples/plotly_example/templates/index.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/redirect_example.py b/examples/redirect_example.py new file mode 100644 index 00000000..acc7d1ff --- /dev/null +++ b/examples/redirect_example.py @@ -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) \ No newline at end of file diff --git a/examples/request_timeout.py b/examples/request_timeout.py index 261f423a..fb2822ee 100644 --- a/examples/request_timeout.py +++ b/examples/request_timeout.py @@ -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) \ No newline at end of file diff --git a/examples/run_async.py b/examples/run_async.py index d514c7d0..b6f6d76d 100644 --- a/examples/run_async.py +++ b/examples/run_async.py @@ -1,5 +1,5 @@ from sanic import Sanic -from sanic.response import json +from sanic import response from multiprocessing import Event from signal import signal, SIGINT import asyncio @@ -9,10 +9,10 @@ app = Sanic(__name__) @app.route("/") async def test(request): - return json({"answer": "42"}) + return response.json({"answer": "42"}) 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() task = asyncio.ensure_future(server) signal(SIGINT, lambda s, f: loop.stop()) diff --git a/examples/sanic_motor.py b/examples/sanic_motor.py index 495875a4..c7d2b60f 100644 --- a/examples/sanic_motor.py +++ b/examples/sanic_motor.py @@ -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) diff --git a/examples/simple_server.py b/examples/simple_server.py index a803feb8..948090c4 100644 --- a/examples/simple_server.py +++ b/examples/simple_server.py @@ -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__': diff --git a/examples/try_everything.py b/examples/try_everything.py index da3cc515..d46b832e 100644 --- a/examples/try_everything.py +++ b/examples/try_everything.py @@ -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//") 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}) # ----------------------------------------------- # diff --git a/examples/url_for_example.py b/examples/url_for_example.py new file mode 100644 index 00000000..c26debf4 --- /dev/null +++ b/examples/url_for_example.py @@ -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/') +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) \ No newline at end of file diff --git a/examples/vhosts.py b/examples/vhosts.py index 810dc513..50c9b6f6 100644 --- a/examples/vhosts.py +++ b/examples/vhosts.py @@ -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) \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 28014eb6..163df025 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ aiofiles aiohttp==1.3.5 +chardet<=2.3.0 beautifulsoup4 coverage httptools diff --git a/sanic/__init__.py b/sanic/__init__.py index b67a3a6a..7ddeb1ea 100644 --- a/sanic/__init__.py +++ b/sanic/__init__.py @@ -1,6 +1,6 @@ from sanic.app import Sanic from sanic.blueprints import Blueprint -__version__ = '0.4.1' +__version__ = '0.5.1' __all__ = ['Sanic', 'Blueprint'] diff --git a/sanic/app.py b/sanic/app.py index ad4d7923..d2894ff2 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -26,7 +26,7 @@ from sanic.websocket import WebSocketProtocol, ConnectionClosed class Sanic: 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 # end-user application didn't set anything up. if not logging.root.handlers and log.level == logging.NOTSET: @@ -44,6 +44,7 @@ class Sanic: self.name = name self.router = router or Router() + self.request_class = request_class self.error_handler = error_handler or ErrorHandler() self.config = Config(load_env=load_env) self.request_middleware = deque() @@ -668,6 +669,7 @@ class Sanic: server_settings = { 'protocol': protocol, + 'request_class': self.request_class, 'host': host, 'port': port, 'sock': sock, diff --git a/sanic/response.py b/sanic/response.py index 4eecaf79..bebb8071 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -129,7 +129,7 @@ class StreamingHTTPResponse(BaseHTTPResponse): data = self._encode_body(data) 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( self, version="1.1", keep_alive=False, keep_alive_timeout=None): diff --git a/sanic/server.py b/sanic/server.py index 976503a6..40c3ee69 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -64,12 +64,13 @@ class HttpProtocol(asyncio.Protocol): 'parser', 'request', 'url', 'headers', # request config 'request_handler', 'request_timeout', 'request_max_size', + 'request_class', # connection management '_total_request_size', '_timeout_handler', '_last_communication_time') def __init__(self, *, loop, request_handler, error_handler, signal=Signal(), connections=set(), request_timeout=60, - request_max_size=None): + request_max_size=None, request_class=None): self.loop = loop self.transport = None self.request = None @@ -82,11 +83,16 @@ class HttpProtocol(asyncio.Protocol): self.error_handler = error_handler self.request_timeout = request_timeout self.request_max_size = request_max_size + self.request_class = request_class or Request self._total_request_size = 0 self._timeout_handler = None self._last_request_time = None self._request_handler_task = None + @property + def keep_alive(self): + return self.parser.should_keep_alive() and not self.signal.stopped + # -------------------------------------------- # # Connection # -------------------------------------------- # @@ -151,7 +157,7 @@ class HttpProtocol(asyncio.Protocol): self.headers.append((name.decode().casefold(), value.decode())) def on_headers_complete(self): - self.request = Request( + self.request = self.request_class( url_bytes=self.url, headers=CIDict(self.headers), version=self.parser.get_http_version(), @@ -180,9 +186,7 @@ class HttpProtocol(asyncio.Protocol): Writes response content synchronously to the transport. """ try: - keep_alive = ( - self.parser.should_keep_alive() and not self.signal.stopped) - + keep_alive = self.keep_alive self.transport.write( response.output( self.request.version, keep_alive, @@ -216,9 +220,7 @@ class HttpProtocol(asyncio.Protocol): """ try: - keep_alive = ( - self.parser.should_keep_alive() and not self.signal.stopped) - + keep_alive = self.keep_alive response.transport = self.transport await response.stream( 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, reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100, register_sys_signals=True, run_async=False, connections=None, - signal=Signal()): + signal=Signal(), request_class=None): """Start asynchronous HTTP Server on an individual process. :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 loop: asyncio compatible event loop :param protocol: subclass of asyncio protocol class + :param request_class: Request class to use :return: Nothing """ if not run_async: @@ -366,6 +369,7 @@ def serve(host, port, request_handler, error_handler, before_start=None, error_handler=error_handler, request_timeout=request_timeout, request_max_size=request_max_size, + request_class=request_class, ) server_coroutine = loop.create_server( diff --git a/sanic/static.py b/sanic/static.py index adbdd0ea..24fce4ff 100644 --- a/sanic/static.py +++ b/sanic/static.py @@ -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(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 diff --git a/sanic/testing.py b/sanic/testing.py index 1cad6a7b..787106a6 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -1,3 +1,5 @@ +import traceback + from sanic.log import log HOST = '127.0.0.1' @@ -50,6 +52,8 @@ class SanicTestClient: **request_kwargs) results[-1] = response except Exception as e: + log.error( + 'Exception:\n{}'.format(traceback.format_exc())) exceptions.append(e) self.app.stop() diff --git a/setup.py b/setup.py index deb52c27..aa5f2b08 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,3 @@ except DistutilsPlatformError as exception: print("Installing without uJSON or uvLoop") setup_kwargs['install_requires'] = requirements setup(**setup_kwargs) - -# Installation was successful -print(u"\n\n\U0001F680 " - "Sanic version {} installation suceeded.\n".format(version))