diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docs/index.rst b/docs/index.rst index aaa296ee..7d440e27 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,8 +21,11 @@ Guides sanic/streaming sanic/class_based_views sanic/custom_protocol + sanic/sockets sanic/ssl sanic/logging + sanic/versioning + sanic/debug_mode sanic/testing sanic/deploying sanic/extensions diff --git a/docs/sanic/blueprints.md b/docs/sanic/blueprints.md index 53aef5fd..9a47609c 100644 --- a/docs/sanic/blueprints.md +++ b/docs/sanic/blueprints.md @@ -48,7 +48,7 @@ by that blueprint. In this example, the registered routes in the `app.router` will look like: ```python -[Route(handler=, methods=None, pattern=re.compile('^/$'), parameters=[])] +[Route(handler=, methods=frozenset({'GET'}), pattern=re.compile('^/$'), parameters=[], name='my_blueprint.bp_root', uri='/')] ``` ## Blueprint groups and nesting @@ -87,7 +87,7 @@ from sanic import Blueprint from .static import static from .authors import authors -content = Blueprint.group(assets, authors, url_prefix='/content') +content = Blueprint.group(static, authors, url_prefix='/content') ``` ```python # api/info.py @@ -254,5 +254,3 @@ async def root(request): async def post_handler(request, post_id): return text('Post {} in Blueprint V1'.format(post_id)) ``` - - diff --git a/docs/sanic/contributing.md b/docs/sanic/contributing.md index 95922693..0b527bb7 100644 --- a/docs/sanic/contributing.md +++ b/docs/sanic/contributing.md @@ -29,8 +29,8 @@ 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 +* 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 diff --git a/docs/sanic/getting_started.md b/docs/sanic/getting_started.md index 3e89cc3e..73a25e58 100644 --- a/docs/sanic/getting_started.md +++ b/docs/sanic/getting_started.md @@ -4,8 +4,13 @@ Make sure you have both [pip](https://pip.pypa.io/en/stable/installing/) and at least version 3.5 of Python before starting. Sanic uses the new `async`/`await` syntax, so earlier versions of python won't work. -1. Install Sanic: `python3 -m pip install sanic` -2. Create a file called `main.py` with the following code: +## 1. Install Sanic + + ``` + python3 -m pip install sanic + ``` + +## 2. Create a file called `main.py` ```python from sanic import Sanic @@ -20,9 +25,16 @@ syntax, so earlier versions of python won't work. if __name__ == "__main__": app.run(host="0.0.0.0", port=8000) ``` - -3. Run the server: `python3 main.py` -4. Open the address `http://0.0.0.0:8000` in your web browser. You should see - the message *Hello world!*. + +## 3. Run the server + + ``` + python3 main.py + ``` + +## 4. Check your browser + +Open the address `http://0.0.0.0:8000` in your web browser. You should see +the message *Hello world!*. You now have a working Sanic server! diff --git a/docs/sanic/middleware.md b/docs/sanic/middleware.md index e041d452..823d3916 100644 --- a/docs/sanic/middleware.md +++ b/docs/sanic/middleware.md @@ -17,7 +17,7 @@ string representing its type: `'request'` or `'response'`. The simplest middleware doesn't modify the request or response at all: -```python +``` @app.middleware('request') async def print_on_request(request): print("I print when a request is received by the server") @@ -33,7 +33,7 @@ Middleware can modify the request or response parameter it is given, *as long as it does not return it*. The following example shows a practical use-case for this. -```python +``` app = Sanic(__name__) @app.middleware('response') @@ -60,7 +60,7 @@ and the response will be returned. If this occurs to a request before the relevant user route handler is reached, the handler will never be called. Returning a response will also prevent any further middleware from running. -```python +``` @app.middleware('request') async def halt_request(request): return text('I halted the request') @@ -79,11 +79,11 @@ If you want to execute startup/teardown code as your server starts or closes, yo - `before_server_stop` - `after_server_stop` -These listeners are implemented as decorators on functions which accept the app object as well as the asyncio loop. +These listeners are implemented as decorators on functions which accept the app object as well as the asyncio loop. For example: -```python +``` @app.listener('before_server_start') async def setup_db(app, loop): app.db = await db_setup() @@ -101,16 +101,16 @@ async def close_db(app, loop): await app.db.close() ``` -It's also possible to register a listener using the `register_listener` method. +It's also possible to register a listener using the `register_listener` method. This may be useful if you define your listeners in another module besides the one you instantiate your app in. -```python +``` app = Sanic() - + async def setup_db(app, loop): app.db = await db_setup() - + app.register_listener(setup_db, 'before_server_start') ``` @@ -118,7 +118,7 @@ app.register_listener(setup_db, 'before_server_start') If you want to schedule a background task to run after the loop has started, Sanic provides the `add_task` method to easily do so. -```python +``` async def notify_server_started_after_five_seconds(): await asyncio.sleep(5) print('Server successfully started!') @@ -128,7 +128,7 @@ app.add_task(notify_server_started_after_five_seconds()) Sanic will attempt to automatically inject the app, passing it as an argument to the task: -```python +``` async def notify_server_started_after_five_seconds(app): await asyncio.sleep(5) print(app.name) @@ -138,7 +138,7 @@ app.add_task(notify_server_started_after_five_seconds) Or you can pass the app explicitly for the same effect: -```python +``` async def notify_server_started_after_five_seconds(app): await asyncio.sleep(5) print(app.name) diff --git a/docs/sanic/sockets.rst b/docs/sanic/sockets.rst new file mode 100644 index 00000000..847fded4 --- /dev/null +++ b/docs/sanic/sockets.rst @@ -0,0 +1,66 @@ +Sockets +======= + +Sanic can use the python +`socket module `_ to accommodate +non IPv4 sockets. + +IPv6 example: + +.. code:: python + + from sanic import Sanic + from sanic.response import json + import socket + + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + sock.bind(('::', 7777)) + + app = Sanic() + + + @app.route("/") + async def test(request): + return json({"hello": "world"}) + + if __name__ == "__main__": + app.run(sock=sock) + +to test IPv6 ``curl -g -6 "http://[::1]:7777/"`` + + +UNIX socket example: + +.. code:: python + + import signal + import sys + import socket + import os + from sanic import Sanic + from sanic.response import json + + + server_socket = '/tmp/sanic.sock' + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(server_socket) + + app = Sanic() + + + @app.route("/") + async def test(request): + return json({"hello": "world"}) + + + def signal_handler(sig, frame): + print('Exiting') + os.unlink(server_socket) + sys.exit(0) + + + if __name__ == "__main__": + app.run(sock=sock) + +to test UNIX: ``curl -v --unix-socket /tmp/sanic.sock http://localhost/hello`` diff --git a/docs/sanic/websocket.rst b/docs/sanic/websocket.rst index 8b813bf8..133e73da 100644 --- a/docs/sanic/websocket.rst +++ b/docs/sanic/websocket.rst @@ -43,6 +43,7 @@ and ``recv`` methods to send and receive data respectively. You could setup your own WebSocket configuration through ``app.config``, like .. code:: python + app.config.WEBSOCKET_MAX_SIZE = 2 ** 20 app.config.WEBSOCKET_MAX_QUEUE = 32 app.config.WEBSOCKET_READ_LIMIT = 2 ** 16 diff --git a/environment.yml b/environment.yml index 78eddfd0..165c773d 100644 --- a/environment.yml +++ b/environment.yml @@ -17,5 +17,5 @@ dependencies: - aiofiles>=0.3.0 - websockets>=6.0 - sphinxcontrib-asyncio>=0.2.0 - - multidict>=4.0<5.0 + - multidict>=4.0,<5.0 - https://github.com/channelcat/docutils-fork/zipball/master diff --git a/sanic/__init__.py b/sanic/__init__.py index 786b6647..a6e7ba72 100644 --- a/sanic/__init__.py +++ b/sanic/__init__.py @@ -1,6 +1,7 @@ from sanic.app import Sanic from sanic.blueprints import Blueprint + __version__ = "0.8.3" __all__ = ["Sanic", "Blueprint"] diff --git a/sanic/__main__.py b/sanic/__main__.py index ede743fd..73de3265 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -1,8 +1,9 @@ from argparse import ArgumentParser from importlib import import_module -from sanic.log import logger from sanic.app import Sanic +from sanic.log import logger + if __name__ == "__main__": parser = ArgumentParser(prog="sanic") @@ -51,5 +52,5 @@ if __name__ == "__main__": " Example File: project/sanic_server.py -> app\n" " Example Module: project.sanic_server.app".format(e.name) ) - except ValueError as e: + except ValueError: logger.exception("Failed to run app") diff --git a/sanic/app.py b/sanic/app.py index 9fa2e4ce..c2a24464 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -1,29 +1,30 @@ -import os import logging import logging.config +import os import re import warnings -from asyncio import get_event_loop, ensure_future, CancelledError -from collections import deque, defaultdict + +from asyncio import CancelledError, ensure_future, get_event_loop +from collections import defaultdict, deque from functools import partial from inspect import getmodulename, isawaitable, signature, stack +from ssl import Purpose, create_default_context from traceback import format_exc from urllib.parse import urlencode, urlunparse -from ssl import create_default_context, Purpose +from sanic import reloader_helpers from sanic.config import Config from sanic.constants import HTTP_METHODS -from sanic.exceptions import ServerError, URLBuildError, SanicException +from sanic.exceptions import SanicException, ServerError, URLBuildError from sanic.handlers import ErrorHandler -from sanic.log import logger, error_logger, LOGGING_CONFIG_DEFAULTS +from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger from sanic.response import HTTPResponse, StreamingHTTPResponse from sanic.router import Router -from sanic.server import serve, serve_multiple, HttpProtocol, Signal +from sanic.server import HttpProtocol, Signal, serve, serve_multiple from sanic.static import register as static_register from sanic.testing import SanicTestClient from sanic.views import CompositionView -from sanic.websocket import WebSocketProtocol, ConnectionClosed -import sanic.reloader_helpers as reloader_helpers +from sanic.websocket import ConnectionClosed, WebSocketProtocol class Sanic: @@ -370,8 +371,7 @@ class Sanic: ): """Decorate a function to be registered as a websocket route :param uri: path of the URL - :param subprotocols: optional list of strings with the supported - subprotocols + :param subprotocols: optional list of str with supported subprotocols :param host: :return: decorated function """ @@ -567,7 +567,7 @@ class Sanic: return self.blueprint(*args, **kwargs) def url_for(self, view_name: str, **kwargs): - """Build a URL based on a view name and the values provided. + r"""Build a URL based on a view name and the values provided. In order to build a URL, all request parameters must be supplied as keyword arguments, and each parameter must pass the test for the @@ -578,7 +578,7 @@ class Sanic: the output URL's query string. :param view_name: string referencing the view name - :param \*\*kwargs: keys and values that are used to build request + :param \**kwargs: keys and values that are used to build request parameters and query string arguments. :return: the built URL @@ -835,6 +835,14 @@ class Sanic: access_log=True, **kwargs ): + if "loop" in kwargs: + raise TypeError( + "loop is not a valid argument. To use an existing loop, " + "change to create_server().\nSee more: " + "https://sanic.readthedocs.io/en/latest/sanic/deploying.html" + "#asynchronous-support" + ) + """Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing. diff --git a/sanic/blueprints.py b/sanic/blueprints.py index c148a952..f7f6cfb7 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -3,8 +3,9 @@ from collections import defaultdict, namedtuple from sanic.constants import HTTP_METHODS from sanic.views import CompositionView + FutureRoute = namedtuple( - "Route", + "FutureRoute", [ "handler", "uri", @@ -16,11 +17,15 @@ FutureRoute = namedtuple( "name", ], ) -FutureListener = namedtuple("Listener", ["handler", "uri", "methods", "host"]) -FutureMiddleware = namedtuple("Route", ["middleware", "args", "kwargs"]) -FutureException = namedtuple("Route", ["handler", "args", "kwargs"]) +FutureListener = namedtuple( + "FutureListener", ["handler", "uri", "methods", "host"] +) +FutureMiddleware = namedtuple( + "FutureMiddleware", ["middleware", "args", "kwargs"] +) +FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"]) FutureStatic = namedtuple( - "Route", ["uri", "file_or_directory", "args", "kwargs"] + "FutureStatic", ["uri", "file_or_directory", "args", "kwargs"] ) diff --git a/sanic/cookies.py b/sanic/cookies.py index 7d323529..8210d8f4 100644 --- a/sanic/cookies.py +++ b/sanic/cookies.py @@ -1,6 +1,7 @@ import re import string + # ------------------------------------------------------------ # # SimpleCookie # ------------------------------------------------------------ # @@ -17,7 +18,7 @@ _Translator.update({ord('"'): '\\"', ord("\\"): "\\\\"}) def _quote(str): - """Quote a string for use in a cookie header. + r"""Quote a string for use in a cookie header. If the string does not need to be double-quoted, then just return the string. Otherwise, surround the string in doublequotes and quote (with a \) special characters. diff --git a/sanic/exceptions.py b/sanic/exceptions.py index 6e9323a9..35318ac9 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -1,5 +1,6 @@ from sanic.helpers import STATUS_CODES + TRACEBACK_STYLE = """