diff --git a/.gitignore b/.gitignore index 7fb5634f..89edd032 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ settings.py .idea/* .cache/* .python-version +docs/_build/ +docs/_api/ \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index d54e2598..00000000 --- a/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# Sanic - -[![Join the chat at https://gitter.im/sanic-python/Lobby](https://badges.gitter.im/sanic-python/Lobby.svg)](https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -[![Build Status](https://travis-ci.org/channelcat/sanic.svg?branch=master)](https://travis-ci.org/channelcat/sanic) -[![PyPI](https://img.shields.io/pypi/v/sanic.svg)](https://pypi.python.org/pypi/sanic/) -[![PyPI](https://img.shields.io/pypi/pyversions/sanic.svg)](https://pypi.python.org/pypi/sanic/) - -Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by this article: https://magic.io/blog/uvloop-blazing-fast-python-networking/. - -On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy. - -## Benchmarks - -All tests were run on an AWS medium instance running ubuntu, using 1 process. Each script delivered a small JSON response and was tested with wrk using 100 connections. Pypy was tested for Falcon and Flask but did not speed up requests. - - - -| Server | Implementation | Requests/sec | Avg Latency | -| ------- | ------------------- | ------------:| -----------:| -| Sanic | Python 3.5 + uvloop | 33,342 | 2.96ms | -| Wheezy | gunicorn + meinheld | 20,244 | 4.94ms | -| Falcon | gunicorn + meinheld | 18,972 | 5.27ms | -| Bottle | gunicorn + meinheld | 13,596 | 7.36ms | -| Flask | gunicorn + meinheld | 4,988 | 20.08ms | -| Kyoukai | Python 3.5 + uvloop | 3,889 | 27.44ms | -| Aiohttp | Python 3.5 + uvloop | 2,979 | 33.42ms | -| Tornado | Python 3.5 | 2,138 | 46.66ms | - -## Hello World - -```python -from sanic import Sanic -from sanic.response import json - - -app = Sanic() - - -@app.route("/") -async def test(request): - return json({"hello": "world"}) - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=8000) - -``` - -## Installation - * `python -m pip install sanic` - -## Use SSL - * Optionally pass in an SSLContext: -``` -import ssl -certificate = "/path/to/certificate" -keyfile = "/path/to/keyfile" -context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) -context.load_cert_chain(certificate, keyfile=keyfile) - -app.run(host="0.0.0.0", port=8443, ssl=context) -``` - -## Documentation - * [Getting started](docs/getting_started.md) - * [Request Data](docs/request_data.md) - * [Routing](docs/routing.md) - * [Middleware](docs/middleware.md) - * [Exceptions](docs/exceptions.md) - * [Blueprints](docs/blueprints.md) - * [Class Based Views](docs/class_based_views.md) - * [Cookies](docs/cookies.md) - * [Static Files](docs/static_files.md) - * [Custom Protocol](docs/custom_protocol.md) - * [Testing](docs/testing.md) - * [Deploying](docs/deploying.md) - * [Extensions](docs/extensions.md) - * [Contributing](docs/contributing.md) - * [License](LICENSE) - -## TODO: - * Streamed file processing - * File output - * Examples of integrations with 3rd-party modules - * RESTful router - -## Limitations: - * No wheels for uvloop and httptools on Windows :( - -## Final Thoughts: - - ▄▄▄▄▄ - ▀▀▀██████▄▄▄ _______________ - ▄▄▄▄▄ █████████▄ / \ - ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! | - ▀▀█████▄▄ ▀██████▄██ | _________________/ - ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/ - ▀▀▀▄ ▀▀███ ▀ ▄▄ - ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ - ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ - ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀ - ▌ ▐▀████▐███▒▒▒▒▒▐██▌ - ▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀ - ▀▀█████████▀ - ▄▄██▀██████▀█ - ▄██▀ ▀▀▀ █ - ▄█ ▐▌ - ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄ - ▌ ▐ ▀▀▄▄▄▀ - ▀▀▄▄▀ diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..ab25debf --- /dev/null +++ b/README.rst @@ -0,0 +1,127 @@ +Sanic +================================= + +|Join the chat at https://gitter.im/sanic-python/Lobby| |Build Status| |PyPI| |PyPI version| + +Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by `this article `_. + +On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy. + +Sanic is developed `on GitHub `_. Contributions are welcome! + +Benchmarks +---------- + +All tests were run on an AWS medium instance running ubuntu, using 1 +process. Each script delivered a small JSON response and was tested with +wrk using 100 connections. Pypy was tested for Falcon and Flask but did +not speed up requests. + ++-----------+-----------------------+----------------+---------------+ +| Server | Implementation | Requests/sec | Avg Latency | ++===========+=======================+================+===============+ +| Sanic | Python 3.5 + uvloop | 33,342 | 2.96ms | ++-----------+-----------------------+----------------+---------------+ +| Wheezy | gunicorn + meinheld | 20,244 | 4.94ms | ++-----------+-----------------------+----------------+---------------+ +| Falcon | gunicorn + meinheld | 18,972 | 5.27ms | ++-----------+-----------------------+----------------+---------------+ +| Bottle | gunicorn + meinheld | 13,596 | 7.36ms | ++-----------+-----------------------+----------------+---------------+ +| Flask | gunicorn + meinheld | 4,988 | 20.08ms | ++-----------+-----------------------+----------------+---------------+ +| Kyoukai | Python 3.5 + uvloop | 3,889 | 27.44ms | ++-----------+-----------------------+----------------+---------------+ +| Aiohttp | Python 3.5 + uvloop | 2,979 | 33.42ms | ++-----------+-----------------------+----------------+---------------+ +| Tornado | Python 3.5 | 2,138 | 46.66ms | ++-----------+-----------------------+----------------+---------------+ + +Hello World Example +------------------- + +.. code:: python + + from sanic import Sanic + from sanic.response import json + + + app = Sanic() + + + @app.route("/") + async def test(request): + return json({"hello": "world"}) + + if __name__ == "__main__": + app.run(host="0.0.0.0", port=8000) + +SSL Example +----------- + +Optionally pass in an SSLContext: + +.. code:: python + + import ssl + certificate = "/path/to/certificate" + keyfile = "/path/to/keyfile" + context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain(certificate, keyfile=keyfile) + + app.run(host="0.0.0.0", port=8443, ssl=context) + +Installation +------------ + +- ``python -m pip install sanic`` + +Documentation +------------- + +Documentation can be found in the ``docs`` directory. + +.. |Join the chat at https://gitter.im/sanic-python/Lobby| image:: https://badges.gitter.im/sanic-python/Lobby.svg + :target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +.. |Build Status| image:: https://travis-ci.org/channelcat/sanic.svg?branch=master + :target: https://travis-ci.org/channelcat/sanic +.. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg + :target: https://pypi.python.org/pypi/sanic/ +.. |PyPI version| image:: https://img.shields.io/pypi/pyversions/sanic.svg + :target: https://pypi.python.org/pypi/sanic/ + +TODO +---- +* Streamed file processing +* File output +* Examples of integrations with 3rd-party modules +* RESTful router + +Limitations +----------- +* No wheels for uvloop and httptools on Windows :( + +Final Thoughts +-------------- + +:: + + ▄▄▄▄▄ + ▀▀▀██████▄▄▄ _______________ + ▄▄▄▄▄ █████████▄ / \ + ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! | + ▀▀█████▄▄ ▀██████▄██ | _________________/ + ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/ + ▀▀▀▄ ▀▀███ ▀ ▄▄ + ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ + ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ + ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀ + ▌ ▐▀████▐███▒▒▒▒▒▐██▌ + ▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀ + ▀▀█████████▀ + ▄▄██▀██████▀█ + ▄██▀ ▀▀▀ █ + ▄█ ▐▌ + ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄ + ▌ ▐ ▀▀▄▄▄▀ + ▀▀▄▄▀ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..caf79901 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Sanic documentation build configuration file, created by +# sphinx-quickstart on Sun Dec 25 18:07:21 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. + +import os +import sys + +# Add support for Markdown documentation using Recommonmark +from recommonmark.parser import CommonMarkParser + +# Ensure that sanic is present in the path, to allow sphinx-apidoc to +# autogenerate documentation from docstrings +root_directory = os.path.dirname(os.getcwd()) +sys.path.insert(0, root_directory) + +import sanic + +# -- General configuration ------------------------------------------------ + +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages'] + +templates_path = ['_templates'] + +# Enable support for both Restructured Text and Markdown +source_parsers = {'.md': CommonMarkParser} +source_suffix = ['.rst', '.md'] + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Sanic' +copyright = '2016, Sanic contributors' +author = 'Sanic contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = sanic.__version__ +# The full version, including alpha/beta/rc tags. +release = sanic.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +# +# modules.rst is generated by sphinx-apidoc but is unused. This suppresses +# a warning about it. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'modules.rst'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Sanicdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Sanic.tex', 'Sanic Documentation', + 'Sanic contributors', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'sanic', 'Sanic Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Sanic', 'Sanic Documentation', + author, 'Sanic', 'One line description of project.', + 'Miscellaneous'), +] + + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + diff --git a/docs/contributing.md b/docs/contributing.md index e39a7247..667978ca 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -6,5 +6,18 @@ Thank you for your interest! * `python -m pip install pytest` * `python -m pytest tests` +## 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. \ No newline at end of file +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. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..202a6d50 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,33 @@ +.. include:: ../README.rst + +Guides +====== + +.. toctree:: + :maxdepth: 2 + + getting_started + request_data + routing + middleware + exceptions + blueprints + class_based_views + cookies + static_files + custom_protocol + testing + deploying + extensions + contributing + + +Module Documentation +==================== + +.. toctree:: + + Module Reference <_api/sanic> + +* :ref:`genindex` +* :ref:`search` diff --git a/requirements-dev.txt b/requirements-dev.txt index 8e0ffac1..b41bb274 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,4 +12,6 @@ kyoukai falcon tornado aiofiles +sphinx +recommonmark beautifulsoup4 diff --git a/sanic/exceptions.py b/sanic/exceptions.py index f1d81878..1e8ec639 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -165,6 +165,7 @@ class Handler: def response(self, request, exception): """ Fetches and executes an exception handler and returns a response object + :param request: Request :param exception: Exception to handle :return: Response object diff --git a/sanic/request.py b/sanic/request.py index 0cd7c738..b7a95bc4 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -154,6 +154,7 @@ File = namedtuple('File', ['type', 'body', 'name']) def parse_multipart_form(body, boundary): """ Parses a request body and returns fields and files + :param body: Bytes request body :param boundary: Bytes multipart boundary :return: fields (RequestParameters), files (RequestParameters) diff --git a/sanic/router.py b/sanic/router.py index e817be9c..ec67f690 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -31,12 +31,20 @@ class RouteDoesNotExist(Exception): class Router: """ Router supports basic routing with parameters and method checks + Usage: - @app.route('/my_url/', methods=['GET', 'POST', ...]) + + .. code-block:: python + + @sanic.route('/my/url/', methods=['GET', 'POST', ...]) def my_route(request, my_param): do stuff... + or - @app.route('/my_url/', methods=['GET', 'POST', ...]) + + .. code-block:: python + + @sanic.route('/my/url/', methods['GET', 'POST', ...]) def my_route_with_type(request, my_param: my_type): do stuff... @@ -61,11 +69,12 @@ class Router: def add(self, uri, methods, handler, host=None): """ Adds a handler to the route list + :param uri: Path to match :param methods: Array of accepted method names. - If none are provided, any method is allowed + If none are provided, any method is allowed :param handler: Request handler function. - When executed, it should provide a response object. + When executed, it should provide a response object. :return: Nothing """ @@ -179,6 +188,7 @@ class Router: """ Gets a request handler based on the URL of the request, or raises an error + :param request: Request object :return: handler, arguments, keyword arguments """ diff --git a/sanic/sanic.py b/sanic/sanic.py index 5f8bf650..666365b6 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -49,6 +49,7 @@ class Sanic: def route(self, uri, methods=None, host=None): """ Decorates a function to be registered as a route + :param uri: path of the URL :param methods: list or tuple of methods allowed :return: decorated function @@ -71,6 +72,7 @@ class Sanic: A helper method to register class instance or functions as a handler to the application url routes. + :param handler: function or class instance :param uri: path of the URL :param methods: list or tuple of methods allowed @@ -86,7 +88,8 @@ class Sanic: def exception(self, *exceptions): """ Decorates a function to be registered as a handler for exceptions - :param *exceptions: exceptions + + :param \*exceptions: exceptions :return: decorated function """ @@ -132,6 +135,7 @@ class Sanic: def blueprint(self, blueprint, **options): """ Registers a blueprint on the application. + :param blueprint: Blueprint object :param options: option dictionary with blueprint defaults :return: Nothing @@ -165,9 +169,10 @@ class Sanic: Takes a request from the HTTP Server and returns a response object to be sent back The HTTP Server only expects a response object, so exception handling must be done here + :param request: HTTP Request object :param response_callback: Response function to be called with the - response as the only argument + response as the only argument :return: Nothing """ try: @@ -248,21 +253,22 @@ class Sanic: """ Runs the HTTP Server and listens until keyboard interrupt or term signal. On termination, drains connections before closing. + :param host: Address to host on :param port: Port to host on :param debug: Enables debug output (slows server) :param before_start: Functions to be executed before the server starts - accepting connections + accepting connections :param after_start: Functions to be executed after the server starts - accepting connections + accepting connections :param before_stop: Functions to be executed when a stop signal is - received before it is respected + received before it is respected :param after_stop: Functions to be executed when all requests are - complete + complete :param ssl: SSLContext for SSL encryption of worker(s) :param sock: Socket for the server to accept connections from :param workers: Number of processes - received before it is respected + received before it is respected :param loop: asyncio compatible event loop :param protocol: Subclass of asyncio protocol class :return: Nothing @@ -352,6 +358,7 @@ class Sanic: """ Starts multiple server processes simultaneously. Stops on interrupt and terminate signals, and drains connections when complete. + :param server_settings: kw arguments to be passed to the serve function :param workers: number of workers to launch :param stop_event: if provided, is used as a stop signal diff --git a/sanic/server.py b/sanic/server.py index 8285cfe0..f796c102 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -223,6 +223,7 @@ def update_current_time(loop): """ Caches the current time, since it is needed at the end of every keep-alive request to update the request timeout time + :param loop: :return: """ @@ -251,18 +252,21 @@ def serve(host, port, request_handler, error_handler, before_start=None, reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100): """ Starts asynchronous HTTP Server on an individual process. + :param host: Address to host on :param port: Port to host on :param request_handler: Sanic request handler with middleware :param error_handler: Sanic error handler with middleware :param before_start: Function to be executed before the server starts - listening. Takes single argument `loop` + listening. Takes single argument `loop` :param after_start: Function to be executed after the server starts - listening. Takes single argument `loop` + listening. Takes single argument `loop` :param before_stop: Function to be executed when a stop signal is - received before it is respected. Takes single argumenet `loop` + received before it is respected. Takes single + argument `loop` :param after_stop: Function to be executed when a stop signal is - received after it is respected. Takes single argumenet `loop` + received after it is respected. Takes single + argument `loop` :param debug: Enables debug output (slows server) :param request_timeout: time in seconds :param ssl: SSLContext diff --git a/sanic/static.py b/sanic/static.py index 9f5f2d52..1d0bff0f 100644 --- a/sanic/static.py +++ b/sanic/static.py @@ -15,12 +15,14 @@ def register(app, uri, file_or_directory, pattern, use_modified_since): """ Registers a static directory handler with Sanic by adding a route to the router and registering a handler. + :param app: Sanic :param file_or_directory: File or directory path to serve from :param uri: URL to serve from :param pattern: regular expression used to match files in the URL :param use_modified_since: If true, send file modified time, and return - not modified if the browser's matches the server's + not modified if the browser's matches the + server's """ # If we're not trying to match a file directly, diff --git a/sanic/views.py b/sanic/views.py index 640165fe..407ba136 100644 --- a/sanic/views.py +++ b/sanic/views.py @@ -7,21 +7,25 @@ class HTTPMethodView: to every HTTP method you want to support. For example: - class DummyView(HTTPMethodView): + .. code-block:: python + + class DummyView(HTTPMethodView): def get(self, request, *args, **kwargs): return text('I am get method') - def put(self, request, *args, **kwargs): return text('I am put method') + etc. If someone tries to use a non-implemented method, there will be a 405 response. If you need any url params just mention them in method definition: - class DummyView(HTTPMethodView): + .. code-block:: python + + class DummyView(HTTPMethodView): def get(self, request, my_param_here, *args, **kwargs): return text('I am get method with %s' % my_param_here)