Merge branch 'master' of https://github.com/channelcat/sanic into cleanups
This commit is contained in:
		| @@ -78,10 +78,7 @@ Documentation | ||||
| TODO | ||||
| ---- | ||||
| * Streamed file processing | ||||
| * File output | ||||
| * Examples of integrations with 3rd-party modules | ||||
| * RESTful router | ||||
|  | ||||
| * http2 | ||||
| Limitations | ||||
| ----------- | ||||
| * No wheels for uvloop and httptools on Windows :( | ||||
|   | ||||
| @@ -131,8 +131,8 @@ can be used to implement our API versioning scheme. | ||||
| from sanic.response import text | ||||
| from sanic import Blueprint | ||||
|  | ||||
| blueprint_v1 = Blueprint('v1') | ||||
| blueprint_v2 = Blueprint('v2') | ||||
| blueprint_v1 = Blueprint('v1', url_prefix='/v1') | ||||
| blueprint_v2 = Blueprint('v2', url_prefix='/v2') | ||||
|  | ||||
| @blueprint_v1.route('/') | ||||
| async def api_v1_root(request): | ||||
|   | ||||
| @@ -11,7 +11,7 @@ from sanic.response import json | ||||
| @app.route("/") | ||||
| async def test(request): | ||||
|     return json({ "hello": "world" }) | ||||
| ```  | ||||
| ``` | ||||
|  | ||||
| When the url `http://server.url/` is accessed (the base url of the server), the | ||||
| final `/` is matched by the router to the handler function, `test`, which then | ||||
| @@ -145,6 +145,28 @@ Other things to keep in mind when using `url_for`: | ||||
| url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two') | ||||
| # /posts/5?arg_one=one&arg_two=two | ||||
| ``` | ||||
| - Multivalue argument can be passed to `url_for`. For example: | ||||
| ```python | ||||
| url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two']) | ||||
| # /posts/5?arg_one=one&arg_one=two | ||||
| ``` | ||||
| - Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not support now and will be ignored). For example: | ||||
| ```python | ||||
| url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor') | ||||
| # /posts/5?arg_one=one#anchor | ||||
|  | ||||
| url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True) | ||||
| # //server/posts/5?arg_one=one | ||||
| # _external requires passed argument _server or SERVER_NAME in app.config or url will be same as no _external | ||||
|  | ||||
| url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True) | ||||
| # http://server/posts/5?arg_one=one | ||||
| # when specifying _scheme, _external must be True | ||||
|  | ||||
| # you can pass all special arguments one time | ||||
| url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888') | ||||
| # http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor | ||||
| ``` | ||||
| - All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be thrown. | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| from collections import defaultdict, namedtuple | ||||
|  | ||||
| from sanic.constants import HTTP_METHODS | ||||
| from sanic.views import CompositionView | ||||
|  | ||||
| FutureRoute = namedtuple('Route', ['handler', 'uri', 'methods', 'host']) | ||||
| FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host']) | ||||
| @@ -11,9 +13,9 @@ FutureStatic = namedtuple('Route', | ||||
|  | ||||
| class Blueprint: | ||||
|     def __init__(self, name, url_prefix=None, host=None): | ||||
|         """ | ||||
|         Creates a new blueprint | ||||
|         :param name: Unique name of the blueprint | ||||
|         """Create a new blueprint | ||||
|  | ||||
|         :param name: unique name of the blueprint | ||||
|         :param url_prefix: URL to be prefixed before all route URLs | ||||
|         """ | ||||
|         self.name = name | ||||
| @@ -27,9 +29,7 @@ class Blueprint: | ||||
|         self.statics = [] | ||||
|  | ||||
|     def register(self, app, options): | ||||
|         """ | ||||
|         Registers the blueprint to the sanic app. | ||||
|         """ | ||||
|         """Register the blueprint to the sanic app.""" | ||||
|  | ||||
|         url_prefix = options.get('url_prefix', self.url_prefix) | ||||
|  | ||||
| @@ -71,10 +71,10 @@ class Blueprint: | ||||
|                 app.listener(event)(listener) | ||||
|  | ||||
|     def route(self, uri, methods=frozenset({'GET'}), host=None): | ||||
|         """ | ||||
|         Creates a blueprint route from a decorated function. | ||||
|         :param uri: Endpoint at which the route will be accessible. | ||||
|         :param methods: List of acceptable HTTP methods. | ||||
|         """Create a blueprint route from a decorated function. | ||||
|  | ||||
|         :param uri: endpoint at which the route will be accessible. | ||||
|         :param methods: list of acceptable HTTP methods. | ||||
|         """ | ||||
|         def decorator(handler): | ||||
|             route = FutureRoute(handler, uri, methods, host) | ||||
| @@ -82,20 +82,33 @@ class Blueprint: | ||||
|             return handler | ||||
|         return decorator | ||||
|  | ||||
|     def add_route(self, handler, uri, methods=None, host=None): | ||||
|     def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None): | ||||
|         """Create a blueprint route from a function. | ||||
|  | ||||
|         :param handler: function for handling uri requests. Accepts function, | ||||
|                         or class instance with a view_class method. | ||||
|         :param uri: endpoint at which the route will be accessible. | ||||
|         :param methods: list of acceptable HTTP methods. | ||||
|         :return: function or class instance | ||||
|         """ | ||||
|         Creates a blueprint route from a function. | ||||
|         :param handler: Function to handle uri request. | ||||
|         :param uri: Endpoint at which the route will be accessible. | ||||
|         :param methods: List of acceptable HTTP methods. | ||||
|         """ | ||||
|         route = FutureRoute(handler, uri, methods, host) | ||||
|         self.routes.append(route) | ||||
|         # Handle HTTPMethodView differently | ||||
|         if hasattr(handler, 'view_class'): | ||||
|             methods = set() | ||||
|  | ||||
|             for method in HTTP_METHODS: | ||||
|                 if getattr(handler.view_class, method.lower(), None): | ||||
|                     methods.add(method) | ||||
|  | ||||
|         # handle composition view differently | ||||
|         if isinstance(handler, CompositionView): | ||||
|             methods = handler.handlers.keys() | ||||
|  | ||||
|         self.route(uri=uri, methods=methods, host=host)(handler) | ||||
|         return handler | ||||
|  | ||||
|     def listener(self, event): | ||||
|         """ | ||||
|         Create a listener from a decorated function. | ||||
|         """Create a listener from a decorated function. | ||||
|  | ||||
|         :param event: Event to listen to. | ||||
|         """ | ||||
|         def decorator(listener): | ||||
| @@ -104,9 +117,7 @@ class Blueprint: | ||||
|         return decorator | ||||
|  | ||||
|     def middleware(self, *args, **kwargs): | ||||
|         """ | ||||
|         Creates a blueprint middleware from a decorated function. | ||||
|         """ | ||||
|         """Create a blueprint middleware from a decorated function.""" | ||||
|         def register_middleware(_middleware): | ||||
|             future_middleware = FutureMiddleware(_middleware, args, kwargs) | ||||
|             self.middlewares.append(future_middleware) | ||||
| @@ -121,9 +132,7 @@ class Blueprint: | ||||
|             return register_middleware | ||||
|  | ||||
|     def exception(self, *args, **kwargs): | ||||
|         """ | ||||
|         Creates a blueprint exception from a decorated function. | ||||
|         """ | ||||
|         """Create a blueprint exception from a decorated function.""" | ||||
|         def decorator(handler): | ||||
|             exception = FutureException(handler, args, kwargs) | ||||
|             self.exceptions.append(exception) | ||||
| @@ -131,9 +140,9 @@ class Blueprint: | ||||
|         return decorator | ||||
|  | ||||
|     def static(self, uri, file_or_directory, *args, **kwargs): | ||||
|         """ | ||||
|         Creates a blueprint static route from a decorated function. | ||||
|         :param uri: Endpoint at which the route will be accessible. | ||||
|         """Create a blueprint static route from a decorated function. | ||||
|  | ||||
|         :param uri: endpoint at which the route will be accessible. | ||||
|         :param file_or_directory: Static asset. | ||||
|         """ | ||||
|         static = FutureStatic(uri, file_or_directory, args, kwargs) | ||||
|   | ||||
| @@ -39,8 +39,9 @@ class Config(dict): | ||||
|         self[attr] = value | ||||
|  | ||||
|     def from_envvar(self, variable_name): | ||||
|         """Loads a configuration from an environment variable pointing to | ||||
|         """Load a configuration from an environment variable pointing to | ||||
|         a configuration file. | ||||
|  | ||||
|         :param variable_name: name of the environment variable | ||||
|         :return: bool. ``True`` if able to load config, ``False`` otherwise. | ||||
|         """ | ||||
| @@ -52,8 +53,9 @@ class Config(dict): | ||||
|         return self.from_pyfile(config_file) | ||||
|  | ||||
|     def from_pyfile(self, filename): | ||||
|         """Updates the values in the config from a Python file. Only the uppercase | ||||
|         variables in that module are stored in the config. | ||||
|         """Update the values in the config from a Python file. | ||||
|         Only the uppercase variables in that module are stored in the config. | ||||
|  | ||||
|         :param filename: an absolute path to the config file | ||||
|         """ | ||||
|         module = types.ModuleType('config') | ||||
| @@ -69,7 +71,7 @@ class Config(dict): | ||||
|         return True | ||||
|  | ||||
|     def from_object(self, obj): | ||||
|         """Updates the values from the given object. | ||||
|         """Update the values from the given object. | ||||
|         Objects are usually either modules or classes. | ||||
|  | ||||
|         Just the uppercase variables in that object are stored in the config. | ||||
|   | ||||
| @@ -39,8 +39,7 @@ _is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch | ||||
|  | ||||
|  | ||||
| class CookieJar(dict): | ||||
|     """ | ||||
|     CookieJar dynamically writes headers as cookies are added and removed | ||||
|     """CookieJar dynamically writes headers as cookies are added and removed | ||||
|     It gets around the limitation of one header per name by using the | ||||
|     MultiHeader class to provide a unique key that encodes to Set-Cookie. | ||||
|     """ | ||||
| @@ -75,9 +74,7 @@ class CookieJar(dict): | ||||
|  | ||||
|  | ||||
| class Cookie(dict): | ||||
|     """ | ||||
|     This is a stripped down version of Morsel from SimpleCookie #gottagofast | ||||
|     """ | ||||
|     """A stripped down version of Morsel from SimpleCookie #gottagofast""" | ||||
|     _keys = { | ||||
|         "expires": "expires", | ||||
|         "path": "Path", | ||||
| @@ -128,9 +125,8 @@ class Cookie(dict): | ||||
|  | ||||
|  | ||||
| class MultiHeader: | ||||
|     """ | ||||
|     Allows us to set a header within response that has a unique key, | ||||
|     but may contain duplicate header names | ||||
|     """String-holding object which allow us to set a header within response | ||||
|     that has a unique key, but may contain duplicate header names | ||||
|     """ | ||||
|     def __init__(self, name): | ||||
|         self.name = name | ||||
|   | ||||
| @@ -35,8 +35,8 @@ class ErrorHandler: | ||||
|         self.handlers[exception] = handler | ||||
|  | ||||
|     def response(self, request, exception): | ||||
|         """ | ||||
|         Fetches and executes an exception handler and returns a response object | ||||
|         """Fetches and executes an exception handler and returns a response | ||||
|         object | ||||
|  | ||||
|         :param request: Request | ||||
|         :param exception: Exception to handle | ||||
| @@ -79,9 +79,7 @@ class ErrorHandler: | ||||
|  | ||||
|  | ||||
| class ContentRangeHandler: | ||||
|     """ | ||||
|     This class is for parsing the request header | ||||
|     """ | ||||
|     """Class responsible for parsing request header""" | ||||
|     __slots__ = ('start', 'end', 'size', 'total', 'headers') | ||||
|  | ||||
|     def __init__(self, request, stats): | ||||
|   | ||||
| @@ -16,8 +16,7 @@ DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream" | ||||
|  | ||||
|  | ||||
| class RequestParameters(dict): | ||||
|     """ | ||||
|     Hosts a dict with lists as values where get returns the first | ||||
|     """Hosts a dict with lists as values where get returns the first | ||||
|     value of the list and getlist returns the whole shebang | ||||
|     """ | ||||
|  | ||||
| @@ -31,9 +30,7 @@ class RequestParameters(dict): | ||||
|  | ||||
|  | ||||
| class Request(dict): | ||||
|     """ | ||||
|     Properties of an HTTP request such as URL, headers, etc. | ||||
|     """ | ||||
|     """Properties of an HTTP request such as URL, headers, etc.""" | ||||
|     __slots__ = ( | ||||
|         'url', 'headers', 'version', 'method', '_cookies', 'transport', | ||||
|         'query_string', 'body', | ||||
| @@ -73,8 +70,8 @@ class Request(dict): | ||||
|  | ||||
|     @property | ||||
|     def token(self): | ||||
|         """ | ||||
|         Attempts to return the auth header token. | ||||
|         """Attempt to return the auth header token. | ||||
|  | ||||
|         :return: token related to request | ||||
|         """ | ||||
|         auth_header = self.headers.get('Authorization') | ||||
| @@ -146,11 +143,10 @@ File = namedtuple('File', ['type', 'body', 'name']) | ||||
|  | ||||
|  | ||||
| def parse_multipart_form(body, boundary): | ||||
|     """ | ||||
|     Parses a request body and returns fields and files | ||||
|     """Parse a request body and returns fields and files | ||||
|  | ||||
|     :param body: Bytes request body | ||||
|     :param boundary: Bytes multipart boundary | ||||
|     :param body: bytes request body | ||||
|     :param boundary: bytes multipart boundary | ||||
|     :return: fields (RequestParameters), files (RequestParameters) | ||||
|     """ | ||||
|     files = RequestParameters() | ||||
|   | ||||
| @@ -154,15 +154,32 @@ def json(body, status=200, headers=None, **kwargs): | ||||
|                         status=status, content_type="application/json") | ||||
|  | ||||
|  | ||||
| def text(body, status=200, headers=None): | ||||
| def text(body, status=200, headers=None, | ||||
|          content_type="text/plain; charset=utf-8"): | ||||
|     """ | ||||
|     Returns response object with body in text format. | ||||
|     :param body: Response data to be encoded. | ||||
|     :param status: Response code. | ||||
|     :param headers: Custom Headers. | ||||
|     :param content_type: | ||||
|         the content type (string) of the response | ||||
|     """ | ||||
|     return HTTPResponse(body, status=status, headers=headers, | ||||
|                         content_type="text/plain; charset=utf-8") | ||||
|                         content_type=content_type) | ||||
|  | ||||
|  | ||||
| def raw(body, status=200, headers=None, | ||||
|         content_type="application/octet-stream"): | ||||
|     """ | ||||
|     Returns response object without encoding the body. | ||||
|     :param body: Response data. | ||||
|     :param status: Response code. | ||||
|     :param headers: Custom Headers. | ||||
|     :param content_type: | ||||
|         the content type (string) of the response | ||||
|     """ | ||||
|     return HTTPResponse(body_bytes=body, status=status, headers=headers, | ||||
|                         content_type=content_type) | ||||
|  | ||||
|  | ||||
| def html(body, status=200, headers=None): | ||||
| @@ -177,8 +194,8 @@ def html(body, status=200, headers=None): | ||||
|  | ||||
|  | ||||
| async def file(location, mime_type=None, headers=None, _range=None): | ||||
|     """ | ||||
|     Returns response object with file data. | ||||
|     """Return a response object with file data. | ||||
|  | ||||
|     :param location: Location of file on system. | ||||
|     :param mime_type: Specific mime_type. | ||||
|     :param headers: Custom Headers. | ||||
| @@ -205,14 +222,12 @@ async def file(location, mime_type=None, headers=None, _range=None): | ||||
|  | ||||
| def redirect(to, headers=None, status=302, | ||||
|              content_type="text/html; charset=utf-8"): | ||||
|     """ | ||||
|     Aborts execution and causes a 302 redirect (by default). | ||||
|     """Abort execution and cause a 302 redirect (by default). | ||||
|  | ||||
|     :param to: path or fully qualified URL to redirect to | ||||
|     :param headers: optional dict of headers to include in the new request | ||||
|     :param status: status code (int) of the new request, defaults to 302 | ||||
|     :param content_type: | ||||
|         the content type (string) of the response | ||||
|     :param content_type: the content type (string) of the response | ||||
|     :returns: the redirecting Response | ||||
|     """ | ||||
|     headers = headers or {} | ||||
|   | ||||
| @@ -33,8 +33,7 @@ class RouteDoesNotExist(Exception): | ||||
|  | ||||
|  | ||||
| class Router: | ||||
|     """ | ||||
|     Router supports basic routing with parameters and method checks | ||||
|     """Router supports basic routing with parameters and method checks | ||||
|  | ||||
|     Usage: | ||||
|  | ||||
| @@ -72,8 +71,9 @@ class Router: | ||||
|         self.hosts = set() | ||||
|  | ||||
|     def parse_parameter_string(self, parameter_string): | ||||
|         """ | ||||
|         Parse a parameter string into its constituent name, type, and pattern | ||||
|         """Parse a parameter string into its constituent name, type, and | ||||
|         pattern | ||||
|  | ||||
|         For example: | ||||
|         `parse_parameter_string('<param_one:[A-z]>')` -> | ||||
|             ('param_one', str, '[A-z]') | ||||
| @@ -95,14 +95,13 @@ class Router: | ||||
|         return name, _type, pattern | ||||
|  | ||||
|     def add(self, uri, methods, handler, host=None): | ||||
|         """ | ||||
|         Adds a handler to the route list | ||||
|         """Add 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 | ||||
|         :param handler: Request handler function. | ||||
|                         When executed, it should provide a response object. | ||||
|         :param uri: path to match | ||||
|         :param methods: sequence of accepted method names. If none are | ||||
|             provided, any method is allowed | ||||
|         :param handler: request handler function. | ||||
|             When executed, it should provide a response object. | ||||
|         :return: Nothing | ||||
|         """ | ||||
|  | ||||
| @@ -237,8 +236,7 @@ class Router: | ||||
|  | ||||
|     @lru_cache(maxsize=ROUTER_CACHE_SIZE) | ||||
|     def find_route_by_view_name(self, view_name): | ||||
|         """ | ||||
|         Find a route in the router based on the specified view name. | ||||
|         """Find a route in the router based on the specified view name. | ||||
|  | ||||
|         :param view_name: string of view name to search by | ||||
|         :return: tuple containing (uri, Route) | ||||
| @@ -253,8 +251,7 @@ class Router: | ||||
|         return (None, None) | ||||
|  | ||||
|     def get(self, request): | ||||
|         """ | ||||
|         Gets a request handler based on the URL of the request, or raises an | ||||
|         """Get a request handler based on the URL of the request, or raises an | ||||
|         error | ||||
|  | ||||
|         :param request: Request object | ||||
| @@ -273,11 +270,11 @@ class Router: | ||||
|  | ||||
|     @lru_cache(maxsize=ROUTER_CACHE_SIZE) | ||||
|     def _get(self, url, method, host): | ||||
|         """ | ||||
|         Gets a request handler based on the URL of the request, or raises an | ||||
|         """Get a request handler based on the URL of the request, or raises an | ||||
|         error.  Internal method for caching. | ||||
|         :param url: Request URL | ||||
|         :param method: Request method | ||||
|  | ||||
|         :param url: request URL | ||||
|         :param method: request method | ||||
|         :return: handler, arguments, keyword arguments | ||||
|         """ | ||||
|         url = host + url | ||||
|   | ||||
| @@ -22,8 +22,7 @@ from .views import CompositionView | ||||
|  | ||||
| class Sanic: | ||||
|  | ||||
|     def __init__(self, name=None, router=None, | ||||
|                  error_handler=None): | ||||
|     def __init__(self, name=None, router=None, error_handler=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: | ||||
| @@ -33,9 +32,12 @@ class Sanic: | ||||
|             handler.setFormatter(formatter) | ||||
|             log.addHandler(handler) | ||||
|             log.setLevel(logging.INFO) | ||||
|  | ||||
|         # Get name from previous stack frame | ||||
|         if name is None: | ||||
|             frame_records = stack()[1] | ||||
|             name = getmodulename(frame_records[1]) | ||||
|  | ||||
|         self.name = name | ||||
|         self.router = router or Router() | ||||
|         self.error_handler = error_handler or ErrorHandler() | ||||
| @@ -53,9 +55,7 @@ class Sanic: | ||||
|  | ||||
|     @property | ||||
|     def loop(self): | ||||
|         """ | ||||
|         Synonymous with asyncio.get_event_loop() | ||||
|         """ | ||||
|         """Synonymous with asyncio.get_event_loop().""" | ||||
|         return get_event_loop() | ||||
|  | ||||
|     # -------------------------------------------------------------------- # | ||||
| @@ -63,13 +63,12 @@ class Sanic: | ||||
|     # -------------------------------------------------------------------- # | ||||
|  | ||||
|     def add_task(self, task): | ||||
|         """ | ||||
|         Schedule a task to run later, after the loop has started. | ||||
|         """Schedule a task to run later, after the loop has started. | ||||
|         Different from asyncio.ensure_future in that it does not | ||||
|         also return a future, and the actual ensure_future call | ||||
|         is delayed until before server start. | ||||
|  | ||||
|         :param task: A future, couroutine or awaitable. | ||||
|         :param task: future, couroutine or awaitable | ||||
|         """ | ||||
|         @self.listener('before_server_start') | ||||
|         def run(app, loop): | ||||
| @@ -80,10 +79,9 @@ class Sanic: | ||||
|  | ||||
|     # Decorator | ||||
|     def listener(self, event): | ||||
|         """ | ||||
|         Create a listener from a decorated function. | ||||
|         """Create a listener from a decorated function. | ||||
|  | ||||
|         :param event: Event to listen to. | ||||
|         :param event: event to listen to | ||||
|         """ | ||||
|         def decorator(listener): | ||||
|             self.listeners[event].append(listener) | ||||
| @@ -92,8 +90,7 @@ class Sanic: | ||||
|  | ||||
|     # Decorator | ||||
|     def route(self, uri, methods=frozenset({'GET'}), host=None): | ||||
|         """ | ||||
|         Decorates a function to be registered as a route | ||||
|         """Decorate a function to be registered as a route | ||||
|  | ||||
|         :param uri: path of the URL | ||||
|         :param methods: list or tuple of methods allowed | ||||
| @@ -136,8 +133,7 @@ class Sanic: | ||||
|         return self.route(uri, methods=frozenset({"DELETE"}), host=host) | ||||
|  | ||||
|     def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None): | ||||
|         """ | ||||
|         A helper method to register class instance or | ||||
|         """A helper method to register class instance or | ||||
|         functions as a handler to the application url | ||||
|         routes. | ||||
|  | ||||
| @@ -168,8 +164,7 @@ class Sanic: | ||||
|  | ||||
|     # Decorator | ||||
|     def exception(self, *exceptions): | ||||
|         """ | ||||
|         Decorates a function to be registered as a handler for exceptions | ||||
|         """Decorate a function to be registered as a handler for exceptions | ||||
|  | ||||
|         :param exceptions: exceptions | ||||
|         :return: decorated function | ||||
| @@ -184,9 +179,8 @@ class Sanic: | ||||
|  | ||||
|     # Decorator | ||||
|     def middleware(self, middleware_or_request): | ||||
|         """ | ||||
|         Decorates and registers middleware to be called before a request | ||||
|         can either be called as @app.middleware or @app.middleware('request') | ||||
|         """Decorate and register middleware to be called before a request. | ||||
|         Can either be called as @app.middleware or @app.middleware('request') | ||||
|         """ | ||||
|         def register_middleware(middleware, attach_to='request'): | ||||
|             if attach_to == 'request': | ||||
| @@ -206,16 +200,14 @@ class Sanic: | ||||
|     # Static Files | ||||
|     def static(self, uri, file_or_directory, pattern='.+', | ||||
|                use_modified_since=True, use_content_range=False): | ||||
|         """ | ||||
|         Registers a root to serve files from.  The input can either be a file | ||||
|         or a directory.  See | ||||
|         """Register a root to serve files from. The input can either be a | ||||
|         file or a directory. See | ||||
|         """ | ||||
|         static_register(self, uri, file_or_directory, pattern, | ||||
|                         use_modified_since, use_content_range) | ||||
|  | ||||
|     def blueprint(self, blueprint, **options): | ||||
|         """ | ||||
|         Registers a blueprint on the application. | ||||
|         """Register a blueprint on the application. | ||||
|  | ||||
|         :param blueprint: Blueprint object | ||||
|         :param options: option dictionary with blueprint defaults | ||||
| @@ -242,7 +234,7 @@ class Sanic: | ||||
|         return self.blueprint(*args, **kwargs) | ||||
|  | ||||
|     def url_for(self, view_name: str, **kwargs): | ||||
|         """Builds a URL based on a view name and the values provided. | ||||
|         """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 | ||||
| @@ -252,7 +244,7 @@ class Sanic: | ||||
|         Keyword arguments that are not request parameters will be included in | ||||
|         the output URL's query string. | ||||
|  | ||||
|         :param view_name: A string referencing the view name | ||||
|         :param view_name: string referencing the view name | ||||
|         :param **kwargs: keys and values that are used to build request | ||||
|             parameters and query string arguments. | ||||
|  | ||||
| @@ -275,6 +267,19 @@ class Sanic: | ||||
|         matched_params = re.findall( | ||||
|             self.router.parameter_pattern, uri) | ||||
|  | ||||
|         # _method is only a placeholder now, don't know how to support it | ||||
|         kwargs.pop('_method', None) | ||||
|         anchor = kwargs.pop('_anchor', '') | ||||
|         # _external need SERVER_NAME in config or pass _server arg | ||||
|         external = kwargs.pop('_external', False) | ||||
|         scheme = kwargs.pop('_scheme', '') | ||||
|         if scheme and not external: | ||||
|             raise ValueError('When specifying _scheme, _external must be True') | ||||
|  | ||||
|         netloc = kwargs.pop('_server', None) | ||||
|         if netloc is None and external: | ||||
|             netloc = self.config.get('SERVER_NAME', '') | ||||
|  | ||||
|         for match in matched_params: | ||||
|             name, _type, pattern = self.router.parse_parameter_string( | ||||
|                 match) | ||||
| @@ -315,12 +320,9 @@ class Sanic: | ||||
|                 replacement_regex, supplied_param, out) | ||||
|  | ||||
|         # parse the remainder of the keyword arguments into a querystring | ||||
|         if kwargs: | ||||
|             query_string = urlencode(kwargs) | ||||
|             out = urlunparse(( | ||||
|                 '', '', out, | ||||
|                 '', query_string, '' | ||||
|             )) | ||||
|         query_string = urlencode(kwargs, doseq=True) if kwargs else '' | ||||
|         # scheme://netloc/path;parameters?query#fragment | ||||
|         out = urlunparse((scheme, netloc, out, '', query_string, anchor)) | ||||
|  | ||||
|         return out | ||||
|  | ||||
| @@ -332,9 +334,8 @@ class Sanic: | ||||
|         pass | ||||
|  | ||||
|     async def handle_request(self, request, response_callback): | ||||
|         """ | ||||
|         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 | ||||
|         """Take a request from the HTTP Server and return 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 | ||||
| @@ -416,9 +417,8 @@ class Sanic: | ||||
|             after_start=None, before_stop=None, after_stop=None, ssl=None, | ||||
|             sock=None, workers=1, loop=None, protocol=HttpProtocol, | ||||
|             backlog=100, stop_event=None, register_sys_signals=True): | ||||
|         """ | ||||
|         Runs the HTTP Server and listens until keyboard interrupt or term | ||||
|         signal. On termination, drains connections before closing. | ||||
|         """Run the HTTP Server and listen until keyboard interrupt or term | ||||
|         signal. On termination, drain connections before closing. | ||||
|  | ||||
|         :param host: Address to host on | ||||
|         :param port: Port to host on | ||||
| @@ -468,9 +468,7 @@ class Sanic: | ||||
|                             before_stop=None, after_stop=None, ssl=None, | ||||
|                             sock=None, loop=None, protocol=HttpProtocol, | ||||
|                             backlog=100, stop_event=None): | ||||
|         """ | ||||
|         Asynchronous version of `run`. | ||||
|         """ | ||||
|         """Asynchronous version of `run`.""" | ||||
|         server_settings = self._helper( | ||||
|             host=host, port=port, debug=debug, before_start=before_start, | ||||
|             after_start=after_start, before_stop=before_stop, | ||||
| @@ -491,9 +489,7 @@ class Sanic: | ||||
|                 after_stop=None, ssl=None, sock=None, workers=1, loop=None, | ||||
|                 protocol=HttpProtocol, backlog=100, stop_event=None, | ||||
|                 register_sys_signals=True, run_async=False): | ||||
|         """ | ||||
|         Helper function used by `run` and `create_server`. | ||||
|         """ | ||||
|         """Helper function used by `run` and `create_server`.""" | ||||
|  | ||||
|         if loop is not None: | ||||
|             if debug: | ||||
|   | ||||
| @@ -33,8 +33,7 @@ class Signal: | ||||
|  | ||||
|  | ||||
| class CIDict(dict): | ||||
|     """ | ||||
|     Case Insensitive dict where all keys are converted to lowercase | ||||
|     """Case Insensitive dict where all keys are converted to lowercase | ||||
|     This does not maintain the inputted case when calling items() or keys() | ||||
|     in favor of speed, since headers are case insensitive | ||||
|     """ | ||||
| @@ -228,8 +227,8 @@ class HttpProtocol(asyncio.Protocol): | ||||
|         self._total_request_size = 0 | ||||
|  | ||||
|     def close_if_idle(self): | ||||
|         """ | ||||
|         Close the connection if a request is not being sent or received | ||||
|         """Close the connection if a request is not being sent or received | ||||
|  | ||||
|         :return: boolean - True if closed, false if staying open | ||||
|         """ | ||||
|         if not self.parser: | ||||
| @@ -239,9 +238,8 @@ class HttpProtocol(asyncio.Protocol): | ||||
|  | ||||
|  | ||||
| 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 | ||||
|     """Cache the current time, since it is needed at the end of every | ||||
|     keep-alive request to update the request timeout time | ||||
|  | ||||
|     :param loop: | ||||
|     :return: | ||||
| @@ -252,7 +250,8 @@ def update_current_time(loop): | ||||
|  | ||||
|  | ||||
| def trigger_events(events, loop): | ||||
|     """ | ||||
|     """Trigger event callbacks (functions or async) | ||||
|  | ||||
|     :param events: one or more sync or async functions to execute | ||||
|     :param loop: event loop | ||||
|     """ | ||||
| @@ -267,31 +266,30 @@ 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): | ||||
|     """ | ||||
|     Starts asynchronous HTTP Server on an individual process. | ||||
|     """Start 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 | ||||
|     :param before_start: function to be executed before the server starts | ||||
|                          listening. Takes arguments `app` instance and `loop` | ||||
|     :param after_start: Function to be executed after the server starts | ||||
|     :param after_start: function to be executed after the server starts | ||||
|                         listening. Takes  arguments `app` instance and `loop` | ||||
|     :param before_stop: Function to be executed when a stop signal is | ||||
|     :param before_stop: function to be executed when a stop signal is | ||||
|                         received before it is respected. Takes arguments | ||||
|                         `app` instance and `loop` | ||||
|     :param after_stop: Function to be executed when a stop signal is | ||||
|     :param after_stop: function to be executed when a stop signal is | ||||
|                        received after it is respected. Takes arguments | ||||
|                         `app` instance and `loop` | ||||
|     :param debug: Enables debug output (slows server) | ||||
|     :param debug: enables debug output (slows server) | ||||
|     :param request_timeout: time in seconds | ||||
|     :param ssl: SSLContext | ||||
|     :param sock: Socket for the server to accept connections from | ||||
|     :param request_max_size: size in bytes, `None` for no limit | ||||
|     :param reuse_port: `True` for multiple workers | ||||
|     :param loop: asyncio compatible event loop | ||||
|     :param protocol: Subclass of asyncio protocol class | ||||
|     :param protocol: subclass of asyncio protocol class | ||||
|     :return: Nothing | ||||
|     """ | ||||
|     if not run_async: | ||||
| @@ -377,9 +375,8 @@ def serve(host, port, request_handler, error_handler, before_start=None, | ||||
|  | ||||
|  | ||||
| def serve_multiple(server_settings, workers, stop_event=None): | ||||
|     """ | ||||
|     Starts multiple server processes simultaneously.  Stops on interrupt | ||||
|     and terminate signals, and drains connections when complete. | ||||
|     """Start multiple server processes simultaneously.  Stop on interrupt | ||||
|     and terminate signals, and drain connections when complete. | ||||
|  | ||||
|     :param server_settings: kw arguments to be passed to the serve function | ||||
|     :param workers: number of workers to launch | ||||
|   | ||||
| @@ -18,7 +18,7 @@ def register(app, uri, file_or_directory, pattern, | ||||
|     #       make a good effort here.  Modified-since is nice, but we could | ||||
|     #       also look into etags, expires, and caching | ||||
|     """ | ||||
|     Registers a static directory handler with Sanic by adding a route to the | ||||
|     Register a static directory handler with Sanic by adding a route to the | ||||
|     router and registering a handler. | ||||
|  | ||||
|     :param app: Sanic | ||||
|   | ||||
| @@ -6,7 +6,11 @@ PORT = 42101 | ||||
|  | ||||
|  | ||||
| async def local_request(method, uri, cookies=None, *args, **kwargs): | ||||
|     url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri) | ||||
|     if uri.startswith(('http:', 'https:', 'ftp:', 'ftps://' '//')): | ||||
|         url = uri | ||||
|     else: | ||||
|         url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri) | ||||
|  | ||||
|     log.info(url) | ||||
|     async with aiohttp.ClientSession(cookies=cookies) as session: | ||||
|         async with getattr( | ||||
| @@ -17,8 +21,8 @@ async def local_request(method, uri, cookies=None, *args, **kwargs): | ||||
|  | ||||
|  | ||||
| def sanic_endpoint_test(app, method='get', uri='/', gather_request=True, | ||||
|                         debug=False, server_kwargs={}, | ||||
|                         *request_args, **request_kwargs): | ||||
|                         debug=False, server_kwargs={}, *request_args, | ||||
|                         **request_kwargs): | ||||
|     results = [None, None] | ||||
|     exceptions = [] | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ from .constants import HTTP_METHODS | ||||
|  | ||||
|  | ||||
| class HTTPMethodView: | ||||
|     """ Simple class based implementation of view for the sanic. | ||||
|     """Simple class based implementation of view for the sanic. | ||||
|     You should implement methods (get, post, put, patch, delete) for the class | ||||
|     to every HTTP method you want to support. | ||||
|  | ||||
| @@ -45,9 +45,8 @@ class HTTPMethodView: | ||||
|  | ||||
|     @classmethod | ||||
|     def as_view(cls, *class_args, **class_kwargs): | ||||
|         """ Converts the class into an actual view function that can be used | ||||
|         with the routing system. | ||||
|  | ||||
|         """Return view function for use with the routing system, that | ||||
|         dispatches request to appropriate handler method. | ||||
|         """ | ||||
|         def view(*args, **kwargs): | ||||
|             self = view.view_class(*class_args, **class_kwargs) | ||||
| @@ -66,7 +65,7 @@ class HTTPMethodView: | ||||
|  | ||||
|  | ||||
| class CompositionView: | ||||
|     """ Simple method-function mapped view for the sanic. | ||||
|     """Simple method-function mapped view for the sanic. | ||||
|     You can add handler functions to methods (get, post, put, patch, delete) | ||||
|     for every HTTP method you want to support. | ||||
|  | ||||
|   | ||||
| @@ -5,11 +5,19 @@ from sanic import Sanic | ||||
| from sanic.response import text | ||||
| from sanic.views import HTTPMethodView | ||||
| from sanic.blueprints import Blueprint | ||||
| from sanic.utils import sanic_endpoint_test | ||||
| from sanic.utils import sanic_endpoint_test, PORT as test_port | ||||
| from sanic.exceptions import URLBuildError | ||||
|  | ||||
| import string | ||||
|  | ||||
| URL_FOR_ARGS1 = dict(arg1=['v1', 'v2']) | ||||
| URL_FOR_VALUE1 = '/myurl?arg1=v1&arg1=v2' | ||||
| URL_FOR_ARGS2 = dict(arg1=['v1', 'v2'], _anchor='anchor') | ||||
| URL_FOR_VALUE2 = '/myurl?arg1=v1&arg1=v2#anchor' | ||||
| URL_FOR_ARGS3 = dict(arg1='v1', _anchor='anchor', _scheme='http', | ||||
|                      _server='localhost:{}'.format(test_port), _external=True) | ||||
| URL_FOR_VALUE3 = 'http://localhost:{}/myurl?arg1=v1#anchor'.format(test_port) | ||||
|  | ||||
|  | ||||
| def _generate_handlers_from_names(app, l): | ||||
|     for name in l: | ||||
| @@ -39,6 +47,23 @@ def test_simple_url_for_getting(simple_app): | ||||
|         assert response.text == letter | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('args,url', | ||||
|                          [(URL_FOR_ARGS1, URL_FOR_VALUE1), | ||||
|                           (URL_FOR_ARGS2, URL_FOR_VALUE2), | ||||
|                           (URL_FOR_ARGS3, URL_FOR_VALUE3)]) | ||||
| def test_simple_url_for_getting_with_more_params(args, url): | ||||
|     app = Sanic('more_url_build') | ||||
|  | ||||
|     @app.route('/myurl') | ||||
|     def passes(request): | ||||
|         return text('this should pass') | ||||
|  | ||||
|     assert url == app.url_for('passes', **args) | ||||
|     request, response = sanic_endpoint_test(app, uri=url) | ||||
|     assert response.status == 200 | ||||
|     assert response.text == 'this should pass' | ||||
|  | ||||
|  | ||||
| def test_fails_if_endpoint_not_found(): | ||||
|     app = Sanic('fail_url_build') | ||||
|  | ||||
| @@ -75,6 +100,19 @@ def test_fails_url_build_if_param_not_passed(): | ||||
|     assert 'Required parameter `Z` was not passed to url_for' in str(e.value) | ||||
|  | ||||
|  | ||||
| def test_fails_url_build_if_params_not_passed(): | ||||
|     app = Sanic('fail_url_build') | ||||
|  | ||||
|     @app.route('/fail') | ||||
|     def fail(): | ||||
|         return text('this should fail') | ||||
|  | ||||
|     with pytest.raises(ValueError) as e: | ||||
|         app.url_for('fail', _scheme='http') | ||||
|  | ||||
|     assert str(e.value) == 'When specifying _scheme, _external must be True' | ||||
|  | ||||
|  | ||||
| COMPLEX_PARAM_URL = ( | ||||
|     '/<foo:int>/<four_letter_string:[A-z]{4}>/' | ||||
|     '<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:number>') | ||||
| @@ -179,11 +217,11 @@ def blueprint_app(): | ||||
|         return text( | ||||
|             'foo from first : {}'.format(param)) | ||||
|  | ||||
|     @second_print.route('/foo') # noqa | ||||
|     @second_print.route('/foo')  # noqa | ||||
|     def foo(): | ||||
|         return text('foo from second') | ||||
|  | ||||
|     @second_print.route('/foo/<param>') # noqa | ||||
|     @second_print.route('/foo/<param>')  # noqa | ||||
|     def foo_with_param(request, param): | ||||
|         return text( | ||||
|             'foo from second : {}'.format(param)) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Angus Hollands
					Angus Hollands