diff --git a/docs/Makefile b/docs/Makefile index 92dd33a1..d0c3cbf1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,7 +6,7 @@ SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source -BUILDDIR = _build +BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 1aa8048f..00000000 --- a/docs/api.rst +++ /dev/null @@ -1,717 +0,0 @@ -API -=== - -.. module:: flask - -This part of the documentation covers all the interfaces of Flask. For -parts where Flask depends on external libraries, we document the most -important right here and provide links to the canonical documentation. - - -Application Object ------------------- - -.. autoclass:: Flask - :members: - :inherited-members: - - -Blueprint Objects ------------------ - -.. autoclass:: Blueprint - :members: - :inherited-members: - -Incoming Request Data ---------------------- - -.. autoclass:: Request - :members: - :inherited-members: - :exclude-members: json_module - -.. attribute:: request - - To access incoming request data, you can use the global `request` - object. Flask parses incoming request data for you and gives you - access to it through that global object. Internally Flask makes - sure that you always get the correct data for the active thread if you - are in a multithreaded environment. - - This is a proxy. See :ref:`notes-on-proxies` for more information. - - The request object is an instance of a :class:`~flask.Request`. - - -Response Objects ----------------- - -.. autoclass:: flask.Response - :members: - :inherited-members: - :exclude-members: json_module - -Sessions --------- - -If you have set :attr:`Flask.secret_key` (or configured it from -:data:`SECRET_KEY`) you can use sessions in Flask applications. A session makes -it possible to remember information from one request to another. The way Flask -does this is by using a signed cookie. The user can look at the session -contents, but can't modify it unless they know the secret key, so make sure to -set that to something complex and unguessable. - -To access the current session you can use the :class:`session` object: - -.. class:: session - - The session object works pretty much like an ordinary dict, with the - difference that it keeps track of modifications. - - This is a proxy. See :ref:`notes-on-proxies` for more information. - - The following attributes are interesting: - - .. attribute:: new - - ``True`` if the session is new, ``False`` otherwise. - - .. attribute:: modified - - ``True`` if the session object detected a modification. Be advised - that modifications on mutable structures are not picked up - automatically, in that situation you have to explicitly set the - attribute to ``True`` yourself. Here an example:: - - # this change is not picked up because a mutable object (here - # a list) is changed. - session['objects'].append(42) - # so mark it as modified yourself - session.modified = True - - .. attribute:: permanent - - If set to ``True`` the session lives for - :attr:`~flask.Flask.permanent_session_lifetime` seconds. The - default is 31 days. If set to ``False`` (which is the default) the - session will be deleted when the user closes the browser. - - -Session Interface ------------------ - -.. versionadded:: 0.8 - -The session interface provides a simple way to replace the session -implementation that Flask is using. - -.. currentmodule:: flask.sessions - -.. autoclass:: SessionInterface - :members: - -.. autoclass:: SecureCookieSessionInterface - :members: - -.. autoclass:: SecureCookieSession - :members: - -.. autoclass:: NullSession - :members: - -.. autoclass:: SessionMixin - :members: - -.. admonition:: Notice - - The :data:`PERMANENT_SESSION_LIFETIME` config can be an integer or ``timedelta``. - The :attr:`~flask.Flask.permanent_session_lifetime` attribute is always a - ``timedelta``. - - -Test Client ------------ - -.. currentmodule:: flask.testing - -.. autoclass:: FlaskClient - :members: - - -Test CLI Runner ---------------- - -.. currentmodule:: flask.testing - -.. autoclass:: FlaskCliRunner - :members: - - -Application Globals -------------------- - -.. currentmodule:: flask - -To share data that is valid for one request only from one function to -another, a global variable is not good enough because it would break in -threaded environments. Flask provides you with a special object that -ensures it is only valid for the active request and that will return -different values for each request. In a nutshell: it does the right -thing, like it does for :class:`request` and :class:`session`. - -.. data:: g - - A namespace object that can store data during an - :doc:`application context `. This is an instance of - :attr:`Flask.app_ctx_globals_class`, which defaults to - :class:`ctx._AppCtxGlobals`. - - This is a good place to store resources during a request. For - example, a ``before_request`` function could load a user object from - a session id, then set ``g.user`` to be used in the view function. - - This is a proxy. See :ref:`notes-on-proxies` for more information. - - .. versionchanged:: 0.10 - Bound to the application context instead of the request context. - -.. autoclass:: flask.ctx._AppCtxGlobals - :members: - - -Useful Functions and Classes ----------------------------- - -.. data:: current_app - - A proxy to the application handling the current request. This is - useful to access the application without needing to import it, or if - it can't be imported, such as when using the application factory - pattern or in blueprints and extensions. - - This is only available when an - :doc:`application context ` is pushed. This happens - automatically during requests and CLI commands. It can be controlled - manually with :meth:`~flask.Flask.app_context`. - - This is a proxy. See :ref:`notes-on-proxies` for more information. - -.. autofunction:: has_request_context - -.. autofunction:: copy_current_request_context - -.. autofunction:: has_app_context - -.. autofunction:: url_for - -.. autofunction:: abort - -.. autofunction:: redirect - -.. autofunction:: make_response - -.. autofunction:: after_this_request - -.. autofunction:: send_file - -.. autofunction:: send_from_directory - - -Message Flashing ----------------- - -.. autofunction:: flash - -.. autofunction:: get_flashed_messages - - -JSON Support ------------- - -.. module:: flask.json - -Flask uses Python's built-in :mod:`json` module for handling JSON by -default. The JSON implementation can be changed by assigning a different -provider to :attr:`flask.Flask.json_provider_class` or -:attr:`flask.Flask.json`. The functions provided by ``flask.json`` will -use methods on ``app.json`` if an app context is active. - -Jinja's ``|tojson`` filter is configured to use the app's JSON provider. -The filter marks the output with ``|safe``. Use it to render data inside -HTML `` - -.. autofunction:: jsonify - -.. autofunction:: dumps - -.. autofunction:: dump - -.. autofunction:: loads - -.. autofunction:: load - -.. autoclass:: flask.json.provider.JSONProvider - :members: - :member-order: bysource - -.. autoclass:: flask.json.provider.DefaultJSONProvider - :members: - :member-order: bysource - -.. automodule:: flask.json.tag - - -Template Rendering ------------------- - -.. currentmodule:: flask - -.. autofunction:: render_template - -.. autofunction:: render_template_string - -.. autofunction:: stream_template - -.. autofunction:: stream_template_string - -.. autofunction:: get_template_attribute - -Configuration -------------- - -.. autoclass:: Config - :members: - - -Stream Helpers --------------- - -.. autofunction:: stream_with_context - -Useful Internals ----------------- - -.. autoclass:: flask.ctx.RequestContext - :members: - -.. data:: flask.globals.request_ctx - - The current :class:`~flask.ctx.RequestContext`. If a request context - is not active, accessing attributes on this proxy will raise a - ``RuntimeError``. - - This is an internal object that is essential to how Flask handles - requests. Accessing this should not be needed in most cases. Most - likely you want :data:`request` and :data:`session` instead. - -.. autoclass:: flask.ctx.AppContext - :members: - -.. data:: flask.globals.app_ctx - - The current :class:`~flask.ctx.AppContext`. If an app context is not - active, accessing attributes on this proxy will raise a - ``RuntimeError``. - - This is an internal object that is essential to how Flask handles - requests. Accessing this should not be needed in most cases. Most - likely you want :data:`current_app` and :data:`g` instead. - -.. autoclass:: flask.blueprints.BlueprintSetupState - :members: - -.. _core-signals-list: - -Signals -------- - -Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introduction. - -.. _blinker: https://blinker.readthedocs.io/ - -.. data:: template_rendered - - This signal is sent when a template was successfully rendered. The - signal is invoked with the instance of the template as `template` - and the context as dictionary (named `context`). - - Example subscriber:: - - def log_template_renders(sender, template, context, **extra): - sender.logger.debug('Rendering template "%s" with context %s', - template.name or 'string template', - context) - - from flask import template_rendered - template_rendered.connect(log_template_renders, app) - -.. data:: flask.before_render_template - :noindex: - - This signal is sent before template rendering process. The - signal is invoked with the instance of the template as `template` - and the context as dictionary (named `context`). - - Example subscriber:: - - def log_template_renders(sender, template, context, **extra): - sender.logger.debug('Rendering template "%s" with context %s', - template.name or 'string template', - context) - - from flask import before_render_template - before_render_template.connect(log_template_renders, app) - -.. data:: request_started - - This signal is sent when the request context is set up, before - any request processing happens. Because the request context is already - bound, the subscriber can access the request with the standard global - proxies such as :class:`~flask.request`. - - Example subscriber:: - - def log_request(sender, **extra): - sender.logger.debug('Request context is set up') - - from flask import request_started - request_started.connect(log_request, app) - -.. data:: request_finished - - This signal is sent right before the response is sent to the client. - It is passed the response to be sent named `response`. - - Example subscriber:: - - def log_response(sender, response, **extra): - sender.logger.debug('Request context is about to close down. ' - 'Response: %s', response) - - from flask import request_finished - request_finished.connect(log_response, app) - -.. data:: got_request_exception - - This signal is sent when an unhandled exception happens during - request processing, including when debugging. The exception is - passed to the subscriber as ``exception``. - - This signal is not sent for - :exc:`~werkzeug.exceptions.HTTPException`, or other exceptions that - have error handlers registered, unless the exception was raised from - an error handler. - - This example shows how to do some extra logging if a theoretical - ``SecurityException`` was raised: - - .. code-block:: python - - from flask import got_request_exception - - def log_security_exception(sender, exception, **extra): - if not isinstance(exception, SecurityException): - return - - security_logger.exception( - f"SecurityException at {request.url!r}", - exc_info=exception, - ) - - got_request_exception.connect(log_security_exception, app) - -.. data:: request_tearing_down - - This signal is sent when the request is tearing down. This is always - called, even if an exception is caused. Currently functions listening - to this signal are called after the regular teardown handlers, but this - is not something you can rely on. - - Example subscriber:: - - def close_db_connection(sender, **extra): - session.close() - - from flask import request_tearing_down - request_tearing_down.connect(close_db_connection, app) - - As of Flask 0.9, this will also be passed an `exc` keyword argument - that has a reference to the exception that caused the teardown if - there was one. - -.. data:: appcontext_tearing_down - - This signal is sent when the app context is tearing down. This is always - called, even if an exception is caused. Currently functions listening - to this signal are called after the regular teardown handlers, but this - is not something you can rely on. - - Example subscriber:: - - def close_db_connection(sender, **extra): - session.close() - - from flask import appcontext_tearing_down - appcontext_tearing_down.connect(close_db_connection, app) - - This will also be passed an `exc` keyword argument that has a reference - to the exception that caused the teardown if there was one. - -.. data:: appcontext_pushed - - This signal is sent when an application context is pushed. The sender - is the application. This is usually useful for unittests in order to - temporarily hook in information. For instance it can be used to - set a resource early onto the `g` object. - - Example usage:: - - from contextlib import contextmanager - from flask import appcontext_pushed - - @contextmanager - def user_set(app, user): - def handler(sender, **kwargs): - g.user = user - with appcontext_pushed.connected_to(handler, app): - yield - - And in the testcode:: - - def test_user_me(self): - with user_set(app, 'john'): - c = app.test_client() - resp = c.get('/users/me') - assert resp.data == 'username=john' - - .. versionadded:: 0.10 - -.. data:: appcontext_popped - - This signal is sent when an application context is popped. The sender - is the application. This usually falls in line with the - :data:`appcontext_tearing_down` signal. - - .. versionadded:: 0.10 - -.. data:: message_flashed - - This signal is sent when the application is flashing a message. The - messages is sent as `message` keyword argument and the category as - `category`. - - Example subscriber:: - - recorded = [] - def record(sender, message, category, **extra): - recorded.append((message, category)) - - from flask import message_flashed - message_flashed.connect(record, app) - - .. versionadded:: 0.10 - - -Class-Based Views ------------------ - -.. versionadded:: 0.7 - -.. currentmodule:: None - -.. autoclass:: flask.views.View - :members: - -.. autoclass:: flask.views.MethodView - :members: - -.. _url-route-registrations: - -URL Route Registrations ------------------------ - -Generally there are three ways to define rules for the routing system: - -1. You can use the :meth:`flask.Flask.route` decorator. -2. You can use the :meth:`flask.Flask.add_url_rule` function. -3. You can directly access the underlying Werkzeug routing system - which is exposed as :attr:`flask.Flask.url_map`. - -Variable parts in the route can be specified with angular brackets -(``/user/``). By default a variable part in the URL accepts any -string without a slash however a different converter can be specified as -well by using ````. - -Variable parts are passed to the view function as keyword arguments. - -The following converters are available: - -=========== =============================================== -`string` accepts any text without a slash (the default) -`int` accepts integers -`float` like `int` but for floating point values -`path` like the default but also accepts slashes -`any` matches one of the items provided -`uuid` accepts UUID strings -=========== =============================================== - -Custom converters can be defined using :attr:`flask.Flask.url_map`. - -Here are some examples:: - - @app.route('/') - def index(): - pass - - @app.route('/') - def show_user(username): - pass - - @app.route('/post/') - def show_post(post_id): - pass - -An important detail to keep in mind is how Flask deals with trailing -slashes. The idea is to keep each URL unique so the following rules -apply: - -1. If a rule ends with a slash and is requested without a slash by the - user, the user is automatically redirected to the same page with a - trailing slash attached. -2. If a rule does not end with a trailing slash and the user requests the - page with a trailing slash, a 404 not found is raised. - -This is consistent with how web servers deal with static files. This -also makes it possible to use relative link targets safely. - -You can also define multiple rules for the same function. They have to be -unique however. Defaults can also be specified. Here for example is a -definition for a URL that accepts an optional page:: - - @app.route('/users/', defaults={'page': 1}) - @app.route('/users/page/') - def show_users(page): - pass - -This specifies that ``/users/`` will be the URL for page one and -``/users/page/N`` will be the URL for page ``N``. - -If a URL contains a default value, it will be redirected to its simpler -form with a 301 redirect. In the above example, ``/users/page/1`` will -be redirected to ``/users/``. If your route handles ``GET`` and ``POST`` -requests, make sure the default route only handles ``GET``, as redirects -can't preserve form data. :: - - @app.route('/region/', defaults={'id': 1}) - @app.route('/region/', methods=['GET', 'POST']) - def region(id): - pass - -Here are the parameters that :meth:`~flask.Flask.route` and -:meth:`~flask.Flask.add_url_rule` accept. The only difference is that -with the route parameter the view function is defined with the decorator -instead of the `view_func` parameter. - -=============== ========================================================== -`rule` the URL rule as string -`endpoint` the endpoint for the registered URL rule. Flask itself - assumes that the name of the view function is the name - of the endpoint if not explicitly stated. -`view_func` the function to call when serving a request to the - provided endpoint. If this is not provided one can - specify the function later by storing it in the - :attr:`~flask.Flask.view_functions` dictionary with the - endpoint as key. -`defaults` A dictionary with defaults for this rule. See the - example above for how defaults work. -`subdomain` specifies the rule for the subdomain in case subdomain - matching is in use. If not specified the default - subdomain is assumed. -`**options` the options to be forwarded to the underlying - :class:`~werkzeug.routing.Rule` object. A change to - Werkzeug is handling of method options. methods is a list - of methods this rule should be limited to (``GET``, ``POST`` - etc.). By default a rule just listens for ``GET`` (and - implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is - implicitly added and handled by the standard request - handling. They have to be specified as keyword arguments. -=============== ========================================================== - - -View Function Options ---------------------- - -For internal usage the view functions can have some attributes attached to -customize behavior the view function would normally not have control over. -The following attributes can be provided optionally to either override -some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior: - -- `__name__`: The name of a function is by default used as endpoint. If - endpoint is provided explicitly this value is used. Additionally this - will be prefixed with the name of the blueprint by default which - cannot be customized from the function itself. - -- `methods`: If methods are not provided when the URL rule is added, - Flask will look on the view function object itself if a `methods` - attribute exists. If it does, it will pull the information for the - methods from there. - -- `provide_automatic_options`: if this attribute is set Flask will - either force enable or disable the automatic implementation of the - HTTP ``OPTIONS`` response. This can be useful when working with - decorators that want to customize the ``OPTIONS`` response on a per-view - basis. - -- `required_methods`: if this attribute is set, Flask will always add - these methods when registering a URL rule even if the methods were - explicitly overridden in the ``route()`` call. - -Full example:: - - def index(): - if request.method == 'OPTIONS': - # custom options handling here - ... - return 'Hello World!' - index.provide_automatic_options = False - index.methods = ['GET', 'OPTIONS'] - - app.add_url_rule('/', index) - -.. versionadded:: 0.8 - The `provide_automatic_options` functionality was added. - -Command Line Interface ----------------------- - -.. currentmodule:: flask.cli - -.. autoclass:: FlaskGroup - :members: - -.. autoclass:: AppGroup - :members: - -.. autoclass:: ScriptInfo - :members: - -.. autofunction:: load_dotenv - -.. autofunction:: with_appcontext - -.. autofunction:: pass_script_info - - Marks a function so that an instance of :class:`ScriptInfo` is passed - as first argument to the click callback. - -.. autodata:: run_command - -.. autodata:: shell_command diff --git a/docs/appcontext.rst b/docs/appcontext.rst deleted file mode 100644 index 5509a9a7..00000000 --- a/docs/appcontext.rst +++ /dev/null @@ -1,147 +0,0 @@ -.. currentmodule:: flask - -The Application Context -======================= - -The application context keeps track of the application-level data during -a request, CLI command, or other activity. Rather than passing the -application around to each function, the :data:`current_app` and -:data:`g` proxies are accessed instead. - -This is similar to :doc:`/reqcontext`, which keeps track of -request-level data during a request. A corresponding application context -is pushed when a request context is pushed. - -Purpose of the Context ----------------------- - -The :class:`Flask` application object has attributes, such as -:attr:`~Flask.config`, that are useful to access within views and -:doc:`CLI commands `. However, importing the ``app`` instance -within the modules in your project is prone to circular import issues. -When using the :doc:`app factory pattern ` or -writing reusable :doc:`blueprints ` or -:doc:`extensions ` there won't be an ``app`` instance to -import at all. - -Flask solves this issue with the *application context*. Rather than -referring to an ``app`` directly, you use the :data:`current_app` -proxy, which points to the application handling the current activity. - -Flask automatically *pushes* an application context when handling a -request. View functions, error handlers, and other functions that run -during a request will have access to :data:`current_app`. - -Flask will also automatically push an app context when running CLI -commands registered with :attr:`Flask.cli` using ``@app.cli.command()``. - - -Lifetime of the Context ------------------------ - -The application context is created and destroyed as necessary. When a -Flask application begins handling a request, it pushes an application -context and a :doc:`request context `. When the request -ends it pops the request context then the application context. -Typically, an application context will have the same lifetime as a -request. - -See :doc:`/reqcontext` for more information about how the contexts work -and the full life cycle of a request. - - -Manually Push a Context ------------------------ - -If you try to access :data:`current_app`, or anything that uses it, -outside an application context, you'll get this error message: - -.. code-block:: pytb - - RuntimeError: Working outside of application context. - - This typically means that you attempted to use functionality that - needed to interface with the current application object in some way. - To solve this, set up an application context with app.app_context(). - -If you see that error while configuring your application, such as when -initializing an extension, you can push a context manually since you -have direct access to the ``app``. Use :meth:`~Flask.app_context` in a -``with`` block, and everything that runs in the block will have access -to :data:`current_app`. :: - - def create_app(): - app = Flask(__name__) - - with app.app_context(): - init_db() - - return app - -If you see that error somewhere else in your code not related to -configuring the application, it most likely indicates that you should -move that code into a view function or CLI command. - - -Storing Data ------------- - -The application context is a good place to store common data during a -request or CLI command. Flask provides the :data:`g object ` for this -purpose. It is a simple namespace object that has the same lifetime as -an application context. - -.. note:: - The ``g`` name stands for "global", but that is referring to the - data being global *within a context*. The data on ``g`` is lost - after the context ends, and it is not an appropriate place to store - data between requests. Use the :data:`session` or a database to - store data across requests. - -A common use for :data:`g` is to manage resources during a request. - -1. ``get_X()`` creates resource ``X`` if it does not exist, caching it - as ``g.X``. -2. ``teardown_X()`` closes or otherwise deallocates the resource if it - exists. It is registered as a :meth:`~Flask.teardown_appcontext` - handler. - -For example, you can manage a database connection using this pattern:: - - from flask import g - - def get_db(): - if 'db' not in g: - g.db = connect_to_database() - - return g.db - - @app.teardown_appcontext - def teardown_db(exception): - db = g.pop('db', None) - - if db is not None: - db.close() - -During a request, every call to ``get_db()`` will return the same -connection, and it will be closed automatically at the end of the -request. - -You can use :class:`~werkzeug.local.LocalProxy` to make a new context -local from ``get_db()``:: - - from werkzeug.local import LocalProxy - db = LocalProxy(get_db) - -Accessing ``db`` will call ``get_db`` internally, in the same way that -:data:`current_app` works. - - -Events and Signals ------------------- - -The application will call functions registered with :meth:`~Flask.teardown_appcontext` -when the application context is popped. - -The following signals are sent: :data:`appcontext_pushed`, -:data:`appcontext_tearing_down`, and :data:`appcontext_popped`. diff --git a/docs/async-await.rst b/docs/async-await.rst deleted file mode 100644 index 16b61945..00000000 --- a/docs/async-await.rst +++ /dev/null @@ -1,125 +0,0 @@ -.. _async_await: - -Using ``async`` and ``await`` -============================= - -.. versionadded:: 2.0 - -Routes, error handlers, before request, after request, and teardown -functions can all be coroutine functions if Flask is installed with the -``async`` extra (``pip install flask[async]``). This allows views to be -defined with ``async def`` and use ``await``. - -.. code-block:: python - - @app.route("/get-data") - async def get_data(): - data = await async_db_query(...) - return jsonify(data) - -Pluggable class-based views also support handlers that are implemented as -coroutines. This applies to the :meth:`~flask.views.View.dispatch_request` -method in views that inherit from the :class:`flask.views.View` class, as -well as all the HTTP method handlers in views that inherit from the -:class:`flask.views.MethodView` class. - -.. admonition:: Using ``async`` with greenlet - - When using gevent or eventlet to serve an application or patch the - runtime, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is - required. - - -Performance ------------ - -Async functions require an event loop to run. Flask, as a WSGI -application, uses one worker to handle one request/response cycle. -When a request comes in to an async view, Flask will start an event loop -in a thread, run the view function there, then return the result. - -Each request still ties up one worker, even for async views. The upside -is that you can run async code within a view, for example to make -multiple concurrent database queries, HTTP requests to an external API, -etc. However, the number of requests your application can handle at one -time will remain the same. - -**Async is not inherently faster than sync code.** Async is beneficial -when performing concurrent IO-bound tasks, but will probably not improve -CPU-bound tasks. Traditional Flask views will still be appropriate for -most use cases, but Flask's async support enables writing and using -code that wasn't possible natively before. - - -Background tasks ----------------- - -Async functions will run in an event loop until they complete, at -which stage the event loop will stop. This means any additional -spawned tasks that haven't completed when the async function completes -will be cancelled. Therefore you cannot spawn background tasks, for -example via ``asyncio.create_task``. - -If you wish to use background tasks it is best to use a task queue to -trigger background work, rather than spawn tasks in a view -function. With that in mind you can spawn asyncio tasks by serving -Flask with an ASGI server and utilising the asgiref WsgiToAsgi adapter -as described in :doc:`deploying/asgi`. This works as the adapter creates -an event loop that runs continually. - - -When to use Quart instead -------------------------- - -Flask's async support is less performant than async-first frameworks due -to the way it is implemented. If you have a mainly async codebase it -would make sense to consider `Quart`_. Quart is a reimplementation of -Flask based on the `ASGI`_ standard instead of WSGI. This allows it to -handle many concurrent requests, long running requests, and websockets -without requiring multiple worker processes or threads. - -It has also already been possible to run Flask with Gevent or Eventlet -to get many of the benefits of async request handling. These libraries -patch low-level Python functions to accomplish this, whereas ``async``/ -``await`` and ASGI use standard, modern Python capabilities. Deciding -whether you should use Flask, Quart, or something else is ultimately up -to understanding the specific needs of your project. - -.. _Quart: https://github.com/pallets/quart -.. _ASGI: https://asgi.readthedocs.io/en/latest/ - - -Extensions ----------- - -Flask extensions predating Flask's async support do not expect async views. -If they provide decorators to add functionality to views, those will probably -not work with async views because they will not await the function or be -awaitable. Other functions they provide will not be awaitable either and -will probably be blocking if called within an async view. - -Extension authors can support async functions by utilising the -:meth:`flask.Flask.ensure_sync` method. For example, if the extension -provides a view function decorator add ``ensure_sync`` before calling -the decorated function, - -.. code-block:: python - - def extension(func): - @wraps(func) - def wrapper(*args, **kwargs): - ... # Extension logic - return current_app.ensure_sync(func)(*args, **kwargs) - - return wrapper - -Check the changelog of the extension you want to use to see if they've -implemented async support, or make a feature request or PR to them. - - -Other event loops ------------------ - -At the moment Flask only supports :mod:`asyncio`. It's possible to -override :meth:`flask.Flask.ensure_sync` to change how async functions -are wrapped to use a different library. diff --git a/docs/blueprints.rst b/docs/blueprints.rst deleted file mode 100644 index d5cf3d82..00000000 --- a/docs/blueprints.rst +++ /dev/null @@ -1,315 +0,0 @@ -Modular Applications with Blueprints -==================================== - -.. currentmodule:: flask - -.. versionadded:: 0.7 - -Flask uses a concept of *blueprints* for making application components and -supporting common patterns within an application or across applications. -Blueprints can greatly simplify how large applications work and provide a -central means for Flask extensions to register operations on applications. -A :class:`Blueprint` object works similarly to a :class:`Flask` -application object, but it is not actually an application. Rather it is a -*blueprint* of how to construct or extend an application. - -Why Blueprints? ---------------- - -Blueprints in Flask are intended for these cases: - -* Factor an application into a set of blueprints. This is ideal for - larger applications; a project could instantiate an application object, - initialize several extensions, and register a collection of blueprints. -* Register a blueprint on an application at a URL prefix and/or subdomain. - Parameters in the URL prefix/subdomain become common view arguments - (with defaults) across all view functions in the blueprint. -* Register a blueprint multiple times on an application with different URL - rules. -* Provide template filters, static files, templates, and other utilities - through blueprints. A blueprint does not have to implement applications - or view functions. -* Register a blueprint on an application for any of these cases when - initializing a Flask extension. - -A blueprint in Flask is not a pluggable app because it is not actually an -application -- it's a set of operations which can be registered on an -application, even multiple times. Why not have multiple application -objects? You can do that (see :doc:`/patterns/appdispatch`), but your -applications will have separate configs and will be managed at the WSGI -layer. - -Blueprints instead provide separation at the Flask level, share -application config, and can change an application object as necessary with -being registered. The downside is that you cannot unregister a blueprint -once an application was created without having to destroy the whole -application object. - -The Concept of Blueprints -------------------------- - -The basic concept of blueprints is that they record operations to execute -when registered on an application. Flask associates view functions with -blueprints when dispatching requests and generating URLs from one endpoint -to another. - -My First Blueprint ------------------- - -This is what a very basic blueprint looks like. In this case we want to -implement a blueprint that does simple rendering of static templates:: - - from flask import Blueprint, render_template, abort - from jinja2 import TemplateNotFound - - simple_page = Blueprint('simple_page', __name__, - template_folder='templates') - - @simple_page.route('/', defaults={'page': 'index'}) - @simple_page.route('/') - def show(page): - try: - return render_template(f'pages/{page}.html') - except TemplateNotFound: - abort(404) - -When you bind a function with the help of the ``@simple_page.route`` -decorator, the blueprint will record the intention of registering the -function ``show`` on the application when it's later registered. -Additionally it will prefix the endpoint of the function with the -name of the blueprint which was given to the :class:`Blueprint` -constructor (in this case also ``simple_page``). The blueprint's name -does not modify the URL, only the endpoint. - -Registering Blueprints ----------------------- - -So how do you register that blueprint? Like this:: - - from flask import Flask - from yourapplication.simple_page import simple_page - - app = Flask(__name__) - app.register_blueprint(simple_page) - -If you check the rules registered on the application, you will find -these:: - - >>> app.url_map - Map([' (HEAD, OPTIONS, GET) -> static>, - ' (HEAD, OPTIONS, GET) -> simple_page.show>, - simple_page.show>]) - -The first one is obviously from the application itself for the static -files. The other two are for the `show` function of the ``simple_page`` -blueprint. As you can see, they are also prefixed with the name of the -blueprint and separated by a dot (``.``). - -Blueprints however can also be mounted at different locations:: - - app.register_blueprint(simple_page, url_prefix='/pages') - -And sure enough, these are the generated rules:: - - >>> app.url_map - Map([' (HEAD, OPTIONS, GET) -> static>, - ' (HEAD, OPTIONS, GET) -> simple_page.show>, - simple_page.show>]) - -On top of that you can register blueprints multiple times though not every -blueprint might respond properly to that. In fact it depends on how the -blueprint is implemented if it can be mounted more than once. - -Nesting Blueprints ------------------- - -It is possible to register a blueprint on another blueprint. - -.. code-block:: python - - parent = Blueprint('parent', __name__, url_prefix='/parent') - child = Blueprint('child', __name__, url_prefix='/child') - parent.register_blueprint(child) - app.register_blueprint(parent) - -The child blueprint will gain the parent's name as a prefix to its -name, and child URLs will be prefixed with the parent's URL prefix. - -.. code-block:: python - - url_for('parent.child.create') - /parent/child/create - -In addition a child blueprint's will gain their parent's subdomain, -with their subdomain as prefix if present i.e. - -.. code-block:: python - - parent = Blueprint('parent', __name__, subdomain='parent') - child = Blueprint('child', __name__, subdomain='child') - parent.register_blueprint(child) - app.register_blueprint(parent) - - url_for('parent.child.create', _external=True) - "child.parent.domain.tld" - -Blueprint-specific before request functions, etc. registered with the -parent will trigger for the child. If a child does not have an error -handler that can handle a given exception, the parent's will be tried. - - -Blueprint Resources -------------------- - -Blueprints can provide resources as well. Sometimes you might want to -introduce a blueprint only for the resources it provides. - -Blueprint Resource Folder -````````````````````````` - -Like for regular applications, blueprints are considered to be contained -in a folder. While multiple blueprints can originate from the same folder, -it does not have to be the case and it's usually not recommended. - -The folder is inferred from the second argument to :class:`Blueprint` which -is usually `__name__`. This argument specifies what logical Python -module or package corresponds to the blueprint. If it points to an actual -Python package that package (which is a folder on the filesystem) is the -resource folder. If it's a module, the package the module is contained in -will be the resource folder. You can access the -:attr:`Blueprint.root_path` property to see what the resource folder is:: - - >>> simple_page.root_path - '/Users/username/TestProject/yourapplication' - -To quickly open sources from this folder you can use the -:meth:`~Blueprint.open_resource` function:: - - with simple_page.open_resource('static/style.css') as f: - code = f.read() - -Static Files -```````````` - -A blueprint can expose a folder with static files by providing the path -to the folder on the filesystem with the ``static_folder`` argument. -It is either an absolute path or relative to the blueprint's location:: - - admin = Blueprint('admin', __name__, static_folder='static') - -By default the rightmost part of the path is where it is exposed on the -web. This can be changed with the ``static_url_path`` argument. Because the -folder is called ``static`` here it will be available at the -``url_prefix`` of the blueprint + ``/static``. If the blueprint -has the prefix ``/admin``, the static URL will be ``/admin/static``. - -The endpoint is named ``blueprint_name.static``. You can generate URLs -to it with :func:`url_for` like you would with the static folder of the -application:: - - url_for('admin.static', filename='style.css') - -However, if the blueprint does not have a ``url_prefix``, it is not -possible to access the blueprint's static folder. This is because the -URL would be ``/static`` in this case, and the application's ``/static`` -route takes precedence. Unlike template folders, blueprint static -folders are not searched if the file does not exist in the application -static folder. - -Templates -````````` - -If you want the blueprint to expose templates you can do that by providing -the `template_folder` parameter to the :class:`Blueprint` constructor:: - - admin = Blueprint('admin', __name__, template_folder='templates') - -For static files, the path can be absolute or relative to the blueprint -resource folder. - -The template folder is added to the search path of templates but with a lower -priority than the actual application's template folder. That way you can -easily override templates that a blueprint provides in the actual application. -This also means that if you don't want a blueprint template to be accidentally -overridden, make sure that no other blueprint or actual application template -has the same relative path. When multiple blueprints provide the same relative -template path the first blueprint registered takes precedence over the others. - - -So if you have a blueprint in the folder ``yourapplication/admin`` and you -want to render the template ``'admin/index.html'`` and you have provided -``templates`` as a `template_folder` you will have to create a file like -this: :file:`yourapplication/admin/templates/admin/index.html`. The reason -for the extra ``admin`` folder is to avoid getting our template overridden -by a template named ``index.html`` in the actual application template -folder. - -To further reiterate this: if you have a blueprint named ``admin`` and you -want to render a template called :file:`index.html` which is specific to this -blueprint, the best idea is to lay out your templates like this:: - - yourpackage/ - blueprints/ - admin/ - templates/ - admin/ - index.html - __init__.py - -And then when you want to render the template, use :file:`admin/index.html` as -the name to look up the template by. If you encounter problems loading -the correct templates enable the ``EXPLAIN_TEMPLATE_LOADING`` config -variable which will instruct Flask to print out the steps it goes through -to locate templates on every ``render_template`` call. - -Building URLs -------------- - -If you want to link from one page to another you can use the -:func:`url_for` function just like you normally would do just that you -prefix the URL endpoint with the name of the blueprint and a dot (``.``):: - - url_for('admin.index') - -Additionally if you are in a view function of a blueprint or a rendered -template and you want to link to another endpoint of the same blueprint, -you can use relative redirects by prefixing the endpoint with a dot only:: - - url_for('.index') - -This will link to ``admin.index`` for instance in case the current request -was dispatched to any other admin blueprint endpoint. - - -Blueprint Error Handlers ------------------------- - -Blueprints support the ``errorhandler`` decorator just like the :class:`Flask` -application object, so it is easy to make Blueprint-specific custom error -pages. - -Here is an example for a "404 Page Not Found" exception:: - - @simple_page.errorhandler(404) - def page_not_found(e): - return render_template('pages/404.html') - -Most errorhandlers will simply work as expected; however, there is a caveat -concerning handlers for 404 and 405 exceptions. These errorhandlers are only -invoked from an appropriate ``raise`` statement or a call to ``abort`` in another -of the blueprint's view functions; they are not invoked by, e.g., an invalid URL -access. This is because the blueprint does not "own" a certain URL space, so -the application instance has no way of knowing which blueprint error handler it -should run if given an invalid URL. If you would like to execute different -handling strategies for these errors based on URL prefixes, they may be defined -at the application level using the ``request`` proxy object:: - - @app.errorhandler(404) - @app.errorhandler(405) - def _handle_api_error(ex): - if request.path.startswith('/api/'): - return jsonify(error=str(ex)), ex.code - else: - return ex - -See :doc:`/errorhandling`. diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle new file mode 100644 index 00000000..cf5ce898 Binary files /dev/null and b/docs/build/doctrees/environment.pickle differ diff --git a/docs/build/doctrees/flask.doctree b/docs/build/doctrees/flask.doctree new file mode 100644 index 00000000..96358e9e Binary files /dev/null and b/docs/build/doctrees/flask.doctree differ diff --git a/docs/build/doctrees/flask.json.doctree b/docs/build/doctrees/flask.json.doctree new file mode 100644 index 00000000..fad21093 Binary files /dev/null and b/docs/build/doctrees/flask.json.doctree differ diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree new file mode 100644 index 00000000..e5b77521 Binary files /dev/null and b/docs/build/doctrees/index.doctree differ diff --git a/docs/build/doctrees/modules.doctree b/docs/build/doctrees/modules.doctree new file mode 100644 index 00000000..f9947e42 Binary files /dev/null and b/docs/build/doctrees/modules.doctree differ diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo new file mode 100644 index 00000000..8a2d869b --- /dev/null +++ b/docs/build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 21a39fdf72bffce870db8834b83d0887 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_sources/flask.json.rst.txt b/docs/build/html/_sources/flask.json.rst.txt new file mode 100644 index 00000000..1ed514ee --- /dev/null +++ b/docs/build/html/_sources/flask.json.rst.txt @@ -0,0 +1,29 @@ +flask.json package +================== + +Submodules +---------- + +flask.json.provider module +-------------------------- + +.. automodule:: flask.json.provider + :members: + :undoc-members: + :show-inheritance: + +flask.json.tag module +--------------------- + +.. automodule:: flask.json.tag + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: flask.json + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/flask.rst.txt b/docs/build/html/_sources/flask.rst.txt new file mode 100644 index 00000000..7ebba00b --- /dev/null +++ b/docs/build/html/_sources/flask.rst.txt @@ -0,0 +1,149 @@ +flask package +============= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + flask.json + +Submodules +---------- + +flask.app module +---------------- + +.. automodule:: flask.app + :members: + :undoc-members: + :show-inheritance: + +flask.blueprints module +----------------------- + +.. automodule:: flask.blueprints + :members: + :undoc-members: + :show-inheritance: + +flask.cli module +---------------- + +.. automodule:: flask.cli + :members: + :undoc-members: + :show-inheritance: + +flask.config module +------------------- + +.. automodule:: flask.config + :members: + :undoc-members: + :show-inheritance: + +flask.ctx module +---------------- + +.. automodule:: flask.ctx + :members: + :undoc-members: + :show-inheritance: + +flask.debughelpers module +------------------------- + +.. automodule:: flask.debughelpers + :members: + :undoc-members: + :show-inheritance: + +flask.globals module +-------------------- + +.. automodule:: flask.globals + :members: + :undoc-members: + :show-inheritance: + +flask.helpers module +-------------------- + +.. automodule:: flask.helpers + :members: + :undoc-members: + :show-inheritance: + +flask.logging module +-------------------- + +.. automodule:: flask.logging + :members: + :undoc-members: + :show-inheritance: + +flask.sessions module +--------------------- + +.. automodule:: flask.sessions + :members: + :undoc-members: + :show-inheritance: + +flask.signals module +-------------------- + +.. automodule:: flask.signals + :members: + :undoc-members: + :show-inheritance: + +flask.templating module +----------------------- + +.. automodule:: flask.templating + :members: + :undoc-members: + :show-inheritance: + +flask.testing module +-------------------- + +.. automodule:: flask.testing + :members: + :undoc-members: + :show-inheritance: + +flask.typing module +------------------- + +.. automodule:: flask.typing + :members: + :undoc-members: + :show-inheritance: + +flask.views module +------------------ + +.. automodule:: flask.views + :members: + :undoc-members: + :show-inheritance: + +flask.wrappers module +--------------------- + +.. automodule:: flask.wrappers + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: flask + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/index.rst.txt b/docs/build/html/_sources/index.rst.txt new file mode 100644 index 00000000..bef08e80 --- /dev/null +++ b/docs/build/html/_sources/index.rst.txt @@ -0,0 +1,16 @@ +.. Flask documentation master file, created by + sphinx-quickstart on Thu Apr 10 19:53:46 2025. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Flask - Documentación Personalizada +==================================== + +Bienvenido a la documentación del proyecto Flask. + +.. toctree:: + :maxdepth: 2 + :caption: Contenido: + + modules + diff --git a/docs/build/html/_sources/modules.rst.txt b/docs/build/html/_sources/modules.rst.txt new file mode 100644 index 00000000..5823aac6 --- /dev/null +++ b/docs/build/html/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +flask +===== + +.. toctree:: + :maxdepth: 4 + + flask diff --git a/docs/build/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/build/html/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..81415803 --- /dev/null +++ b/docs/build/html/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/docs/build/html/_static/basic.css b/docs/build/html/_static/basic.css new file mode 100644 index 00000000..7ebbd6d0 --- /dev/null +++ b/docs/build/html/_static/basic.css @@ -0,0 +1,914 @@ +/* + * Sphinx stylesheet -- basic theme. + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin-top: 10px; +} + +ul.search li { + padding: 5px 0; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/build/html/_static/css/badge_only.css b/docs/build/html/_static/css/badge_only.css new file mode 100644 index 00000000..88ba55b9 --- /dev/null +++ b/docs/build/html/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.eot b/docs/build/html/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/docs/build/html/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.svg b/docs/build/html/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/docs/build/html/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.woff b/docs/build/html/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/docs/build/html/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/build/html/_static/css/fonts/lato-bold-italic.woff b/docs/build/html/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/build/html/_static/css/fonts/lato-bold.woff b/docs/build/html/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-bold.woff differ diff --git a/docs/build/html/_static/css/fonts/lato-bold.woff2 b/docs/build/html/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/build/html/_static/css/fonts/lato-normal-italic.woff b/docs/build/html/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/build/html/_static/css/fonts/lato-normal.woff b/docs/build/html/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-normal.woff differ diff --git a/docs/build/html/_static/css/fonts/lato-normal.woff2 b/docs/build/html/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/build/html/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/build/html/_static/css/theme.css b/docs/build/html/_static/css/theme.css new file mode 100644 index 00000000..0f14f106 --- /dev/null +++ b/docs/build/html/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/build/html/_static/doctools.js b/docs/build/html/_static/doctools.js new file mode 100644 index 00000000..0398ebb9 --- /dev/null +++ b/docs/build/html/_static/doctools.js @@ -0,0 +1,149 @@ +/* + * Base JavaScript utilities for all Sphinx HTML documentation. + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js new file mode 100644 index 00000000..7e4c114f --- /dev/null +++ b/docs/build/html/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/build/html/_static/file.png b/docs/build/html/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/build/html/_static/file.png differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.eot b/docs/build/html/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 00000000..3361183a Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.ttf b/docs/build/html/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 00000000..29f691d5 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.woff b/docs/build/html/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.woff2 b/docs/build/html/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot b/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 00000000..3d415493 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf b/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 00000000..f402040b Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff b/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.eot b/docs/build/html/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 00000000..3f826421 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.ttf b/docs/build/html/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 00000000..b4bfc9b2 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.woff b/docs/build/html/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.woff2 b/docs/build/html/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.eot b/docs/build/html/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 00000000..11e3f2a5 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.ttf b/docs/build/html/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.woff b/docs/build/html/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.woff2 b/docs/build/html/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/build/html/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 00000000..79dc8efe Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 00000000..df5d1df2 Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 00000000..2f7ca78a Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 00000000..eb52a790 Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/build/html/_static/jquery.js b/docs/build/html/_static/jquery.js new file mode 100644 index 00000000..c4c6022f --- /dev/null +++ b/docs/build/html/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); + + const languagesHTML = ` +
+
Languages
+ ${languages + .map( + (translation) => ` +
+ ${translation.language.code} +
+ `, + ) + .join("\n")} +
+ `; + return languagesHTML; + } + + function renderVersions(config) { + if (!config.versions.active.length) { + return ""; + } + const versionsHTML = ` +
+
Versions
+ ${config.versions.active + .map( + (version) => ` +
+ ${version.slug} +
+ `, + ) + .join("\n")} +
+ `; + return versionsHTML; + } + + function renderDownloads(config) { + if (!Object.keys(config.versions.current.downloads).length) { + return ""; + } + const downloadsNameDisplay = { + pdf: "PDF", + epub: "Epub", + htmlzip: "HTML", + }; + + const downloadsHTML = ` +
+
Downloads
+ ${Object.entries(config.versions.current.downloads) + .map( + ([name, url]) => ` +
+ ${downloadsNameDisplay[name]} +
+ `, + ) + .join("\n")} +
+ `; + return downloadsHTML; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const flyout = ` +
+ + Read the Docs + v: ${config.versions.current.slug} + + +
+
+ ${renderLanguages(config)} + ${renderVersions(config)} + ${renderDownloads(config)} +
+
On Read the Docs
+
+ Project Home +
+
+ Builds +
+
+ Downloads +
+
+
+
Search
+
+
+ +
+
+
+
+ + Hosted by Read the Docs + +
+
+ `; + + // Inject the generated flyout into the body HTML element. + document.body.insertAdjacentHTML("beforeend", flyout); + + // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. + document + .querySelector("#flyout-search-form") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); + }) +} + +if (themeLanguageSelector || themeVersionSelector) { + function onSelectorSwitch(event) { + const option = event.target.selectedIndex; + const item = event.target.options[option]; + window.location.href = item.dataset.url; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const versionSwitch = document.querySelector( + "div.switch-menus > div.version-switch", + ); + if (themeVersionSelector) { + let versions = config.versions.active; + if (config.versions.current.hidden || config.versions.current.type === "external") { + versions.unshift(config.versions.current); + } + const versionSelect = ` + + `; + + versionSwitch.innerHTML = versionSelect; + versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + + const languageSwitch = document.querySelector( + "div.switch-menus > div.language-switch", + ); + + if (themeLanguageSelector) { + if (config.projects.translations.length) { + // Add the current language to the options on the selector + let languages = config.projects.translations.concat( + config.projects.current, + ); + languages = languages.sort((a, b) => + a.language.name.localeCompare(b.language.name), + ); + + const languageSelect = ` + + `; + + languageSwitch.innerHTML = languageSelect; + languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + else { + languageSwitch.remove(); + } + } + }); +} + +document.addEventListener("readthedocs-addons-data-ready", function (event) { + // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. + document + .querySelector("[role='search'] input") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); +}); \ No newline at end of file diff --git a/docs/build/html/_static/language_data.js b/docs/build/html/_static/language_data.js new file mode 100644 index 00000000..c7fe6c6f --- /dev/null +++ b/docs/build/html/_static/language_data.js @@ -0,0 +1,192 @@ +/* + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/docs/build/html/_static/minus.png b/docs/build/html/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/docs/build/html/_static/minus.png differ diff --git a/docs/build/html/_static/plus.png b/docs/build/html/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/docs/build/html/_static/plus.png differ diff --git a/docs/build/html/_static/pygments.css b/docs/build/html/_static/pygments.css new file mode 100644 index 00000000..6f8b210a --- /dev/null +++ b/docs/build/html/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #F00 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #04D } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ +.highlight .no { color: #800 } /* Name.Constant */ +.highlight .nd { color: #A2F } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #00F } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #BBB } /* Text.Whitespace */ +.highlight .mb { color: #666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666 } /* Literal.Number.Float */ +.highlight .mh { color: #666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #00F } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/build/html/_static/searchtools.js b/docs/build/html/_static/searchtools.js new file mode 100644 index 00000000..2c774d17 --- /dev/null +++ b/docs/build/html/_static/searchtools.js @@ -0,0 +1,632 @@ +/* + * Sphinx JavaScript utilities for the full-text search. + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename, kind] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename, kind] = item; + + let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + SearchResultKind.title, + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + SearchResultKind.object, + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + SearchResultKind.text, + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/docs/build/html/_static/sphinx_highlight.js b/docs/build/html/_static/sphinx_highlight.js new file mode 100644 index 00000000..8a96c69a --- /dev/null +++ b/docs/build/html/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/docs/build/html/flask.html b/docs/build/html/flask.html new file mode 100644 index 00000000..bcd9d8c2 --- /dev/null +++ b/docs/build/html/flask.html @@ -0,0 +1,3605 @@ + + + + + + + + + flask package — Flask documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

flask package

+
+

Subpackages

+
+ +
+
+
+

Submodules

+
+
+

flask.app module

+
+
+class flask.app.Flask(import_name: str, static_url_path: str | None = None, static_folder: str | PathLike[str] | None = 'static', static_host: str | None = None, host_matching: bool = False, subdomain_matching: bool = False, template_folder: str | PathLike[str] | None = 'templates', instance_path: str | None = None, instance_relative_config: bool = False, root_path: str | None = None)
+

Bases: App

+

The flask object implements a WSGI application and acts as the central +object. It is passed the name of the module or package of the +application. Once it is created it will act as a central registry for +the view functions, the URL rules, template configuration and much more.

+

The name of the package is used to resolve resources from inside the +package or the folder the module is contained in depending on if the +package parameter resolves to an actual python package (a folder with +an __init__.py file inside) or a standard module (just a .py file).

+

For more information about resource loading, see open_resource().

+

Usually you create a Flask instance in your main module or +in the __init__.py file of your package like this:

+
from flask import Flask
+app = Flask(__name__)
+
+
+
+

About the First Parameter

+

The idea of the first parameter is to give Flask an idea of what +belongs to your application. This name is used to find resources +on the filesystem, can be used by extensions to improve debugging +information and a lot more.

+

So it’s important what you provide there. If you are using a single +module, __name__ is always the correct value. If you however are +using a package, it’s usually recommended to hardcode the name of +your package there.

+

For example if your application is defined in yourapplication/app.py +you should create it with one of the two versions below:

+
app = Flask('yourapplication')
+app = Flask(__name__.split('.')[0])
+
+
+

Why is that? The application will work even with __name__, thanks +to how resources are looked up. However it will make debugging more +painful. Certain extensions can make assumptions based on the +import name of your application. For example the Flask-SQLAlchemy +extension will look for the code in your application that triggered +an SQL query in debug mode. If the import name is not properly set +up, that debugging information is lost. (For example it would only +pick up SQL queries in yourapplication.app and not +yourapplication.views.frontend)

+
+
+

Added in version 0.7: The static_url_path, static_folder, and template_folder +parameters were added.

+
+
+

Added in version 0.8: The instance_path and instance_relative_config parameters were +added.

+
+
+

Added in version 0.11: The root_path parameter was added.

+
+
+

Added in version 1.0: The host_matching and static_host parameters were added.

+
+
+

Added in version 1.0: The subdomain_matching parameter was added. Subdomain +matching needs to be enabled manually now. Setting +SERVER_NAME does not implicitly enable it.

+
+
+
Parameters:
+
    +
  • import_name – the name of the application package

  • +
  • static_url_path – can be used to specify a different path for the +static files on the web. Defaults to the name +of the static_folder folder.

  • +
  • static_folder – The folder with static files that is served at +static_url_path. Relative to the application root_path +or an absolute path. Defaults to 'static'.

  • +
  • static_host – the host to use when adding the static route. +Defaults to None. Required when using host_matching=True +with a static_folder configured.

  • +
  • host_matching – set url_map.host_matching attribute. +Defaults to False.

  • +
  • subdomain_matching – consider the subdomain relative to +SERVER_NAME when matching routes. Defaults to False.

  • +
  • template_folder – the folder that contains the templates that should +be used by the application. Defaults to +'templates' folder in the root path of the +application.

  • +
  • instance_path – An alternative instance path for the application. +By default the folder 'instance' next to the +package or module is assumed to be the instance +path.

  • +
  • instance_relative_config – if set to True relative filenames +for loading the config are assumed to +be relative to the instance path instead +of the application root.

  • +
  • root_path – The path to the root of the application files. +This should only be set manually when it can’t be detected +automatically, such as for namespace packages.

  • +
+
+
+
+
+app_context() AppContext
+

Create an AppContext. Use as a with +block to push the context, which will make current_app +point at this application.

+

An application context is automatically pushed by +RequestContext.push() +when handling a request, and when running a CLI command. Use +this to manually create a context outside of these situations.

+
with app.app_context():
+    init_db()
+
+
+

See /appcontext.

+
+

Added in version 0.9.

+
+
+ +
+
+async_to_sync(func: Callable[[...], Coroutine[Any, Any, Any]]) Callable[[...], Any]
+

Return a sync function that will run the coroutine function.

+
result = app.async_to_sync(func)(*args, **kwargs)
+
+
+

Override this method to change how the app converts async code +to be synchronously callable.

+
+

Added in version 2.0.

+
+
+ +
+
+cli: Group
+

The Click command group for registering CLI commands for this +object. The commands are available from the flask command +once the application has been discovered and blueprints have +been registered.

+
+ +
+
+create_jinja_environment() Environment
+

Create the Jinja environment based on jinja_options +and the various Jinja-related methods of the app. Changing +jinja_options after this will have no effect. Also adds +Flask-related globals and filters to the environment.

+
+

Changed in version 0.11: Environment.auto_reload set in accordance with +TEMPLATES_AUTO_RELOAD configuration option.

+
+
+

Added in version 0.5.

+
+
+ +
+
+create_url_adapter(request: Request | None) MapAdapter | None
+

Creates a URL adapter for the given request. The URL adapter +is created at a point where the request context is not yet set +up so the request is passed explicitly.

+
+

Changed in version 3.1: If SERVER_NAME is set, it does not restrict requests to +only that domain, for both subdomain_matching and +host_matching.

+
+
+

Changed in version 1.0: SERVER_NAME no longer implicitly enables subdomain +matching. Use subdomain_matching instead.

+
+
+

Changed in version 0.9: This can be called outside a request when the URL adapter is created +for an application context.

+
+
+

Added in version 0.6.

+
+
+ +
+
+default_config: dict[str, t.Any] = {'APPLICATION_ROOT': '/', 'DEBUG': None, 'EXPLAIN_TEMPLATE_LOADING': False, 'MAX_CONTENT_LENGTH': None, 'MAX_COOKIE_SIZE': 4093, 'MAX_FORM_MEMORY_SIZE': 500000, 'MAX_FORM_PARTS': 1000, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'PREFERRED_URL_SCHEME': 'http', 'PROPAGATE_EXCEPTIONS': None, 'PROVIDE_AUTOMATIC_OPTIONS': True, 'SECRET_KEY': None, 'SECRET_KEY_FALLBACKS': None, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'SERVER_NAME': None, 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_PARTITIONED': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'TEMPLATES_AUTO_RELOAD': None, 'TESTING': False, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'TRUSTED_HOSTS': None, 'USE_X_SENDFILE': False}
+
+ +
+
+dispatch_request() ft.ResponseReturnValue
+

Does the request dispatching. Matches the URL and returns the +return value of the view or error handler. This does not have to +be a response object. In order to convert the return value to a +proper response object, call make_response().

+
+

Changed in version 0.7: This no longer does the exception handling, this code was +moved to the new full_dispatch_request().

+
+
+ +
+
+do_teardown_appcontext(exc: BaseException | None = <object object>) None
+

Called right before the application context is popped.

+

When handling a request, the application context is popped +after the request context. See do_teardown_request().

+

This calls all functions decorated with +teardown_appcontext(). Then the +appcontext_tearing_down signal is sent.

+

This is called by +AppContext.pop().

+
+

Added in version 0.9.

+
+
+ +
+
+do_teardown_request(exc: BaseException | None = <object object>) None
+

Called after the request is dispatched and the response is +returned, right before the request context is popped.

+

This calls all functions decorated with +teardown_request(), and Blueprint.teardown_request() +if a blueprint handled the request. Finally, the +request_tearing_down signal is sent.

+

This is called by +RequestContext.pop(), +which may be delayed during testing to maintain access to +resources.

+
+
Parameters:
+

exc – An unhandled exception raised while dispatching the +request. Detected from the current exception information if +not passed. Passed to each teardown function.

+
+
+
+

Changed in version 0.9: Added the exc argument.

+
+
+ +
+
+ensure_sync(func: Callable[[...], Any]) Callable[[...], Any]
+

Ensure that the function is synchronous for WSGI workers. +Plain def functions are returned as-is. async def +functions are wrapped to run and wait for the response.

+

Override this method to change how the app runs async views.

+
+

Added in version 2.0.

+
+
+ +
+
+finalize_request(rv: ft.ResponseReturnValue | HTTPException, from_error_handler: bool = False) Response
+

Given the return value from a view function this finalizes +the request by converting it into a response and invoking the +postprocessing functions. This is invoked for both normal +request dispatching as well as error handlers.

+

Because this means that it might be called as a result of a +failure a special safe mode is available which can be enabled +with the from_error_handler flag. If enabled, failures in +response processing will be logged and otherwise ignored.

+
+
Internal:
+

+
+
+ +
+
+full_dispatch_request() Response
+

Dispatches the request and on top of that performs request +pre and postprocessing as well as HTTP exception catching and +error handling.

+
+

Added in version 0.7.

+
+
+ +
+
+get_send_file_max_age(filename: str | None) int | None
+

Used by send_file() to determine the max_age cache +value for a given file path if it wasn’t passed.

+

By default, this returns SEND_FILE_MAX_AGE_DEFAULT from +the configuration of current_app. This defaults +to None, which tells the browser to use conditional requests +instead of a timed cache, which is usually preferable.

+

Note this is a duplicate of the same method in the Flask +class.

+
+

Changed in version 2.0: The default configuration is None instead of 12 hours.

+
+
+

Added in version 0.9.

+
+
+ +
+
+handle_exception(e: Exception) Response
+

Handle an exception that did not have an error handler +associated with it, or that was raised from an error handler. +This always causes a 500 InternalServerError.

+

Always sends the got_request_exception signal.

+

If PROPAGATE_EXCEPTIONS is True, such as in debug +mode, the error will be re-raised so that the debugger can +display it. Otherwise, the original exception is logged, and +an InternalServerError is returned.

+

If an error handler is registered for InternalServerError or +500, it will be used. For consistency, the handler will +always receive the InternalServerError. The original +unhandled exception is available as e.original_exception.

+
+

Changed in version 1.1.0: Always passes the InternalServerError instance to the +handler, setting original_exception to the unhandled +error.

+
+
+

Changed in version 1.1.0: after_request functions and other finalization is done +even for the default 500 response when there is no handler.

+
+
+

Added in version 0.3.

+
+
+ +
+
+handle_http_exception(e: HTTPException) HTTPException | ft.ResponseReturnValue
+

Handles an HTTP exception. By default this will invoke the +registered error handlers and fall back to returning the +exception as response.

+
+

Changed in version 1.0.3: RoutingException, used internally for actions such as + slash redirects during routing, is not passed to error + handlers.

+
+
+

Changed in version 1.0: Exceptions are looked up by code and by MRO, so +HTTPException subclasses can be handled with a catch-all +handler for the base HTTPException.

+
+
+

Added in version 0.3.

+
+
+ +
+
+handle_user_exception(e: Exception) HTTPException | ft.ResponseReturnValue
+

This method is called whenever an exception occurs that +should be handled. A special case is HTTPException which is forwarded to the +handle_http_exception() method. This function will either +return a response value or reraise the exception with the same +traceback.

+
+

Changed in version 1.0: Key errors raised from request data like form show the +bad key in debug mode rather than a generic bad request +message.

+
+
+

Added in version 0.7.

+
+
+ +
+
+log_exception(exc_info: tuple[type, BaseException, TracebackType] | tuple[None, None, None]) None
+

Logs an exception. This is called by handle_exception() +if debugging is disabled and right before the handler is called. +The default implementation logs the exception as error on the +logger.

+
+

Added in version 0.8.

+
+
+ +
+
+make_default_options_response() Response
+

This method is called to create the default OPTIONS response. +This can be changed through subclassing to change the default +behavior of OPTIONS responses.

+
+

Added in version 0.7.

+
+
+ +
+
+make_response(rv: ft.ResponseReturnValue) Response
+

Convert the return value from a view function to an instance of +response_class.

+
+
Parameters:
+

rv

the return value from the view function. The view function +must return a response. Returning None, or the view ending +without returning, is not allowed. The following types are allowed +for view_rv:

+
+
str

A response object is created with the string encoded to UTF-8 +as the body.

+
+
bytes

A response object is created with the bytes as the body.

+
+
dict

A dictionary that will be jsonify’d before being returned.

+
+
list

A list that will be jsonify’d before being returned.

+
+
generator or iterator

A generator that returns str or bytes to be +streamed as the response.

+
+
tuple

Either (body, status, headers), (body, status), or +(body, headers), where body is any of the other types +allowed here, status is a string or an integer, and +headers is a dictionary or a list of (key, value) +tuples. If body is a response_class instance, +status overwrites the exiting value and headers are +extended.

+
+
response_class

The object is returned unchanged.

+
+
other Response class

The object is coerced to response_class.

+
+
callable()

The function is called as a WSGI application. The result is +used to create a response object.

+
+
+

+
+
+
+

Changed in version 2.2: A generator will be converted to a streaming response. +A list will be converted to a JSON response.

+
+
+

Changed in version 1.1: A dict will be converted to a JSON response.

+
+
+

Changed in version 0.9: Previously a tuple was interpreted as the arguments for the +response object.

+
+
+ +
+
+make_shell_context() dict[str, Any]
+

Returns the shell context for an interactive shell for this +application. This runs all the registered shell context +processors.

+
+

Added in version 0.11.

+
+
+ +
+
+open_instance_resource(resource: str, mode: str = 'rb', encoding: str | None = 'utf-8') IO
+

Open a resource file relative to the application’s instance folder +instance_path. Unlike open_resource(), files in the +instance folder can be opened for writing.

+
+
Parameters:
+
    +
  • resource – Path to the resource relative to instance_path.

  • +
  • mode – Open the file in this mode.

  • +
  • encoding – Open the file with this encoding when opening in text +mode. This is ignored when opening in binary mode.

  • +
+
+
+
+

Changed in version 3.1: Added the encoding parameter.

+
+
+ +
+
+open_resource(resource: str, mode: str = 'rb', encoding: str | None = None) IO
+

Open a resource file relative to root_path for reading.

+

For example, if the file schema.sql is next to the file +app.py where the Flask app is defined, it can be opened +with:

+
with app.open_resource("schema.sql") as f:
+    conn.executescript(f.read())
+
+
+
+
Parameters:
+
    +
  • resource – Path to the resource relative to root_path.

  • +
  • mode – Open the file in this mode. Only reading is supported, +valid values are "r" (or "rt") and "rb".

  • +
  • encoding – Open the file with this encoding when opening in text +mode. This is ignored when opening in binary mode.

  • +
+
+
+
+

Changed in version 3.1: Added the encoding parameter.

+
+
+ +
+
+preprocess_request() ft.ResponseReturnValue | None
+

Called before the request is dispatched. Calls +url_value_preprocessors registered with the app and the +current blueprint (if any). Then calls before_request_funcs +registered with the app and the blueprint.

+

If any before_request() handler returns a non-None value, the +value is handled as if it was the return value from the view, and +further request handling is stopped.

+
+ +
+
+process_response(response: Response) Response
+

Can be overridden in order to modify the response object +before it’s sent to the WSGI server. By default this will +call all the after_request() decorated functions.

+
+

Changed in version 0.5: As of Flask 0.5 the functions registered for after request +execution are called in reverse order of registration.

+
+
+
Parameters:
+

response – a response_class object.

+
+
Returns:
+

a new response object or the same, has to be an +instance of response_class.

+
+
+
+ +
+
+request_class
+

The class that is used for request objects. See Request +for more information.

+

alias of Request

+
+ +
+
+request_context(environ: WSGIEnvironment) RequestContext
+

Create a RequestContext representing a +WSGI environment. Use a with block to push the context, +which will make request point at this request.

+

See /reqcontext.

+

Typically you should not call this from your own code. A request +context is automatically pushed by the wsgi_app() when +handling a request. Use test_request_context() to create +an environment and context instead of this method.

+
+
Parameters:
+

environ – a WSGI environment

+
+
+
+ +
+
+response_class
+

The class that is used for response objects. See +Response for more information.

+

alias of Response

+
+ +
+
+run(host: str | None = None, port: int | None = None, debug: bool | None = None, load_dotenv: bool = True, **options: Any) None
+

Runs the application on a local development server.

+

Do not use run() in a production setting. It is not intended to +meet security and performance requirements for a production server. +Instead, see /deploying/index for WSGI server recommendations.

+

If the debug flag is set the server will automatically reload +for code changes and show a debugger in case an exception happened.

+

If you want to run the application in debug mode, but disable the +code execution on the interactive debugger, you can pass +use_evalex=False as parameter. This will keep the debugger’s +traceback screen active, but disable code execution.

+

It is not recommended to use this function for development with +automatic reloading as this is badly supported. Instead you should +be using the flask command line script’s run support.

+
+

Keep in Mind

+

Flask will suppress any server error with a generic error page +unless it is in debug mode. As such to enable just the +interactive debugger without the code reloading, you have to +invoke run() with debug=True and use_reloader=False. +Setting use_debugger to True without being in debug mode +won’t catch any exceptions because there won’t be any to +catch.

+
+
+
Parameters:
+
    +
  • host – the hostname to listen on. Set this to '0.0.0.0' to +have the server available externally as well. Defaults to +'127.0.0.1' or the host in the SERVER_NAME config variable +if present.

  • +
  • port – the port of the webserver. Defaults to 5000 or the +port defined in the SERVER_NAME config variable if present.

  • +
  • debug – if given, enable or disable debug mode. See +debug.

  • +
  • load_dotenv – Load the nearest .env and .flaskenv +files to set environment variables. Will also change the working +directory to the directory containing the first file found.

  • +
  • options – the options to be forwarded to the underlying Werkzeug +server. See werkzeug.serving.run_simple() for more +information.

  • +
+
+
+
+

Changed in version 1.0: If installed, python-dotenv will be used to load environment +variables from .env and .flaskenv files.

+

The FLASK_DEBUG environment variable will override debug.

+

Threaded mode is enabled by default.

+
+
+

Changed in version 0.10: The default port is now picked from the SERVER_NAME +variable.

+
+
+ +
+
+send_static_file(filename: str) Response
+

The view function used to serve files from +static_folder. A route is automatically registered for +this view at static_url_path if static_folder is +set.

+

Note this is a duplicate of the same method in the Flask +class.

+
+

Added in version 0.5.

+
+
+ +
+
+session_interface: SessionInterface = <flask.sessions.SecureCookieSessionInterface object>
+

the session interface to use. By default an instance of +SecureCookieSessionInterface is used here.

+
+

Added in version 0.8.

+
+
+ +
+
+test_cli_runner(**kwargs: t.Any) FlaskCliRunner
+

Create a CLI runner for testing CLI commands. +See testing-cli.

+

Returns an instance of test_cli_runner_class, by default +FlaskCliRunner. The Flask app object is +passed as the first argument.

+
+

Added in version 1.0.

+
+
+ +
+
+test_client(use_cookies: bool = True, **kwargs: t.Any) FlaskClient
+

Creates a test client for this application. For information +about unit testing head over to /testing.

+

Note that if you are testing for assertions or exceptions in your +application code, you must set app.testing = True in order for the +exceptions to propagate to the test client. Otherwise, the exception +will be handled by the application (not visible to the test client) and +the only indication of an AssertionError or other exception will be a +500 status code response to the test client. See the testing +attribute. For example:

+
app.testing = True
+client = app.test_client()
+
+
+

The test client can be used in a with block to defer the closing down +of the context until the end of the with block. This is useful if +you want to access the context locals for testing:

+
with app.test_client() as c:
+    rv = c.get('/?vodka=42')
+    assert request.args['vodka'] == '42'
+
+
+

Additionally, you may pass optional keyword arguments that will then +be passed to the application’s test_client_class constructor. +For example:

+
from flask.testing import FlaskClient
+
+class CustomClient(FlaskClient):
+    def __init__(self, *args, **kwargs):
+        self._authentication = kwargs.pop("authentication")
+        super(CustomClient,self).__init__( *args, **kwargs)
+
+app.test_client_class = CustomClient
+client = app.test_client(authentication='Basic ....')
+
+
+

See FlaskClient for more information.

+
+

Changed in version 0.4: added support for with block usage for the client.

+
+
+

Added in version 0.7: The use_cookies parameter was added as well as the ability +to override the client to be used by setting the +test_client_class attribute.

+
+
+

Changed in version 0.11: Added **kwargs to support passing additional keyword arguments to +the constructor of test_client_class.

+
+
+ +
+
+test_request_context(*args: Any, **kwargs: Any) RequestContext
+

Create a RequestContext for a WSGI +environment created from the given values. This is mostly useful +during testing, where you may want to run a function that uses +request data without dispatching a full request.

+

See /reqcontext.

+

Use a with block to push the context, which will make +request point at the request for the created +environment.

+
with app.test_request_context(...):
+    generate_report()
+
+
+

When using the shell, it may be easier to push and pop the +context manually to avoid indentation.

+
ctx = app.test_request_context(...)
+ctx.push()
+...
+ctx.pop()
+
+
+

Takes the same arguments as Werkzeug’s +EnvironBuilder, with some defaults from +the application. See the linked Werkzeug docs for most of the +available arguments. Flask-specific behavior is listed here.

+
+
Parameters:
+
    +
  • path – URL path being requested.

  • +
  • base_url – Base URL where the app is being served, which +path is relative to. If not given, built from +PREFERRED_URL_SCHEME, subdomain, +SERVER_NAME, and APPLICATION_ROOT.

  • +
  • subdomain – Subdomain name to append to +SERVER_NAME.

  • +
  • url_scheme – Scheme to use instead of +PREFERRED_URL_SCHEME.

  • +
  • data – The request body, either as a string or a dict of +form keys and values.

  • +
  • json – If given, this is serialized as JSON and passed as +data. Also defaults content_type to +application/json.

  • +
  • args – other positional arguments passed to +EnvironBuilder.

  • +
  • kwargs – other keyword arguments passed to +EnvironBuilder.

  • +
+
+
+
+ +
+
+update_template_context(context: dict[str, Any]) None
+

Update the template context with some commonly used variables. +This injects request, session, config and g into the template +context as well as everything template context processors want +to inject. Note that the as of Flask 0.6, the original values +in the context will not be overridden if a context processor +decides to return a value with the same key.

+
+
Parameters:
+

context – the context as a dictionary that is updated in place +to add extra variables.

+
+
+
+ +
+
+url_for(endpoint: str, *, _anchor: str | None = None, _method: str | None = None, _scheme: str | None = None, _external: bool | None = None, **values: Any) str
+

Generate a URL to the given endpoint with the given values.

+

This is called by flask.url_for(), and can be called +directly as well.

+

An endpoint is the name of a URL rule, usually added with +@app.route(), and usually the same name as the +view function. A route defined in a Blueprint +will prepend the blueprint’s name separated by a . to the +endpoint.

+

In some cases, such as email messages, you want URLs to include +the scheme and domain, like https://example.com/hello. When +not in an active request, URLs will be external by default, but +this requires setting SERVER_NAME so Flask knows what +domain to use. APPLICATION_ROOT and +PREFERRED_URL_SCHEME should also be configured as +needed. This config is only used when not in an active request.

+

Functions can be decorated with url_defaults() to modify +keyword arguments before the URL is built.

+

If building fails for some reason, such as an unknown endpoint +or incorrect values, the app’s handle_url_build_error() +method is called. If that returns a string, that is returned, +otherwise a BuildError is raised.

+
+
Parameters:
+
    +
  • endpoint – The endpoint name associated with the URL to +generate. If this starts with a ., the current blueprint +name (if any) will be used.

  • +
  • _anchor – If given, append this as #anchor to the URL.

  • +
  • _method – If given, generate the URL associated with this +method for the endpoint.

  • +
  • _scheme – If given, the URL will have this scheme if it +is external.

  • +
  • _external – If given, prefer the URL to be internal +(False) or require it to be external (True). External URLs +include the scheme and domain. When not in an active +request, URLs are external by default.

  • +
  • values – Values to use for the variable parts of the URL +rule. Unknown keys are appended as query string arguments, +like ?a=b&c=d.

  • +
+
+
+
+

Added in version 2.2: Moved from flask.url_for, which calls this method.

+
+
+ +
+
+wsgi_app(environ: WSGIEnvironment, start_response: StartResponse) cabc.Iterable[bytes]
+

The actual WSGI application. This is not implemented in +__call__() so that middlewares can be applied without +losing a reference to the app object. Instead of doing this:

+
app = MyMiddleware(app)
+
+
+

It’s a better idea to do this instead:

+
app.wsgi_app = MyMiddleware(app.wsgi_app)
+
+
+

Then you still have the original application object around and +can continue to call methods on it.

+
+

Changed in version 0.7: Teardown events for the request and app contexts are called +even if an unhandled error occurs. Other events may not be +called depending on when an error occurs during dispatch. +See callbacks-and-errors.

+
+
+
Parameters:
+
    +
  • environ – A WSGI environment.

  • +
  • start_response – A callable accepting a status code, +a list of headers, and an optional exception context to +start the response.

  • +
+
+
+
+ +
+ +
+
+

flask.blueprints module

+
+
+class flask.blueprints.Blueprint(name: str, import_name: str, static_folder: str | ~os.PathLike[str] | None = None, static_url_path: str | None = None, template_folder: str | ~os.PathLike[str] | None = None, url_prefix: str | None = None, subdomain: str | None = None, url_defaults: dict[str, ~typing.Any] | None = None, root_path: str | None = None, cli_group: str | None = <object object>)
+

Bases: Blueprint

+
+
+cli: Group
+

The Click command group for registering CLI commands for this +object. The commands are available from the flask command +once the application has been discovered and blueprints have +been registered.

+
+ +
+
+get_send_file_max_age(filename: str | None) int | None
+

Used by send_file() to determine the max_age cache +value for a given file path if it wasn’t passed.

+

By default, this returns SEND_FILE_MAX_AGE_DEFAULT from +the configuration of current_app. This defaults +to None, which tells the browser to use conditional requests +instead of a timed cache, which is usually preferable.

+

Note this is a duplicate of the same method in the Flask +class.

+
+

Changed in version 2.0: The default configuration is None instead of 12 hours.

+
+
+

Added in version 0.9.

+
+
+ +
+
+open_resource(resource: str, mode: str = 'rb', encoding: str | None = 'utf-8') IO
+

Open a resource file relative to root_path for reading. The +blueprint-relative equivalent of the app’s open_resource() +method.

+
+
Parameters:
+
    +
  • resource – Path to the resource relative to root_path.

  • +
  • mode – Open the file in this mode. Only reading is supported, +valid values are "r" (or "rt") and "rb".

  • +
  • encoding – Open the file with this encoding when opening in text +mode. This is ignored when opening in binary mode.

  • +
+
+
+
+

Changed in version 3.1: Added the encoding parameter.

+
+
+ +
+
+send_static_file(filename: str) Response
+

The view function used to serve files from +static_folder. A route is automatically registered for +this view at static_url_path if static_folder is +set.

+

Note this is a duplicate of the same method in the Flask +class.

+
+

Added in version 0.5.

+
+
+ +
+ +
+
+

flask.cli module

+
+
+class flask.cli.AppGroup(name: str | None = None, commands: MutableMapping[str, Command] | Sequence[Command] | None = None, **attrs: Any)
+

Bases: Group

+

This works similar to a regular click Group but it +changes the behavior of the command() decorator so that it +automatically wraps the functions in with_appcontext().

+

Not to be confused with FlaskGroup.

+
+
+command(*args: Any, **kwargs: Any) Callable[[Callable[[...], Any]], Command]
+

This works exactly like the method of the same name on a regular +click.Group but it wraps callbacks in with_appcontext() +unless it’s disabled by passing with_appcontext=False.

+
+ +
+
+group(*args: Any, **kwargs: Any) Callable[[Callable[[...], Any]], Group]
+

This works exactly like the method of the same name on a regular +click.Group but it defaults the group class to +AppGroup.

+
+ +
+ +
+
+class flask.cli.CertParamType
+

Bases: ParamType

+

Click option type for the --cert option. Allows either an +existing file, the string 'adhoc', or an import for a +SSLContext object.

+
+
+convert(value: Any, param: Parameter | None, ctx: Context | None) Any
+

Convert the value to the correct type. This is not called if +the value is None (the missing value).

+

This must accept string values from the command line, as well as +values that are already the correct type. It may also convert +other compatible types.

+

The param and ctx arguments may be None in certain +situations, such as when converting prompt input.

+

If the value cannot be converted, call fail() with a +descriptive message.

+
+
Parameters:
+
    +
  • value – The value to convert.

  • +
  • param – The parameter that is using this type to convert +its value. May be None.

  • +
  • ctx – The current context that arrived at this value. May +be None.

  • +
+
+
+
+ +
+
+name: str = 'path'
+

the descriptive name of this type

+
+ +
+ +
+
+class flask.cli.FlaskGroup(add_default_commands: bool = True, create_app: t.Callable[..., Flask] | None = None, add_version_option: bool = True, load_dotenv: bool = True, set_debug_flag: bool = True, **extra: t.Any)
+

Bases: AppGroup

+

Special subclass of the AppGroup group that supports +loading more commands from the configured Flask app. Normally a +developer does not have to interface with this class but there are +some very advanced use cases for which it makes sense to create an +instance of this. see custom-scripts.

+
+
Parameters:
+
    +
  • add_default_commands – if this is True then the default run and +shell commands will be added.

  • +
  • add_version_option – adds the --version option.

  • +
  • create_app – an optional callback that is passed the script info and +returns the loaded app.

  • +
  • load_dotenv – Load the nearest .env and .flaskenv +files to set environment variables. Will also change the working +directory to the directory containing the first file found.

  • +
  • set_debug_flag – Set the app’s debug flag.

  • +
+
+
+
+

Changed in version 3.1: -e path takes precedence over default .env and .flaskenv files.

+
+
+

Changed in version 2.2: Added the -A/--app, --debug/--no-debug, -e/--env-file options.

+
+
+

Changed in version 2.2: An app context is pushed when running app.cli commands, so +@with_appcontext is no longer required for those commands.

+
+
+

Changed in version 1.0: If installed, python-dotenv will be used to load environment variables +from .env and .flaskenv files.

+
+
+
+get_command(ctx: Context, name: str) Command | None
+

Given a context and a command name, this returns a +Command object if it exists or returns None.

+
+ +
+
+list_commands(ctx: Context) list[str]
+

Returns a list of subcommand names in the order they should +appear.

+
+ +
+
+make_context(info_name: str | None, args: list[str], parent: Context | None = None, **extra: Any) Context
+

This function when given an info name and arguments will kick +off the parsing and create a new Context. It does not +invoke the actual command callback though.

+

To quickly customize the context class used without overriding +this method, set the context_class attribute.

+
+
Parameters:
+
    +
  • info_name – the info name for this invocation. Generally this +is the most descriptive name for the script or +command. For the toplevel script it’s usually +the name of the script, for commands below it’s +the name of the command.

  • +
  • args – the arguments to parse as list of strings.

  • +
  • parent – the parent context if available.

  • +
  • extra – extra keyword arguments forwarded to the context +constructor.

  • +
+
+
+
+

Changed in version 8.0: Added the context_class attribute.

+
+
+ +
+
+parse_args(ctx: Context, args: list[str]) list[str]
+

Given a context and a list of arguments this creates the parser +and parses the arguments, then modifies the context as necessary. +This is automatically invoked by make_context().

+
+ +
+ +
+
+exception flask.cli.NoAppException(message: str, ctx: Context | None = None)
+

Bases: UsageError

+

Raised if an application cannot be found or loaded.

+
+ +
+
+class flask.cli.ScriptInfo(app_import_path: str | None = None, create_app: t.Callable[..., Flask] | None = None, set_debug_flag: bool = True, load_dotenv_defaults: bool = True)
+

Bases: object

+

Helper object to deal with Flask applications. This is usually not +necessary to interface with as it’s used internally in the dispatching +to click. In future versions of Flask this object will most likely play +a bigger role. Typically it’s created automatically by the +FlaskGroup but you can also manually create it and pass it +onwards as click object.

+
+

Changed in version 3.1: Added the load_dotenv_defaults parameter and attribute.

+
+
+
+app_import_path
+

Optionally the import path for the Flask application.

+
+ +
+
+create_app
+

Optionally a function that is passed the script info to create +the instance of the application.

+
+ +
+
+data: dict[t.Any, t.Any]
+

A dictionary with arbitrary data that can be associated with +this script info.

+
+ +
+
+load_app() Flask
+

Loads the Flask app (if not yet loaded) and returns it. Calling +this multiple times will just result in the already loaded app to +be returned.

+
+ +
+
+load_dotenv_defaults
+

Whether default .flaskenv and .env files should be loaded.

+

ScriptInfo doesn’t load anything, this is for reference when doing +the load elsewhere during processing.

+
+

Added in version 3.1.

+
+
+ +
+ +
+
+class flask.cli.SeparatedPathType(exists: bool = False, file_okay: bool = True, dir_okay: bool = True, writable: bool = False, readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, path_type: Type[Any] | None = None, executable: bool = False)
+

Bases: Path

+

Click option type that accepts a list of values separated by the +OS’s path separator (:, ; on Windows). Each value is +validated as a click.Path type.

+
+
+convert(value: Any, param: Parameter | None, ctx: Context | None) Any
+

Convert the value to the correct type. This is not called if +the value is None (the missing value).

+

This must accept string values from the command line, as well as +values that are already the correct type. It may also convert +other compatible types.

+

The param and ctx arguments may be None in certain +situations, such as when converting prompt input.

+

If the value cannot be converted, call fail() with a +descriptive message.

+
+
Parameters:
+
    +
  • value – The value to convert.

  • +
  • param – The parameter that is using this type to convert +its value. May be None.

  • +
  • ctx – The current context that arrived at this value. May +be None.

  • +
+
+
+
+ +
+ +
+
+flask.cli.find_app_by_string(module: ModuleType, app_name: str) Flask
+

Check if the given string is a variable name or a function. Call +a function to get the app instance, or return the variable directly.

+
+ +
+
+flask.cli.find_best_app(module: ModuleType) Flask
+

Given a module instance this tries to find the best possible +application in the module or raises an exception.

+
+ +
+
+flask.cli.get_version(ctx: Context, param: Parameter, value: Any) None
+
+ +
+
+flask.cli.load_dotenv(path: str | PathLike[str] | None = None, load_defaults: bool = True) bool
+

Load “dotenv” files to set environment variables. A given path takes +precedence over .env, which takes precedence over .flaskenv. After +loading and combining these files, values are only set if the key is not +already set in os.environ.

+

This is a no-op if python-dotenv is not installed.

+
+
Parameters:
+
    +
  • path – Load the file at this location.

  • +
  • load_defaults – Search for and load the default .flaskenv and +.env files.

  • +
+
+
Returns:
+

True if at least one env var was loaded.

+
+
+
+

Changed in version 3.1: Added the load_defaults parameter. A given path takes precedence +over default files.

+
+
+

Changed in version 2.0: The current directory is not changed to the location of the +loaded file.

+
+
+

Changed in version 2.0: When loading the env files, set the default encoding to UTF-8.

+
+
+

Changed in version 1.1.0: Returns False when python-dotenv is not installed, or when +the given path isn’t a file.

+
+
+

Added in version 1.0.

+
+
+ +
+
+flask.cli.locate_app(module_name: str, app_name: str | None, raise_if_not_found: Literal[True] = True) Flask
+
+flask.cli.locate_app(module_name: str, app_name: str | None, raise_if_not_found: Literal[False] = True) Flask | None
+
+ +
+
+flask.cli.main() None
+
+ +
+
+flask.cli.prepare_import(path: str) str
+

Given a filename this will try to calculate the python path, add it +to the search path and return the actual module name that is expected.

+
+ +
+
+flask.cli.show_server_banner(debug: bool, app_import_path: str | None) None
+

Show extra startup messages the first time the server is run, +ignoring the reloader.

+
+ +
+
+flask.cli.with_appcontext(f: F) F
+

Wraps a callback so that it’s guaranteed to be executed with the +script’s application context.

+

Custom commands (and their options) registered under app.cli or +blueprint.cli will always have an app context available, this +decorator is not required in that case.

+
+

Changed in version 2.2: The app context is active for subcommands as well as the +decorated callback. The app context is always available to +app.cli command and parameter callbacks.

+
+
+ +
+
+

flask.config module

+
+
+class flask.config.Config(root_path: str | PathLike[str], defaults: dict[str, Any] | None = None)
+

Bases: dict

+

Works exactly like a dict but provides ways to fill it from files +or special dictionaries. There are two common patterns to populate the +config.

+

Either you can fill the config from a config file:

+
app.config.from_pyfile('yourconfig.cfg')
+
+
+

Or alternatively you can define the configuration options in the +module that calls from_object() or provide an import path to +a module that should be loaded. It is also possible to tell it to +use the same module and with that provide the configuration values +just before the call:

+
DEBUG = True
+SECRET_KEY = 'development key'
+app.config.from_object(__name__)
+
+
+

In both cases (loading from any Python file or loading from modules), +only uppercase keys are added to the config. This makes it possible to use +lowercase values in the config file for temporary values that are not added +to the config or to define the config keys in the same file that implements +the application.

+

Probably the most interesting way to load configurations is from an +environment variable pointing to a file:

+
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
+
+
+

In this case before launching the application you have to set this +environment variable to the file you want to use. On Linux and OS X +use the export statement:

+
export YOURAPPLICATION_SETTINGS='/path/to/config/file'
+
+
+

On windows use set instead.

+
+
Parameters:
+
    +
  • root_path – path to which files are read relative from. When the +config object is created by the application, this is +the application’s root_path.

  • +
  • defaults – an optional dictionary of default values

  • +
+
+
+
+
+from_envvar(variable_name: str, silent: bool = False) bool
+

Loads a configuration from an environment variable pointing to +a configuration file. This is basically just a shortcut with nicer +error messages for this line of code:

+
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
+
+
+
+
Parameters:
+
    +
  • variable_name – name of the environment variable

  • +
  • silent – set to True if you want silent failure for missing +files.

  • +
+
+
Returns:
+

True if the file was loaded successfully.

+
+
+
+ +
+
+from_file(filename: str | PathLike[str], load: Callable[[IO[Any]], Mapping[str, Any]], silent: bool = False, text: bool = True) bool
+

Update the values in the config from a file that is loaded +using the load parameter. The loaded data is passed to the +from_mapping() method.

+
import json
+app.config.from_file("config.json", load=json.load)
+
+import tomllib
+app.config.from_file("config.toml", load=tomllib.load, text=False)
+
+
+
+
Parameters:
+
    +
  • filename – The path to the data file. This can be an +absolute path or relative to the config root path.

  • +
  • load (Callable[[Reader], Mapping] where Reader +implements a read method.) – A callable that takes a file handle and returns a +mapping of loaded data from the file.

  • +
  • silent – Ignore the file if it doesn’t exist.

  • +
  • text – Open the file in text or binary mode.

  • +
+
+
Returns:
+

True if the file was loaded successfully.

+
+
+
+

Changed in version 2.3: The text parameter was added.

+
+
+

Added in version 2.0.

+
+
+ +
+
+from_mapping(mapping: Mapping[str, Any] | None = None, **kwargs: Any) bool
+

Updates the config like update() ignoring items with +non-upper keys.

+
+
Returns:
+

Always returns True.

+
+
+
+

Added in version 0.11.

+
+
+ +
+
+from_object(obj: object | str) None
+

Updates the values from the given object. An object can be of one +of the following two types:

+
    +
  • a string: in this case the object with that name will be imported

  • +
  • an actual object reference: that object is used directly

  • +
+

Objects are usually either modules or classes. from_object() +loads only the uppercase attributes of the module/class. A dict +object will not work with from_object() because the keys of a +dict are not attributes of the dict class.

+

Example of module-based configuration:

+
app.config.from_object('yourapplication.default_config')
+from yourapplication import default_config
+app.config.from_object(default_config)
+
+
+

Nothing is done to the object before loading. If the object is a +class and has @property attributes, it needs to be +instantiated before being passed to this method.

+

You should not use this function to load the actual configuration but +rather configuration defaults. The actual config should be loaded +with from_pyfile() and ideally from a location not within the +package because the package might be installed system wide.

+

See config-dev-prod for an example of class-based configuration +using from_object().

+
+
Parameters:
+

obj – an import name or object

+
+
+
+ +
+
+from_prefixed_env(prefix: str = 'FLASK', *, loads: ~typing.Callable[[str], ~typing.Any] = <function loads>) bool
+

Load any environment variables that start with FLASK_, +dropping the prefix from the env key for the config key. Values +are passed through a loading function to attempt to convert them +to more specific types than strings.

+

Keys are loaded in sorted() order.

+

The default loading function attempts to parse values as any +valid JSON type, including dicts and lists.

+

Specific items in nested dicts can be set by separating the +keys with double underscores (__). If an intermediate key +doesn’t exist, it will be initialized to an empty dict.

+
+
Parameters:
+
    +
  • prefix – Load env vars that start with this prefix, +separated with an underscore (_).

  • +
  • loads – Pass each string value to this function and use +the returned value as the config value. If any error is +raised it is ignored and the value remains a string. The +default is json.loads().

  • +
+
+
+
+

Added in version 2.1.

+
+
+ +
+
+from_pyfile(filename: str | PathLike[str], silent: bool = False) bool
+

Updates the values in the config from a Python file. This function +behaves as if the file was imported as module with the +from_object() function.

+
+
Parameters:
+
    +
  • filename – the filename of the config. This can either be an +absolute filename or a filename relative to the +root path.

  • +
  • silent – set to True if you want silent failure for missing +files.

  • +
+
+
Returns:
+

True if the file was loaded successfully.

+
+
+
+

Added in version 0.7: silent parameter.

+
+
+ +
+
+get_namespace(namespace: str, lowercase: bool = True, trim_namespace: bool = True) dict[str, Any]
+

Returns a dictionary containing a subset of configuration options +that match the specified namespace/prefix. Example usage:

+
app.config['IMAGE_STORE_TYPE'] = 'fs'
+app.config['IMAGE_STORE_PATH'] = '/var/app/images'
+app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
+image_store_config = app.config.get_namespace('IMAGE_STORE_')
+
+
+

The resulting dictionary image_store_config would look like:

+
{
+    'type': 'fs',
+    'path': '/var/app/images',
+    'base_url': 'http://img.website.com'
+}
+
+
+

This is often useful when configuration options map directly to +keyword arguments in functions or class constructors.

+
+
Parameters:
+
    +
  • namespace – a configuration namespace

  • +
  • lowercase – a flag indicating if the keys of the resulting +dictionary should be lowercase

  • +
  • trim_namespace – a flag indicating if the keys of the resulting +dictionary should not include the namespace

  • +
+
+
+
+

Added in version 0.11.

+
+
+ +
+ +
+
+class flask.config.ConfigAttribute(name: str, get_converter: Callable[[Any], T] | None = None)
+

Bases: Generic[T]

+

Makes an attribute forward to the config

+
+ +
+
+

flask.ctx module

+
+
+class flask.ctx.AppContext(app: Flask)
+

Bases: object

+

The app context contains application-specific information. An app +context is created and pushed at the beginning of each request if +one is not already active. An app context is also pushed when +running CLI commands.

+
+
+pop(exc: BaseException | None = <object object>) None
+

Pops the app context.

+
+ +
+
+push() None
+

Binds the app context to the current context.

+
+ +
+ +
+
+class flask.ctx.RequestContext(app: Flask, environ: WSGIEnvironment, request: Request | None = None, session: SessionMixin | None = None)
+

Bases: object

+

The request context contains per-request information. The Flask +app creates and pushes it at the beginning of the request, then pops +it at the end of the request. It will create the URL adapter and +request object for the WSGI environment provided.

+

Do not attempt to use this class directly, instead use +test_request_context() and +request_context() to create this object.

+

When the request context is popped, it will evaluate all the +functions registered on the application for teardown execution +(teardown_request()).

+

The request context is automatically popped at the end of the +request. When using the interactive debugger, the context will be +restored so request is still accessible. Similarly, the test +client can preserve the context after the request ends. However, +teardown functions may already have closed some resources such as +database connections.

+
+
+copy() RequestContext
+

Creates a copy of this request context with the same request object. +This can be used to move a request context to a different greenlet. +Because the actual request object is the same this cannot be used to +move a request context to a different thread unless access to the +request object is locked.

+
+

Added in version 0.10.

+
+
+

Changed in version 1.1: The current session object is used instead of reloading the original +data. This prevents flask.session pointing to an out-of-date object.

+
+
+ +
+
+match_request() None
+

Can be overridden by a subclass to hook into the matching +of the request.

+
+ +
+
+pop(exc: BaseException | None = <object object>) None
+

Pops the request context and unbinds it by doing that. This will +also trigger the execution of functions registered by the +teardown_request() decorator.

+
+

Changed in version 0.9: Added the exc argument.

+
+
+ +
+
+push() None
+
+ +
+ +
+
+flask.ctx.after_this_request(f: Callable[[Any], Any] | Callable[[Any], Awaitable[Any]]) Callable[[Any], Any] | Callable[[Any], Awaitable[Any]]
+

Executes a function after this request. This is useful to modify +response objects. The function is passed the response object and has +to return the same or a new one.

+

Example:

+
@app.route('/')
+def index():
+    @after_this_request
+    def add_header(response):
+        response.headers['X-Foo'] = 'Parachute'
+        return response
+    return 'Hello World!'
+
+
+

This is more useful if a function other than the view function wants to +modify a response. For instance think of a decorator that wants to add +some headers without converting the return value into a response object.

+
+

Added in version 0.9.

+
+
+ +
+
+flask.ctx.copy_current_request_context(f: F) F
+

A helper function that decorates a function to retain the current +request context. This is useful when working with greenlets. The moment +the function is decorated a copy of the request context is created and +then pushed when the function is called. The current session is also +included in the copied request context.

+

Example:

+
import gevent
+from flask import copy_current_request_context
+
+@app.route('/')
+def index():
+    @copy_current_request_context
+    def do_some_work():
+        # do some work here, it can access flask.request or
+        # flask.session like you would otherwise in the view function.
+        ...
+    gevent.spawn(do_some_work)
+    return 'Regular response'
+
+
+
+

Added in version 0.10.

+
+
+ +
+
+flask.ctx.has_app_context() bool
+

Works like has_request_context() but for the application +context. You can also just do a boolean check on the +current_app object instead.

+
+

Added in version 0.9.

+
+
+ +
+
+flask.ctx.has_request_context() bool
+

If you have code that wants to test if a request context is there or +not this function can be used. For instance, you may want to take advantage +of request information if the request object is available, but fail +silently if it is unavailable.

+
class User(db.Model):
+
+    def __init__(self, username, remote_addr=None):
+        self.username = username
+        if remote_addr is None and has_request_context():
+            remote_addr = request.remote_addr
+        self.remote_addr = remote_addr
+
+
+

Alternatively you can also just test any of the context bound objects +(such as request or g) for truthness:

+
class User(db.Model):
+
+    def __init__(self, username, remote_addr=None):
+        self.username = username
+        if remote_addr is None and request:
+            remote_addr = request.remote_addr
+        self.remote_addr = remote_addr
+
+
+
+

Added in version 0.7.

+
+
+ +
+
+

flask.debughelpers module

+
+
+exception flask.debughelpers.DebugFilesKeyError(request: Request, key: str)
+

Bases: KeyError, AssertionError

+

Raised from request.files during debugging. The idea is that it can +provide a better error message than just a generic KeyError/BadRequest.

+
+ +
+
+exception flask.debughelpers.FormDataRoutingRedirect(request: Request)
+

Bases: AssertionError

+

This exception is raised in debug mode if a routing redirect +would cause the browser to drop the method or body. This happens +when method is not GET, HEAD or OPTIONS and the status code is not +307 or 308.

+
+ +
+
+exception flask.debughelpers.UnexpectedUnicodeError
+

Bases: AssertionError, UnicodeError

+

Raised in places where we want some better error reporting for +unexpected unicode or binary data.

+
+ +
+
+flask.debughelpers.explain_template_loading_attempts(app: App, template: str, attempts: list[tuple[BaseLoader, Scaffold, tuple[str, str | None, t.Callable[[], bool] | None] | None]]) None
+

This should help developers understand what failed

+
+ +
+
+

flask.globals module

+
+
+

flask.helpers module

+
+
+flask.helpers.abort(code: int | Response, *args: Any, **kwargs: Any) NoReturn
+

Raise an HTTPException for the given +status code.

+

If current_app is available, it will call its +aborter object, otherwise it will use +werkzeug.exceptions.abort().

+
+
Parameters:
+
    +
  • code – The status code for the exception, which must be +registered in app.aborter.

  • +
  • args – Passed to the exception.

  • +
  • kwargs – Passed to the exception.

  • +
+
+
+
+

Added in version 2.2: Calls current_app.aborter if available instead of always +using Werkzeug’s default abort.

+
+
+ +
+
+flask.helpers.flash(message: str, category: str = 'message') None
+

Flashes a message to the next request. In order to remove the +flashed message from the session and to display it to the user, +the template has to call get_flashed_messages().

+
+

Changed in version 0.3: category parameter added.

+
+
+
Parameters:
+
    +
  • message – the message to be flashed.

  • +
  • category – the category for the message. The following values +are recommended: 'message' for any kind of message, +'error' for errors, 'info' for information +messages and 'warning' for warnings. However any +kind of string can be used as category.

  • +
+
+
+
+ +
+
+flask.helpers.get_debug_flag() bool
+

Get whether debug mode should be enabled for the app, indicated by the +FLASK_DEBUG environment variable. The default is False.

+
+ +
+
+flask.helpers.get_flashed_messages(with_categories: bool = False, category_filter: Iterable[str] = ()) list[str] | list[tuple[str, str]]
+

Pulls all flashed messages from the session and returns them. +Further calls in the same request to the function will return +the same messages. By default just the messages are returned, +but when with_categories is set to True, the return value will +be a list of tuples in the form (category, message) instead.

+

Filter the flashed messages to one or more categories by providing those +categories in category_filter. This allows rendering categories in +separate html blocks. The with_categories and category_filter +arguments are distinct:

+
    +
  • with_categories controls whether categories are returned with message +text (True gives a tuple, where False gives just the message text).

  • +
  • category_filter filters the messages down to only those matching the +provided categories.

  • +
+

See /patterns/flashing for examples.

+
+

Changed in version 0.3: with_categories parameter added.

+
+
+

Changed in version 0.9: category_filter parameter added.

+
+
+
Parameters:
+
    +
  • with_categories – set to True to also receive categories.

  • +
  • category_filter – filter of categories to limit return values. Only +categories in the list will be returned.

  • +
+
+
+
+ +
+
+flask.helpers.get_load_dotenv(default: bool = True) bool
+

Get whether the user has disabled loading default dotenv files by +setting FLASK_SKIP_DOTENV. The default is True, load +the files.

+
+
Parameters:
+

default – What to return if the env var isn’t set.

+
+
+
+ +
+
+flask.helpers.get_template_attribute(template_name: str, attribute: str) Any
+

Loads a macro (or variable) a template exports. This can be used to +invoke a macro from within Python code. If you for example have a +template named _cider.html with the following contents:

+
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
+
+
+

You can access this from Python code like this:

+
hello = get_template_attribute('_cider.html', 'hello')
+return hello('World')
+
+
+
+

Added in version 0.2.

+
+
+
Parameters:
+
    +
  • template_name – the name of the template

  • +
  • attribute – the name of the variable of macro to access

  • +
+
+
+
+ +
+
+flask.helpers.make_response(*args: t.Any) Response
+

Sometimes it is necessary to set additional headers in a view. Because +views do not have to return response objects but can return a value that +is converted into a response object by Flask itself, it becomes tricky to +add headers to it. This function can be called instead of using a return +and you will get a response object which you can use to attach headers.

+

If view looked like this and you want to add a new header:

+
def index():
+    return render_template('index.html', foo=42)
+
+
+

You can now do something like this:

+
def index():
+    response = make_response(render_template('index.html', foo=42))
+    response.headers['X-Parachutes'] = 'parachutes are cool'
+    return response
+
+
+

This function accepts the very same arguments you can return from a +view function. This for example creates a response with a 404 error +code:

+
response = make_response(render_template('not_found.html'), 404)
+
+
+

The other use case of this function is to force the return value of a +view function into a response which is helpful with view +decorators:

+
response = make_response(view_function())
+response.headers['X-Parachutes'] = 'parachutes are cool'
+
+
+

Internally this function does the following things:

+
    +
  • if no arguments are passed, it creates a new response argument

  • +
  • if one argument is passed, flask.Flask.make_response() +is invoked with it.

  • +
  • if more than one argument is passed, the arguments are passed +to the flask.Flask.make_response() function as tuple.

  • +
+
+

Added in version 0.6.

+
+
+ +
+
+flask.helpers.redirect(location: str, code: int = 302, Response: type[Response] | None = None) Response
+

Create a redirect response object.

+

If current_app is available, it will use its +redirect() method, otherwise it will use +werkzeug.utils.redirect().

+
+
Parameters:
+
    +
  • location – The URL to redirect to.

  • +
  • code – The status code for the redirect.

  • +
  • Response – The response class to use. Not used when +current_app is active, which uses app.response_class.

  • +
+
+
+
+

Added in version 2.2: Calls current_app.redirect if available instead of always +using Werkzeug’s default redirect.

+
+
+ +
+
+flask.helpers.send_file(path_or_file: os.PathLike[t.AnyStr] | str | t.BinaryIO, mimetype: str | None = None, as_attachment: bool = False, download_name: str | None = None, conditional: bool = True, etag: bool | str = True, last_modified: datetime | int | float | None = None, max_age: None | int | t.Callable[[str | None], int | None] = None) Response
+

Send the contents of a file to the client.

+

The first argument can be a file path or a file-like object. Paths +are preferred in most cases because Werkzeug can manage the file and +get extra information from the path. Passing a file-like object +requires that the file is opened in binary mode, and is mostly +useful when building a file in memory with io.BytesIO.

+

Never pass file paths provided by a user. The path is assumed to be +trusted, so a user could craft a path to access a file you didn’t +intend. Use send_from_directory() to safely serve +user-requested paths from within a directory.

+

If the WSGI server sets a file_wrapper in environ, it is +used, otherwise Werkzeug’s built-in wrapper is used. Alternatively, +if the HTTP server supports X-Sendfile, configuring Flask with +USE_X_SENDFILE = True will tell the server to send the given +path, which is much more efficient than reading it in Python.

+
+
Parameters:
+
    +
  • path_or_file – The path to the file to send, relative to the +current working directory if a relative path is given. +Alternatively, a file-like object opened in binary mode. Make +sure the file pointer is seeked to the start of the data.

  • +
  • mimetype – The MIME type to send for the file. If not +provided, it will try to detect it from the file name.

  • +
  • as_attachment – Indicate to a browser that it should offer to +save the file instead of displaying it.

  • +
  • download_name – The default name browsers will use when saving +the file. Defaults to the passed file name.

  • +
  • conditional – Enable conditional and range responses based on +request headers. Requires passing a file path and environ.

  • +
  • etag – Calculate an ETag for the file, which requires passing +a file path. Can also be a string to use instead.

  • +
  • last_modified – The last modified time to send for the file, +in seconds. If not provided, it will try to detect it from the +file path.

  • +
  • max_age – How long the client should cache the file, in +seconds. If set, Cache-Control will be public, otherwise +it will be no-cache to prefer conditional caching.

  • +
+
+
+
+

Changed in version 2.0: download_name replaces the attachment_filename +parameter. If as_attachment=False, it is passed with +Content-Disposition: inline instead.

+
+
+

Changed in version 2.0: max_age replaces the cache_timeout parameter. +conditional is enabled and max_age is not set by +default.

+
+
+

Changed in version 2.0: etag replaces the add_etags parameter. It can be a +string to use instead of generating one.

+
+
+

Changed in version 2.0: Passing a file-like object that inherits from +TextIOBase will raise a ValueError rather +than sending an empty file.

+
+
+

Added in version 2.0: Moved the implementation to Werkzeug. This is now a wrapper to +pass some Flask-specific arguments.

+
+
+

Changed in version 1.1: filename may be a PathLike object.

+
+
+

Changed in version 1.1: Passing a BytesIO object supports range requests.

+
+
+

Changed in version 1.0.3: Filenames are encoded with ASCII instead of Latin-1 for broader +compatibility with WSGI servers.

+
+
+

Changed in version 1.0: UTF-8 filenames as specified in RFC 2231 are supported.

+
+
+

Changed in version 0.12: The filename is no longer automatically inferred from file +objects. If you want to use automatic MIME and etag support, +pass a filename via filename_or_fp or +attachment_filename.

+
+
+

Changed in version 0.12: attachment_filename is preferred over filename for MIME +detection.

+
+
+

Changed in version 0.9: cache_timeout defaults to +Flask.get_send_file_max_age().

+
+
+

Changed in version 0.7: MIME guessing and etag support for file-like objects was +removed because it was unreliable. Pass a filename if you are +able to, otherwise attach an etag yourself.

+
+
+

Changed in version 0.5: The add_etags, cache_timeout and conditional +parameters were added. The default behavior is to add etags.

+
+
+

Added in version 0.2.

+
+
+ +
+
+flask.helpers.send_from_directory(directory: os.PathLike[str] | str, path: os.PathLike[str] | str, **kwargs: t.Any) Response
+

Send a file from within a directory using send_file().

+
@app.route("/uploads/<path:name>")
+def download_file(name):
+    return send_from_directory(
+        app.config['UPLOAD_FOLDER'], name, as_attachment=True
+    )
+
+
+

This is a secure way to serve files from a folder, such as static +files or uploads. Uses safe_join() to +ensure the path coming from the client is not maliciously crafted to +point outside the specified directory.

+

If the final path does not point to an existing regular file, +raises a 404 NotFound error.

+
+
Parameters:
+
    +
  • directory – The directory that path must be located under, +relative to the current application’s root path. This must not +be a value provided by the client, otherwise it becomes insecure.

  • +
  • path – The path to the file to send, relative to +directory.

  • +
  • kwargs – Arguments to pass to send_file().

  • +
+
+
+
+

Changed in version 2.0: path replaces the filename parameter.

+
+
+

Added in version 2.0: Moved the implementation to Werkzeug. This is now a wrapper to +pass some Flask-specific arguments.

+
+
+

Added in version 0.5.

+
+
+ +
+
+flask.helpers.stream_with_context(generator_or_function: Iterator) Iterator
+
+flask.helpers.stream_with_context(generator_or_function: Callable[[...], Iterator]) Callable[[Iterator], Iterator]
+

Request contexts disappear when the response is started on the server. +This is done for efficiency reasons and to make it less likely to encounter +memory leaks with badly written WSGI middlewares. The downside is that if +you are using streamed responses, the generator cannot access request bound +information any more.

+

This function however can help you keep the context around for longer:

+
from flask import stream_with_context, request, Response
+
+@app.route('/stream')
+def streamed_response():
+    @stream_with_context
+    def generate():
+        yield 'Hello '
+        yield request.args['name']
+        yield '!'
+    return Response(generate())
+
+
+

Alternatively it can also be used around a specific generator:

+
from flask import stream_with_context, request, Response
+
+@app.route('/stream')
+def streamed_response():
+    def generate():
+        yield 'Hello '
+        yield request.args['name']
+        yield '!'
+    return Response(stream_with_context(generate()))
+
+
+
+

Added in version 0.9.

+
+
+ +
+
+flask.helpers.url_for(endpoint: str, *, _anchor: str | None = None, _method: str | None = None, _scheme: str | None = None, _external: bool | None = None, **values: Any) str
+

Generate a URL to the given endpoint with the given values.

+

This requires an active request or application context, and calls +current_app.url_for(). See that method +for full documentation.

+
+
Parameters:
+
    +
  • endpoint – The endpoint name associated with the URL to +generate. If this starts with a ., the current blueprint +name (if any) will be used.

  • +
  • _anchor – If given, append this as #anchor to the URL.

  • +
  • _method – If given, generate the URL associated with this +method for the endpoint.

  • +
  • _scheme – If given, the URL will have this scheme if it is +external.

  • +
  • _external – If given, prefer the URL to be internal (False) or +require it to be external (True). External URLs include the +scheme and domain. When not in an active request, URLs are +external by default.

  • +
  • values – Values to use for the variable parts of the URL rule. +Unknown keys are appended as query string arguments, like +?a=b&c=d.

  • +
+
+
+
+

Changed in version 2.2: Calls current_app.url_for, allowing an app to override the +behavior.

+
+
+

Changed in version 0.10: The _scheme parameter was added.

+
+
+

Changed in version 0.9: The _anchor and _method parameters were added.

+
+
+

Changed in version 0.9: Calls app.handle_url_build_error on build errors.

+
+
+ +
+
+

flask.logging module

+
+
+flask.logging.create_logger(app: App) logging.Logger
+

Get the Flask app’s logger and configure it if needed.

+

The logger name will be the same as +app.import_name.

+

When debug is enabled, set the logger level to +logging.DEBUG if it is not set.

+

If there is no handler for the logger’s effective level, add a +StreamHandler for +wsgi_errors_stream() with a basic format.

+
+ +
+
+flask.logging.default_handler = <StreamHandler <stderr> (NOTSET)>
+

Log messages to wsgi_errors_stream() with the format +[%(asctime)s] %(levelname)s in %(module)s: %(message)s.

+
+ +
+
+flask.logging.has_level_handler(logger: Logger) bool
+

Check if there is a handler in the logging chain that will handle the +given logger’s effective level.

+
+ +
+
+

flask.sessions module

+
+
+class flask.sessions.NullSession(initial: Mapping[str, Any] | Iterable[tuple[str, Any]] | None = None)
+

Bases: SecureCookieSession

+

Class used to generate nicer error messages if sessions are not +available. Will still allow read-only access to the empty session +but fail on setting.

+
+
+clear() None.  Remove all items from D.
+
+ +
+
+pop(k[, d]) v, remove specified key and return the corresponding value.
+

If the key is not found, return the default if given; otherwise, +raise a KeyError.

+
+ +
+
+popitem(*args: Any, **kwargs: Any) NoReturn
+

Remove and return a (key, value) pair as a 2-tuple.

+

Pairs are returned in LIFO (last-in, first-out) order. +Raises KeyError if the dict is empty.

+
+ +
+
+setdefault(*args: Any, **kwargs: Any) NoReturn
+

Insert key with a value of default if key is not in the dictionary.

+

Return the value for key if key is in the dictionary, else default.

+
+ +
+
+update([E, ]**F) None.  Update D from dict/iterable E and F.
+

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] +If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v +In either case, this is followed by: for k in F: D[k] = F[k]

+
+ +
+ +
+
+class flask.sessions.SecureCookieSession(initial: Mapping[str, Any] | Iterable[tuple[str, Any]] | None = None)
+

Bases: CallbackDict[str, Any], SessionMixin

+

Base class for sessions based on signed cookies.

+

This session backend will set the modified and +accessed attributes. It cannot reliably track whether a +session is new (vs. empty), so new remains hard coded to +False.

+
+
+accessed = False
+

header, which allows caching proxies to cache different pages for +different users.

+
+ +
+
+get(key: str, default: Any | None = None) Any
+

Return the value for key if key is in the dictionary, else default.

+
+ +
+
+modified = False
+

When data is changed, this is set to True. Only the session +dictionary itself is tracked; if the session contains mutable +data (for example a nested dict) then this must be set to +True manually when modifying that data. The session cookie +will only be written to the response if this is True.

+
+ +
+
+setdefault(key: str, default: Any | None = None) Any
+

Insert key with a value of default if key is not in the dictionary.

+

Return the value for key if key is in the dictionary, else default.

+
+ +
+ +
+
+class flask.sessions.SecureCookieSessionInterface
+

Bases: SessionInterface

+

The default session interface that stores sessions in signed cookies +through the itsdangerous module.

+
+
+static digest_method(string: bytes = b'') Any
+

the hash function to use for the signature. The default is sha1

+
+ +
+
+get_signing_serializer(app: Flask) URLSafeTimedSerializer | None
+
+ +
+
+key_derivation = 'hmac'
+

the name of the itsdangerous supported key derivation. The default +is hmac.

+
+ +
+
+open_session(app: Flask, request: Request) SecureCookieSession | None
+

This is called at the beginning of each request, after +pushing the request context, before matching the URL.

+

This must return an object which implements a dictionary-like +interface as well as the SessionMixin interface.

+

This will return None to indicate that loading failed in +some way that is not immediately an error. The request +context will fall back to using make_null_session() +in this case.

+
+ +
+
+salt = 'cookie-session'
+

the salt that should be applied on top of the secret key for the +signing of cookie based sessions.

+
+ +
+
+save_session(app: Flask, session: SessionMixin, response: Response) None
+

This is called at the end of each request, after generating +a response, before removing the request context. It is skipped +if is_null_session() returns True.

+
+ +
+
+serializer = <flask.json.tag.TaggedJSONSerializer object>
+

A python serializer for the payload. The default is a compact +JSON derived serializer with support for some extra Python types +such as datetime objects or tuples.

+
+ +
+
+session_class
+

alias of SecureCookieSession

+
+ +
+ +
+
+class flask.sessions.SessionInterface
+

Bases: object

+

The basic interface you have to implement in order to replace the +default session interface which uses werkzeug’s securecookie +implementation. The only methods you have to implement are +open_session() and save_session(), the others have +useful defaults which you don’t need to change.

+

The session object returned by the open_session() method has to +provide a dictionary like interface plus the properties and methods +from the SessionMixin. We recommend just subclassing a dict +and adding that mixin:

+
class Session(dict, SessionMixin):
+    pass
+
+
+

If open_session() returns None Flask will call into +make_null_session() to create a session that acts as replacement +if the session support cannot work because some requirement is not +fulfilled. The default NullSession class that is created +will complain that the secret key was not set.

+

To replace the session interface on an application all you have to do +is to assign flask.Flask.session_interface:

+
app = Flask(__name__)
+app.session_interface = MySessionInterface()
+
+
+

Multiple requests with the same session may be sent and handled +concurrently. When implementing a new session interface, consider +whether reads or writes to the backing store must be synchronized. +There is no guarantee on the order in which the session for each +request is opened or saved, it will occur in the order that requests +begin and end processing.

+
+

Added in version 0.8.

+
+
+ +

The value of the Domain parameter on the session cookie. If not set, +browsers will only send the cookie to the exact domain it was set from. +Otherwise, they will send it to any subdomain of the given value as well.

+

Uses the SESSION_COOKIE_DOMAIN config.

+
+

Changed in version 2.3: Not set by default, does not fall back to SERVER_NAME.

+
+
+ +
+ +

Returns True if the session cookie should be httponly. This +currently just returns the value of the SESSION_COOKIE_HTTPONLY +config var.

+
+ +
+ +

The name of the session cookie. Uses``app.config[“SESSION_COOKIE_NAME”]``.

+
+ +
+ +

Returns True if the cookie should be partitioned. By default, uses +the value of SESSION_COOKIE_PARTITIONED.

+
+

Added in version 3.1.

+
+
+ +
+ +

Returns the path for which the cookie should be valid. The +default implementation uses the value from the SESSION_COOKIE_PATH +config var if it’s set, and falls back to APPLICATION_ROOT or +uses / if it’s None.

+
+ +
+ +

Return 'Strict' or 'Lax' if the cookie should use the +SameSite attribute. This currently just returns the value of +the SESSION_COOKIE_SAMESITE setting.

+
+ +
+ +

Returns True if the cookie should be secure. This currently +just returns the value of the SESSION_COOKIE_SECURE setting.

+
+ +
+
+get_expiration_time(app: Flask, session: SessionMixin) datetime | None
+

A helper method that returns an expiration date for the session +or None if the session is linked to the browser session. The +default implementation returns now + the permanent session +lifetime configured on the application.

+
+ +
+
+is_null_session(obj: object) bool
+

Checks if a given object is a null session. Null sessions are +not asked to be saved.

+

This checks if the object is an instance of null_session_class +by default.

+
+ +
+
+make_null_session(app: Flask) NullSession
+

Creates a null session which acts as a replacement object if the +real session support could not be loaded due to a configuration +error. This mainly aids the user experience because the job of the +null session is to still support lookup without complaining but +modifications are answered with a helpful error message of what +failed.

+

This creates an instance of null_session_class by default.

+
+ +
+
+null_session_class
+

make_null_session() will look here for the class that should +be created when a null session is requested. Likewise the +is_null_session() method will perform a typecheck against +this type.

+

alias of NullSession

+
+ +
+
+open_session(app: Flask, request: Request) SessionMixin | None
+

This is called at the beginning of each request, after +pushing the request context, before matching the URL.

+

This must return an object which implements a dictionary-like +interface as well as the SessionMixin interface.

+

This will return None to indicate that loading failed in +some way that is not immediately an error. The request +context will fall back to using make_null_session() +in this case.

+
+ +
+
+pickle_based = False
+

A flag that indicates if the session interface is pickle based. +This can be used by Flask extensions to make a decision in regards +to how to deal with the session object.

+
+

Added in version 0.10.

+
+
+ +
+
+save_session(app: Flask, session: SessionMixin, response: Response) None
+

This is called at the end of each request, after generating +a response, before removing the request context. It is skipped +if is_null_session() returns True.

+
+ +
+ +

Used by session backends to determine if a Set-Cookie header +should be set for this session cookie for this response. If the session +has been modified, the cookie is set. If the session is permanent and +the SESSION_REFRESH_EACH_REQUEST config is true, the cookie is +always set.

+

This check is usually skipped if the session was deleted.

+
+

Added in version 0.11.

+
+
+ +
+ +
+
+class flask.sessions.SessionMixin
+

Bases: MutableMapping[str, Any]

+

Expands a basic dictionary with session attributes.

+
+
+accessed = True
+

Some implementations can detect when session data is read or +written and set this when that happens. The mixin default is hard +coded to True.

+
+ +
+
+modified = True
+

Some implementations can detect changes to the session and set +this when that happens. The mixin default is hard coded to +True.

+
+ +
+
+new = False
+
+ +
+
+property permanent: bool
+

This reflects the '_permanent' key in the dict.

+
+ +
+ +
+
+

flask.signals module

+
+
+

flask.templating module

+
+
+class flask.templating.DispatchingJinjaLoader(app: App)
+

Bases: BaseLoader

+

A loader that looks for templates in the application and all +the blueprint folders.

+
+
+get_source(environment: Environment, template: str) tuple[str, str | None, Callable[[], bool] | None]
+

Get the template source, filename and reload helper for a template. +It’s passed the environment and template name and has to return a +tuple in the form (source, filename, uptodate) or raise a +TemplateNotFound error if it can’t locate the template.

+

The source part of the returned tuple must be the source of the +template as a string. The filename should be the name of the +file on the filesystem if it was loaded from there, otherwise +None. The filename is used by Python for the tracebacks +if no loader extension is used.

+

The last item in the tuple is the uptodate function. If auto +reloading is enabled it’s always called to check if the template +changed. No arguments are passed so the function must store the +old state somewhere (for example in a closure). If it returns False +the template will be reloaded.

+
+ +
+
+list_templates() list[str]
+

Iterates over all templates. If the loader does not support that +it should raise a TypeError which is the default behavior.

+
+ +
+ +
+
+class flask.templating.Environment(app: App, **options: t.Any)
+

Bases: Environment

+

Works like a regular Jinja2 environment but has some additional +knowledge of how Flask’s blueprint works so that it can prepend the +name of the blueprint to referenced templates if necessary.

+
+ +
+
+flask.templating.render_template(template_name_or_list: str | Template | list[str | Template], **context: Any) str
+

Render a template by name with the given context.

+
+
Parameters:
+
    +
  • template_name_or_list – The name of the template to render. If +a list is given, the first name to exist will be rendered.

  • +
  • context – The variables to make available in the template.

  • +
+
+
+
+ +
+
+flask.templating.render_template_string(source: str, **context: Any) str
+

Render a template from the given source string with the given +context.

+
+
Parameters:
+
    +
  • source – The source code of the template to render.

  • +
  • context – The variables to make available in the template.

  • +
+
+
+
+ +
+
+flask.templating.stream_template(template_name_or_list: str | Template | list[str | Template], **context: Any) Iterator[str]
+

Render a template by name with the given context as a stream. +This returns an iterator of strings, which can be used as a +streaming response from a view.

+
+
Parameters:
+
    +
  • template_name_or_list – The name of the template to render. If +a list is given, the first name to exist will be rendered.

  • +
  • context – The variables to make available in the template.

  • +
+
+
+
+

Added in version 2.2.

+
+
+ +
+
+flask.templating.stream_template_string(source: str, **context: Any) Iterator[str]
+

Render a template from the given source string with the given +context as a stream. This returns an iterator of strings, which can +be used as a streaming response from a view.

+
+
Parameters:
+
    +
  • source – The source code of the template to render.

  • +
  • context – The variables to make available in the template.

  • +
+
+
+
+

Added in version 2.2.

+
+
+ +
+
+

flask.testing module

+
+
+class flask.testing.EnvironBuilder(app: Flask, path: str = '/', base_url: str | None = None, subdomain: str | None = None, url_scheme: str | None = None, *args: t.Any, **kwargs: t.Any)
+

Bases: EnvironBuilder

+

An EnvironBuilder, that takes defaults from the +application.

+
+
Parameters:
+
    +
  • app – The Flask application to configure the environment from.

  • +
  • path – URL path being requested.

  • +
  • base_url – Base URL where the app is being served, which +path is relative to. If not given, built from +PREFERRED_URL_SCHEME, subdomain, +SERVER_NAME, and APPLICATION_ROOT.

  • +
  • subdomain – Subdomain name to append to SERVER_NAME.

  • +
  • url_scheme – Scheme to use instead of +PREFERRED_URL_SCHEME.

  • +
  • json – If given, this is serialized as JSON and passed as +data. Also defaults content_type to +application/json.

  • +
  • args – other positional arguments passed to +EnvironBuilder.

  • +
  • kwargs – other keyword arguments passed to +EnvironBuilder.

  • +
+
+
+
+
+json_dumps(obj: Any, **kwargs: Any) str
+

Serialize obj to a JSON-formatted string.

+

The serialization will be configured according to the config associated +with this EnvironBuilder’s app.

+
+ +
+ +
+
+class flask.testing.FlaskCliRunner(app: Flask, **kwargs: t.Any)
+

Bases: CliRunner

+

A CliRunner for testing a Flask app’s +CLI commands. Typically created using +test_cli_runner(). See testing-cli.

+
+
+invoke(cli: Any | None = None, args: Any | None = None, **kwargs: Any) Result
+

Invokes a CLI command in an isolated environment. See +CliRunner.invoke for +full method documentation. See testing-cli for examples.

+

If the obj argument is not given, passes an instance of +ScriptInfo that knows how to load the Flask +app being tested.

+
+
Parameters:
+
    +
  • cli – Command object to invoke. Default is the app’s +cli group.

  • +
  • args – List of strings to invoke the command with.

  • +
+
+
Returns:
+

a Result object.

+
+
+
+ +
+ +
+
+class flask.testing.FlaskClient(*args: Any, **kwargs: Any)
+

Bases: Client

+

Works like a regular Werkzeug test client but has knowledge about +Flask’s contexts to defer the cleanup of the request context until +the end of a with block. For general information about how to +use this class refer to werkzeug.test.Client.

+
+

Changed in version 0.12: app.test_client() includes preset default environment, which can be +set after instantiation of the app.test_client() object in +client.environ_base.

+
+

Basic usage is outlined in the /testing chapter.

+
+
+application: Flask
+
+ +
+
+open(*args: t.Any, buffered: bool = False, follow_redirects: bool = False, **kwargs: t.Any) TestResponse
+

Generate an environ dict from the given arguments, make a +request to the application using it, and return the response.

+
+
Parameters:
+
    +
  • args – Passed to EnvironBuilder to create the +environ for the request. If a single arg is passed, it can +be an existing EnvironBuilder or an environ dict.

  • +
  • buffered – Convert the iterator returned by the app into +a list. If the iterator has a close() method, it is +called automatically.

  • +
  • follow_redirects – Make additional requests to follow HTTP +redirects until a non-redirect status is returned. +TestResponse.history lists the intermediate +responses.

  • +
+
+
+
+

Changed in version 2.1: Removed the as_tuple parameter.

+
+
+

Changed in version 2.0: The request input stream is closed when calling +response.close(). Input streams for redirects are +automatically closed.

+
+
+

Changed in version 0.5: If a dict is provided as file in the dict for the data +parameter the content type has to be called content_type +instead of mimetype. This change was made for +consistency with werkzeug.FileWrapper.

+
+
+

Changed in version 0.5: Added the follow_redirects parameter.

+
+
+ +
+
+session_transaction(*args: Any, **kwargs: Any) Iterator[SessionMixin]
+

When used in combination with a with statement this opens a +session transaction. This can be used to modify the session that +the test client uses. Once the with block is left the session is +stored back.

+
with client.session_transaction() as session:
+    session['value'] = 42
+
+
+

Internally this is implemented by going through a temporary test +request context and since session handling could depend on +request variables this function accepts the same arguments as +test_request_context() which are directly +passed through.

+
+ +
+ +
+
+

flask.typing module

+
+
+

flask.views module

+
+
+class flask.views.MethodView
+

Bases: View

+

Dispatches request methods to the corresponding instance methods. +For example, if you implement a get method, it will be used to +handle GET requests.

+

This can be useful for defining a REST API.

+

methods is automatically set based on the methods defined on +the class.

+

See views for a detailed guide.

+
class CounterAPI(MethodView):
+    def get(self):
+        return str(session.get("counter", 0))
+
+    def post(self):
+        session["counter"] = session.get("counter", 0) + 1
+        return redirect(url_for("counter"))
+
+app.add_url_rule(
+    "/counter", view_func=CounterAPI.as_view("counter")
+)
+
+
+
+
+dispatch_request(**kwargs: t.Any) ft.ResponseReturnValue
+

The actual view function behavior. Subclasses must override +this and return a valid response. Any variables from the URL +rule are passed as keyword arguments.

+
+ +
+ +
+
+class flask.views.View
+

Bases: object

+

Subclass this class and override dispatch_request() to +create a generic class-based view. Call as_view() to create a +view function that creates an instance of the class with the given +arguments and calls its dispatch_request method with any URL +variables.

+

See views for a detailed guide.

+
class Hello(View):
+    init_every_request = False
+
+    def dispatch_request(self, name):
+        return f"Hello, {name}!"
+
+app.add_url_rule(
+    "/hello/<name>", view_func=Hello.as_view("hello")
+)
+
+
+

Set methods on the class to change what methods the view +accepts.

+

Set decorators on the class to apply a list of decorators to +the generated view function. Decorators applied to the class itself +will not be applied to the generated view function!

+

Set init_every_request to False for efficiency, unless +you need to store request-global data on self.

+
+
+classmethod as_view(name: str, *class_args: t.Any, **class_kwargs: t.Any) ft.RouteCallable
+

Convert the class into a view function that can be registered +for a route.

+

By default, the generated view will create a new instance of the +view class for every request and call its +dispatch_request() method. If the view class sets +init_every_request to False, the same instance will +be used for every request.

+

Except for name, all other arguments passed to this method +are forwarded to the view class __init__ method.

+
+

Changed in version 2.2: Added the init_every_request class attribute.

+
+
+ +
+
+decorators: ClassVar[list[Callable[[...], Any]]] = []
+

A list of decorators to apply, in order, to the generated view +function. Remember that @decorator syntax is applied bottom +to top, so the first decorator in the list would be the bottom +decorator.

+
+

Added in version 0.8.

+
+
+ +
+
+dispatch_request() ft.ResponseReturnValue
+

The actual view function behavior. Subclasses must override +this and return a valid response. Any variables from the URL +rule are passed as keyword arguments.

+
+ +
+
+init_every_request: ClassVar[bool] = True
+

Create a new instance of this view class for every request by +default. If a view subclass sets this to False, the same +instance is used for every request.

+

A single instance is more efficient, especially if complex setup +is done during init. However, storing data on self is no +longer safe across requests, and g should be used +instead.

+
+

Added in version 2.2.

+
+
+ +
+
+methods: ClassVar[Collection[str] | None] = None
+

The methods this view is registered for. Uses the same default +(["GET", "HEAD", "OPTIONS"]) as route and +add_url_rule by default.

+
+ +
+
+provide_automatic_options: ClassVar[bool | None] = None
+

Control whether the OPTIONS method is handled automatically. +Uses the same default (True) as route and +add_url_rule by default.

+
+ +
+ +
+
+

flask.wrappers module

+
+
+class flask.wrappers.Request(environ: WSGIEnvironment, populate_request: bool = True, shallow: bool = False)
+

Bases: Request

+

The request object used by default in Flask. Remembers the +matched endpoint and view arguments.

+

It is what ends up as request. If you want to replace +the request object used you can subclass this and set +request_class to your subclass.

+

The request object is a Request subclass and +provides all of the attributes Werkzeug defines plus a few Flask +specific ones.

+
+
+property blueprint: str | None
+

The registered name of the current blueprint.

+

This will be None if the endpoint is not part of a +blueprint, or if URL matching failed or has not been performed +yet.

+

This does not necessarily match the name the blueprint was +created with. It may have been nested, or registered with a +different name.

+
+ +
+
+property blueprints: list[str]
+

The registered names of the current blueprint upwards through +parent blueprints.

+

This will be an empty list if there is no current blueprint, or +if URL matching failed.

+
+

Added in version 2.0.1.

+
+
+ +
+
+property endpoint: str | None
+

The endpoint that matched the request URL.

+

This will be None if matching failed or has not been +performed yet.

+

This in combination with view_args can be used to +reconstruct the same URL or a modified URL.

+
+ +
+
+json_module: t.Any = <module 'flask.json' from '/home/edgar/ucr/software_des/repo/flask/src/flask/json/__init__.py'>
+

A module or other object that has dumps and loads +functions that match the API of the built-in json module.

+
+ +
+
+property max_content_length: int | None
+

The maximum number of bytes that will be read during this request. If +this limit is exceeded, a 413 RequestEntityTooLarge +error is raised. If it is set to None, no limit is enforced at the +Flask application level. However, if it is None and the request has +no Content-Length header and the WSGI server does not indicate that +it terminates the stream, then no data is read to avoid an infinite +stream.

+

Each request defaults to the MAX_CONTENT_LENGTH config, which +defaults to None. It can be set on a specific request to apply +the limit to that specific view. This should be set appropriately based +on an application’s or view’s specific needs.

+
+

Changed in version 3.1: This can be set per-request.

+
+
+

Changed in version 0.6: This is configurable through Flask config.

+
+
+ +
+
+property max_form_memory_size: int | None
+

The maximum size in bytes any non-file form field may be in a +multipart/form-data body. If this limit is exceeded, a 413 +RequestEntityTooLarge error is raised. If it +is set to None, no limit is enforced at the Flask application level.

+

Each request defaults to the MAX_FORM_MEMORY_SIZE config, which +defaults to 500_000. It can be set on a specific request to +apply the limit to that specific view. This should be set appropriately +based on an application’s or view’s specific needs.

+
+

Changed in version 3.1: This is configurable through Flask config.

+
+
+ +
+
+property max_form_parts: int | None
+

The maximum number of fields that may be present in a +multipart/form-data body. If this limit is exceeded, a 413 +RequestEntityTooLarge error is raised. If it +is set to None, no limit is enforced at the Flask application level.

+

Each request defaults to the MAX_FORM_PARTS config, which +defaults to 1_000. It can be set on a specific request to apply +the limit to that specific view. This should be set appropriately based +on an application’s or view’s specific needs.

+
+

Changed in version 3.1: This is configurable through Flask config.

+
+
+ +
+
+on_json_loading_failed(e: ValueError | None) Any
+

Called if get_json() fails and isn’t silenced.

+

If this method returns a value, it is used as the return value +for get_json(). The default implementation raises +BadRequest.

+
+
Parameters:
+

e – If parsing failed, this is the exception. It will be +None if the content type wasn’t application/json.

+
+
+
+

Changed in version 2.3: Raise a 415 error instead of 400.

+
+
+ +
+
+routing_exception: HTTPException | None = None
+

If matching the URL failed, this is the exception that will be +raised / was raised as part of the request handling. This is +usually a NotFound exception or +something similar.

+
+ +
+
+url_rule: Rule | None = None
+

The internal URL rule that matched the request. This can be +useful to inspect which methods are allowed for the URL from +a before/after handler (request.url_rule.methods) etc. +Though if the request’s method was invalid for the URL rule, +the valid list is available in routing_exception.valid_methods +instead (an attribute of the Werkzeug exception +MethodNotAllowed) +because the request was never internally bound.

+
+

Added in version 0.6.

+
+
+ +
+
+view_args: dict[str, t.Any] | None = None
+

A dict of view arguments that matched the request. If an exception +happened when matching, this will be None.

+
+ +
+ +
+
+class flask.wrappers.Response(response: Iterable[bytes] | bytes | Iterable[str] | str | None = None, status: int | str | HTTPStatus | None = None, headers: Mapping[str, str | Iterable[str]] | Iterable[tuple[str, str]] | None = None, mimetype: str | None = None, content_type: str | None = None, direct_passthrough: bool = False)
+

Bases: Response

+

The response object that is used by default in Flask. Works like the +response object from Werkzeug but is set to have an HTML mimetype by +default. Quite often you don’t have to create this object yourself because +make_response() will take care of that for you.

+

If you want to replace the response object used you can subclass this and +set response_class to your subclass.

+
+

Changed in version 1.0: JSON support is added to the response, like the request. This is useful +when testing to get the test client response data as JSON.

+
+
+

Changed in version 1.0: Added max_cookie_size.

+
+
+
+autocorrect_location_header = False
+

If a redirect Location header is a relative URL, make it an +absolute URL, including scheme and domain.

+
+

Changed in version 2.1: This is disabled by default, so responses will send relative +redirects.

+
+
+

Added in version 0.8.

+
+
+ +
+
+default_mimetype: str | None = 'text/html'
+

the default mimetype if none is provided.

+
+ +
+
+json_module = <module 'flask.json' from '/home/edgar/ucr/software_des/repo/flask/src/flask/json/__init__.py'>
+

A module or other object that has dumps and loads +functions that match the API of the built-in json module.

+
+ +
+ +

Read-only view of the MAX_COOKIE_SIZE config key.

+

See max_cookie_size in +Werkzeug’s docs.

+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/flask.json.html b/docs/build/html/flask.json.html new file mode 100644 index 00000000..4caf6bcc --- /dev/null +++ b/docs/build/html/flask.json.html @@ -0,0 +1,1053 @@ + + + + + + + + + flask.json package — Flask documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

flask.json package

+
+

Submodules

+
+
+

flask.json.provider module

+
+
+class flask.json.provider.DefaultJSONProvider(app: App)
+

Bases: JSONProvider

+

Provide JSON operations using Python’s built-in json +library. Serializes the following additional data types:

+
    +
  • datetime.datetime and datetime.date are +serialized to RFC 822 strings. This is the same as the HTTP +date format.

  • +
  • uuid.UUID is serialized to a string.

  • +
  • dataclasses.dataclass is passed to +dataclasses.asdict().

  • +
  • Markup (or any object with a __html__ +method) will call the __html__ method to get a string.

  • +
+
+
+compact: bool | None = None
+

If True, or None out of debug mode, the response() +output will not add indentation, newlines, or spaces. If False, +or None in debug mode, it will use a non-compact representation.

+
+ +
+
+static default(o: Any) Any
+

Apply this function to any object that json.dumps() does +not know how to serialize. It should return a valid JSON type or +raise a TypeError.

+
+ +
+
+dumps(obj: Any, **kwargs: Any) str
+

Serialize data as JSON to a string.

+

Keyword arguments are passed to json.dumps(). Sets some +parameter defaults from the default, +ensure_ascii, and sort_keys attributes.

+
+
Parameters:
+
    +
  • obj – The data to serialize.

  • +
  • kwargs – Passed to json.dumps().

  • +
+
+
+
+ +
+
+ensure_ascii = True
+

Replace non-ASCII characters with escape sequences. This may be +more compatible with some clients, but can be disabled for better +performance and size.

+
+ +
+
+loads(s: str | bytes, **kwargs: Any) Any
+

Deserialize data as JSON from a string or bytes.

+
+
Parameters:
+
    +
  • s – Text or UTF-8 bytes.

  • +
  • kwargs – Passed to json.loads().

  • +
+
+
+
+ +
+
+mimetype = 'application/json'
+

The mimetype set in response().

+
+ +
+
+response(*args: t.Any, **kwargs: t.Any) Response
+

Serialize the given arguments as JSON, and return a +Response object with it. The response mimetype +will be “application/json” and can be changed with +mimetype.

+

If compact is False or debug mode is enabled, the +output will be formatted to be easier to read.

+

Either positional or keyword arguments can be given, not both. +If no arguments are given, None is serialized.

+
+
Parameters:
+
    +
  • args – A single value to serialize, or multiple values to +treat as a list to serialize.

  • +
  • kwargs – Treat as a dict to serialize.

  • +
+
+
+
+ +
+
+sort_keys = True
+

Sort the keys in any serialized dicts. This may be useful for +some caching situations, but can be disabled for better performance. +When enabled, keys must all be strings, they are not converted +before sorting.

+
+ +
+ +
+
+class flask.json.provider.JSONProvider(app: App)
+

Bases: object

+

A standard set of JSON operations for an application. Subclasses +of this can be used to customize JSON behavior or use different +JSON libraries.

+

To implement a provider for a specific library, subclass this base +class and implement at least dumps() and loads(). All +other methods have default implementations.

+

To use a different provider, either subclass Flask and set +json_provider_class to a provider class, or set +app.json to an instance of the class.

+
+
Parameters:
+

app – An application instance. This will be stored as a +weakref.proxy on the _app attribute.

+
+
+
+

Added in version 2.2.

+
+
+
+dump(obj: Any, fp: IO[str], **kwargs: Any) None
+

Serialize data as JSON and write to a file.

+
+
Parameters:
+
    +
  • obj – The data to serialize.

  • +
  • fp – A file opened for writing text. Should use the UTF-8 +encoding to be valid JSON.

  • +
  • kwargs – May be passed to the underlying JSON library.

  • +
+
+
+
+ +
+
+dumps(obj: Any, **kwargs: Any) str
+

Serialize data as JSON.

+
+
Parameters:
+
    +
  • obj – The data to serialize.

  • +
  • kwargs – May be passed to the underlying JSON library.

  • +
+
+
+
+ +
+
+load(fp: IO, **kwargs: Any) Any
+

Deserialize data as JSON read from a file.

+
+
Parameters:
+
    +
  • fp – A file opened for reading text or UTF-8 bytes.

  • +
  • kwargs – May be passed to the underlying JSON library.

  • +
+
+
+
+ +
+
+loads(s: str | bytes, **kwargs: Any) Any
+

Deserialize data as JSON.

+
+
Parameters:
+
    +
  • s – Text or UTF-8 bytes.

  • +
  • kwargs – May be passed to the underlying JSON library.

  • +
+
+
+
+ +
+
+response(*args: t.Any, **kwargs: t.Any) Response
+

Serialize the given arguments as JSON, and return a +Response object with the application/json +mimetype.

+

The jsonify() function calls this method for +the current application.

+

Either positional or keyword arguments can be given, not both. +If no arguments are given, None is serialized.

+
+
Parameters:
+
    +
  • args – A single value to serialize, or multiple values to +treat as a list to serialize.

  • +
  • kwargs – Treat as a dict to serialize.

  • +
+
+
+
+ +
+ +
+
+

flask.json.tag module

+
+

Tagged JSON

+

A compact representation for lossless serialization of non-standard JSON +types. SecureCookieSessionInterface uses this +to serialize the session data, but it may be useful in other places. It +can be extended to support other types.

+
+
+class flask.json.tag.TaggedJSONSerializer
+

Serializer that uses a tag system to compactly represent objects that +are not JSON types. Passed as the intermediate serializer to +itsdangerous.Serializer.

+

The following extra types are supported:

+
    +
  • dict

  • +
  • tuple

  • +
  • bytes

  • +
  • Markup

  • +
  • UUID

  • +
  • datetime

  • +
+
+
+default_tags = [<class 'flask.json.tag.TagDict'>, <class 'flask.json.tag.PassDict'>, <class 'flask.json.tag.TagTuple'>, <class 'flask.json.tag.PassList'>, <class 'flask.json.tag.TagBytes'>, <class 'flask.json.tag.TagMarkup'>, <class 'flask.json.tag.TagUUID'>, <class 'flask.json.tag.TagDateTime'>]
+

Tag classes to bind when creating the serializer. Other tags can be +added later using register().

+
+ +
+
+dumps(value: Any) str
+

Tag the value and dump it to a compact JSON string.

+
+ +
+
+loads(value: str) Any
+

Load data from a JSON string and deserialized any tagged objects.

+
+ +
+
+register(tag_class: type[JSONTag], force: bool = False, index: int | None = None) None
+

Register a new tag with this serializer.

+
+
Parameters:
+
    +
  • tag_class – tag class to register. Will be instantiated with this +serializer instance.

  • +
  • force – overwrite an existing tag. If false (default), a +KeyError is raised.

  • +
  • index – index to insert the new tag in the tag order. Useful when +the new tag is a special case of an existing tag. If None +(default), the tag is appended to the end of the order.

  • +
+
+
Raises:
+

KeyError – if the tag key is already registered and force is +not true.

+
+
+
+ +
+
+tag(value: Any) Any
+

Convert a value to a tagged representation if necessary.

+
+ +
+
+untag(value: dict[str, Any]) Any
+

Convert a tagged representation back to the original type.

+
+ +
+ +
+
+class flask.json.tag.JSONTag(serializer: TaggedJSONSerializer)
+

Base class for defining type tags for TaggedJSONSerializer.

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ''
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+tag(value: Any) dict[str, Any]
+

Convert the value to a valid JSON type and add the tag structure +around it.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +

Let’s see an example that adds support for +OrderedDict. Dicts don’t have an order in JSON, so +to handle this we will dump the items as a list of [key, value] +pairs. Subclass JSONTag and give it the new key ' od' to +identify the type. The session serializer processes dicts first, so +insert the new tag at the front of the order since OrderedDict must +be processed before dict.

+
from flask.json.tag import JSONTag
+
+class TagOrderedDict(JSONTag):
+    __slots__ = ('serializer',)
+    key = ' od'
+
+    def check(self, value):
+        return isinstance(value, OrderedDict)
+
+    def to_json(self, value):
+        return [[k, self.serializer.tag(v)] for k, v in iteritems(value)]
+
+    def to_python(self, value):
+        return OrderedDict(value)
+
+app.session_interface.serializer.register(TagOrderedDict, index=0)
+
+
+
+
+
+class flask.json.tag.JSONTag(serializer: TaggedJSONSerializer)
+

Bases: object

+

Base class for defining type tags for TaggedJSONSerializer.

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ''
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+serializer
+
+ +
+
+tag(value: Any) dict[str, Any]
+

Convert the value to a valid JSON type and add the tag structure +around it.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +
+
+class flask.json.tag.PassDict(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+tag(value: Any) Any
+

Convert the value to a valid JSON type and add the tag structure +around it.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+ +
+
+class flask.json.tag.PassList(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+tag(value: Any) Any
+

Convert the value to a valid JSON type and add the tag structure +around it.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+ +
+
+class flask.json.tag.TagBytes(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ' b'
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +
+
+class flask.json.tag.TagDateTime(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ' d'
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +
+
+class flask.json.tag.TagDict(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+

Tag for 1-item dicts whose only key matches a registered tag.

+

Internally, the dict key is suffixed with __, and the suffix is removed +when deserializing.

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ' di'
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +
+
+class flask.json.tag.TagMarkup(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+

Serialize anything matching the Markup API by +having a __html__ method to the result of that method. Always +deserializes to an instance of Markup.

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ' m'
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +
+
+class flask.json.tag.TagTuple(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ' t'
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +
+
+class flask.json.tag.TagUUID(serializer: TaggedJSONSerializer)
+

Bases: JSONTag

+
+
+check(value: Any) bool
+

Check if the given value should be tagged by this tag.

+
+ +
+
+key: str = ' u'
+

The tag to mark the serialized object with. If empty, this tag is +only used as an intermediate step during tagging.

+
+ +
+
+to_json(value: Any) Any
+

Convert the Python object to an object that is a valid JSON type. +The tag will be added later.

+
+ +
+
+to_python(value: Any) Any
+

Convert the JSON representation back to the correct type. The tag +will already be removed.

+
+ +
+ +
+
+class flask.json.tag.TaggedJSONSerializer
+

Bases: object

+

Serializer that uses a tag system to compactly represent objects that +are not JSON types. Passed as the intermediate serializer to +itsdangerous.Serializer.

+

The following extra types are supported:

+
    +
  • dict

  • +
  • tuple

  • +
  • bytes

  • +
  • Markup

  • +
  • UUID

  • +
  • datetime

  • +
+
+
+default_tags = [<class 'flask.json.tag.TagDict'>, <class 'flask.json.tag.PassDict'>, <class 'flask.json.tag.TagTuple'>, <class 'flask.json.tag.PassList'>, <class 'flask.json.tag.TagBytes'>, <class 'flask.json.tag.TagMarkup'>, <class 'flask.json.tag.TagUUID'>, <class 'flask.json.tag.TagDateTime'>]
+

Tag classes to bind when creating the serializer. Other tags can be +added later using register().

+
+ +
+
+dumps(value: Any) str
+

Tag the value and dump it to a compact JSON string.

+
+ +
+
+loads(value: str) Any
+

Load data from a JSON string and deserialized any tagged objects.

+
+ +
+
+order: list[JSONTag]
+
+ +
+
+register(tag_class: type[JSONTag], force: bool = False, index: int | None = None) None
+

Register a new tag with this serializer.

+
+
Parameters:
+
    +
  • tag_class – tag class to register. Will be instantiated with this +serializer instance.

  • +
  • force – overwrite an existing tag. If false (default), a +KeyError is raised.

  • +
  • index – index to insert the new tag in the tag order. Useful when +the new tag is a special case of an existing tag. If None +(default), the tag is appended to the end of the order.

  • +
+
+
Raises:
+

KeyError – if the tag key is already registered and force is +not true.

+
+
+
+ +
+
+tag(value: Any) Any
+

Convert a value to a tagged representation if necessary.

+
+ +
+
+tags: dict[str, JSONTag]
+
+ +
+
+untag(value: dict[str, Any]) Any
+

Convert a tagged representation back to the original type.

+
+ +
+ +
+
+

Module contents

+
+
+flask.json.dump(obj: Any, fp: IO[str], **kwargs: Any) None
+

Serialize data as JSON and write to a file.

+

If current_app is available, it will use its +app.json.dump() +method, otherwise it will use json.dump().

+
+
Parameters:
+
    +
  • obj – The data to serialize.

  • +
  • fp – A file opened for writing text. Should use the UTF-8 +encoding to be valid JSON.

  • +
  • kwargs – Arguments passed to the dump implementation.

  • +
+
+
+
+

Changed in version 2.3: The app parameter was removed.

+
+
+

Changed in version 2.2: Calls current_app.json.dump, allowing an app to override +the behavior.

+
+
+

Changed in version 2.0: Writing to a binary file, and the encoding argument, will be +removed in Flask 2.1.

+
+
+ +
+
+flask.json.dumps(obj: Any, **kwargs: Any) str
+

Serialize data as JSON.

+

If current_app is available, it will use its +app.json.dumps() +method, otherwise it will use json.dumps().

+
+
Parameters:
+
    +
  • obj – The data to serialize.

  • +
  • kwargs – Arguments passed to the dumps implementation.

  • +
+
+
+
+

Changed in version 2.3: The app parameter was removed.

+
+
+

Changed in version 2.2: Calls current_app.json.dumps, allowing an app to override +the behavior.

+
+
+

Changed in version 2.0.2: decimal.Decimal is supported by converting to a string.

+
+
+

Changed in version 2.0: encoding will be removed in Flask 2.1.

+
+
+

Changed in version 1.0.3: app can be passed directly, rather than requiring an app +context for configuration.

+
+
+ +
+
+flask.json.jsonify(*args: t.Any, **kwargs: t.Any) Response
+

Serialize the given arguments as JSON, and return a +Response object with the application/json +mimetype. A dict or list returned from a view will be converted to a +JSON response automatically without needing to call this.

+

This requires an active request or application context, and calls +app.json.response().

+

In debug mode, the output is formatted with indentation to make it +easier to read. This may also be controlled by the provider.

+

Either positional or keyword arguments can be given, not both. +If no arguments are given, None is serialized.

+
+
Parameters:
+
    +
  • args – A single value to serialize, or multiple values to +treat as a list to serialize.

  • +
  • kwargs – Treat as a dict to serialize.

  • +
+
+
+
+

Changed in version 2.2: Calls current_app.json.response, allowing an app to override +the behavior.

+
+
+

Changed in version 2.0.2: decimal.Decimal is supported by converting to a string.

+
+
+

Changed in version 0.11: Added support for serializing top-level arrays. This was a +security risk in ancient browsers. See security-json.

+
+
+

Added in version 0.2.

+
+
+ +
+
+flask.json.load(fp: IO, **kwargs: Any) Any
+

Deserialize data as JSON read from a file.

+

If current_app is available, it will use its +app.json.load() +method, otherwise it will use json.load().

+
+
Parameters:
+
    +
  • fp – A file opened for reading text or UTF-8 bytes.

  • +
  • kwargs – Arguments passed to the load implementation.

  • +
+
+
+
+

Changed in version 2.3: The app parameter was removed.

+
+
+

Changed in version 2.2: Calls current_app.json.load, allowing an app to override +the behavior.

+
+
+

Changed in version 2.2: The app parameter will be removed in Flask 2.3.

+
+
+

Changed in version 2.0: encoding will be removed in Flask 2.1. The file must be text +mode, or binary mode with UTF-8 bytes.

+
+
+ +
+
+flask.json.loads(s: str | bytes, **kwargs: Any) Any
+

Deserialize data as JSON.

+

If current_app is available, it will use its +app.json.loads() +method, otherwise it will use json.loads().

+
+
Parameters:
+
    +
  • s – Text or UTF-8 bytes.

  • +
  • kwargs – Arguments passed to the loads implementation.

  • +
+
+
+
+

Changed in version 2.3: The app parameter was removed.

+
+
+

Changed in version 2.2: Calls current_app.json.loads, allowing an app to override +the behavior.

+
+
+

Changed in version 2.0: encoding will be removed in Flask 2.1. The data must be a +string or UTF-8 bytes.

+
+
+

Changed in version 1.0.3: app can be passed directly, rather than requiring an app +context for configuration.

+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html new file mode 100644 index 00000000..56d4fc0f --- /dev/null +++ b/docs/build/html/genindex.html @@ -0,0 +1,1098 @@ + + + + + + + + Index — Flask documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | K + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

J

+ + + +
+ +

K

+ + + +
+ +

L

+ + + +
+ +

M

+ + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/index.html b/docs/build/html/index.html new file mode 100644 index 00000000..99307238 --- /dev/null +++ b/docs/build/html/index.html @@ -0,0 +1,117 @@ + + + + + + + + + Flask - Documentación Personalizada — Flask documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Flask - Documentación Personalizada

+

Bienvenido a la documentación del proyecto Flask.

+
+

Contenido:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/modules.html b/docs/build/html/modules.html new file mode 100644 index 00000000..1bfa269c --- /dev/null +++ b/docs/build/html/modules.html @@ -0,0 +1,413 @@ + + + + + + + + + flask — Flask documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

flask

+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv new file mode 100644 index 00000000..18547305 Binary files /dev/null and b/docs/build/html/objects.inv differ diff --git a/docs/build/html/py-modindex.html b/docs/build/html/py-modindex.html new file mode 100644 index 00000000..0dc437d7 --- /dev/null +++ b/docs/build/html/py-modindex.html @@ -0,0 +1,215 @@ + + + + + + + + Python Module Index — Flask documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ f +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ f
+ flask +
    + flask.app +
    + flask.blueprints +
    + flask.cli +
    + flask.config +
    + flask.ctx +
    + flask.debughelpers +
    + flask.globals +
    + flask.helpers +
    + flask.json +
    + flask.json.provider +
    + flask.json.tag +
    + flask.logging +
    + flask.sessions +
    + flask.signals +
    + flask.templating +
    + flask.testing +
    + flask.typing +
    + flask.views +
    + flask.wrappers +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/search.html b/docs/build/html/search.html new file mode 100644 index 00000000..e20db996 --- /dev/null +++ b/docs/build/html/search.html @@ -0,0 +1,120 @@ + + + + + + + + Search — Flask documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js new file mode 100644 index 00000000..a054f969 --- /dev/null +++ b/docs/build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"About the First Parameter": [[0, null]], "Contenido:": [[2, null]], "Flask - Documentaci\u00f3n Personalizada": [[2, null]], "Keep in Mind": [[0, null]], "Module contents": [[0, "module-flask"], [1, "module-flask.json"]], "Submodules": [[0, "submodules"], [1, "submodules"]], "Subpackages": [[0, "subpackages"]], "Tagged JSON": [[1, "tagged-json"]], "flask": [[3, null]], "flask package": [[0, null]], "flask.app module": [[0, "module-flask.app"]], "flask.blueprints module": [[0, "module-flask.blueprints"]], "flask.cli module": [[0, "module-flask.cli"]], "flask.config module": [[0, "module-flask.config"]], "flask.ctx module": [[0, "module-flask.ctx"]], "flask.debughelpers module": [[0, "module-flask.debughelpers"]], "flask.globals module": [[0, "module-flask.globals"]], "flask.helpers module": [[0, "module-flask.helpers"]], "flask.json package": [[1, null]], "flask.json.provider module": [[1, "module-flask.json.provider"]], "flask.json.tag module": [[1, "module-flask.json.tag"]], "flask.logging module": [[0, "module-flask.logging"]], "flask.sessions module": [[0, "module-flask.sessions"]], "flask.signals module": [[0, "module-flask.signals"]], "flask.templating module": [[0, "module-flask.templating"]], "flask.testing module": [[0, "module-flask.testing"]], "flask.typing module": [[0, "module-flask.typing"]], "flask.views module": [[0, "module-flask.views"]], "flask.wrappers module": [[0, "module-flask.wrappers"]]}, "docnames": ["flask", "flask.json", "index", "modules"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2}, "filenames": ["flask.rst", "flask.json.rst", "index.rst", "modules.rst"], "indexentries": {"abort() (in module flask.helpers)": [[0, "flask.helpers.abort", false]], "accessed (flask.sessions.securecookiesession attribute)": [[0, "flask.sessions.SecureCookieSession.accessed", false]], "accessed (flask.sessions.sessionmixin attribute)": [[0, "flask.sessions.SessionMixin.accessed", false]], "after_this_request() (in module flask.ctx)": [[0, "flask.ctx.after_this_request", false]], "app_context() (flask.app.flask method)": [[0, "flask.app.Flask.app_context", false]], "app_import_path (flask.cli.scriptinfo attribute)": [[0, "flask.cli.ScriptInfo.app_import_path", false]], "appcontext (class in flask.ctx)": [[0, "flask.ctx.AppContext", false]], "appgroup (class in flask.cli)": [[0, "flask.cli.AppGroup", false]], "application (flask.testing.flaskclient attribute)": [[0, "flask.testing.FlaskClient.application", false]], "as_view() (flask.views.view class method)": [[0, "flask.views.View.as_view", false]], "async_to_sync() (flask.app.flask method)": [[0, "flask.app.Flask.async_to_sync", false]], "autocorrect_location_header (flask.wrappers.response attribute)": [[0, "flask.wrappers.Response.autocorrect_location_header", false]], "blueprint (class in flask.blueprints)": [[0, "flask.blueprints.Blueprint", false]], "blueprint (flask.wrappers.request property)": [[0, "flask.wrappers.Request.blueprint", false]], "blueprints (flask.wrappers.request property)": [[0, "flask.wrappers.Request.blueprints", false]], "certparamtype (class in flask.cli)": [[0, "flask.cli.CertParamType", false]], "check() (flask.json.tag.jsontag method)": [[1, "flask.json.tag.JSONTag.check", false], [1, "id1", false]], "check() (flask.json.tag.passdict method)": [[1, "flask.json.tag.PassDict.check", false]], "check() (flask.json.tag.passlist method)": [[1, "flask.json.tag.PassList.check", false]], "check() (flask.json.tag.tagbytes method)": [[1, "flask.json.tag.TagBytes.check", false]], "check() (flask.json.tag.tagdatetime method)": [[1, "flask.json.tag.TagDateTime.check", false]], "check() (flask.json.tag.tagdict method)": [[1, "flask.json.tag.TagDict.check", false]], "check() (flask.json.tag.tagmarkup method)": [[1, "flask.json.tag.TagMarkup.check", false]], "check() (flask.json.tag.tagtuple method)": [[1, "flask.json.tag.TagTuple.check", false]], "check() (flask.json.tag.taguuid method)": [[1, "flask.json.tag.TagUUID.check", false]], "clear() (flask.sessions.nullsession method)": [[0, "flask.sessions.NullSession.clear", false]], "cli (flask.app.flask attribute)": [[0, "flask.app.Flask.cli", false]], "cli (flask.blueprints.blueprint attribute)": [[0, "flask.blueprints.Blueprint.cli", false]], "command() (flask.cli.appgroup method)": [[0, "flask.cli.AppGroup.command", false]], "compact (flask.json.provider.defaultjsonprovider attribute)": [[1, "flask.json.provider.DefaultJSONProvider.compact", false]], "config (class in flask.config)": [[0, "flask.config.Config", false]], "configattribute (class in flask.config)": [[0, "flask.config.ConfigAttribute", false]], "convert() (flask.cli.certparamtype method)": [[0, "flask.cli.CertParamType.convert", false]], "convert() (flask.cli.separatedpathtype method)": [[0, "flask.cli.SeparatedPathType.convert", false]], "copy() (flask.ctx.requestcontext method)": [[0, "flask.ctx.RequestContext.copy", false]], "copy_current_request_context() (in module flask.ctx)": [[0, "flask.ctx.copy_current_request_context", false]], "create_app (flask.cli.scriptinfo attribute)": [[0, "flask.cli.ScriptInfo.create_app", false]], "create_jinja_environment() (flask.app.flask method)": [[0, "flask.app.Flask.create_jinja_environment", false]], "create_logger() (in module flask.logging)": [[0, "flask.logging.create_logger", false]], "create_url_adapter() (flask.app.flask method)": [[0, "flask.app.Flask.create_url_adapter", false]], "data (flask.cli.scriptinfo attribute)": [[0, "flask.cli.ScriptInfo.data", false]], "debugfileskeyerror": [[0, "flask.debughelpers.DebugFilesKeyError", false]], "decorators (flask.views.view attribute)": [[0, "flask.views.View.decorators", false]], "default() (flask.json.provider.defaultjsonprovider static method)": [[1, "flask.json.provider.DefaultJSONProvider.default", false]], "default_config (flask.app.flask attribute)": [[0, "flask.app.Flask.default_config", false]], "default_handler (in module flask.logging)": [[0, "flask.logging.default_handler", false]], "default_mimetype (flask.wrappers.response attribute)": [[0, "flask.wrappers.Response.default_mimetype", false]], "default_tags (flask.json.tag.taggedjsonserializer attribute)": [[1, "flask.json.tag.TaggedJSONSerializer.default_tags", false], [1, "id7", false]], "defaultjsonprovider (class in flask.json.provider)": [[1, "flask.json.provider.DefaultJSONProvider", false]], "digest_method() (flask.sessions.securecookiesessioninterface static method)": [[0, "flask.sessions.SecureCookieSessionInterface.digest_method", false]], "dispatch_request() (flask.app.flask method)": [[0, "flask.app.Flask.dispatch_request", false]], "dispatch_request() (flask.views.methodview method)": [[0, "flask.views.MethodView.dispatch_request", false]], "dispatch_request() (flask.views.view method)": [[0, "flask.views.View.dispatch_request", false]], "dispatchingjinjaloader (class in flask.templating)": [[0, "flask.templating.DispatchingJinjaLoader", false]], "do_teardown_appcontext() (flask.app.flask method)": [[0, "flask.app.Flask.do_teardown_appcontext", false]], "do_teardown_request() (flask.app.flask method)": [[0, "flask.app.Flask.do_teardown_request", false]], "dump() (flask.json.provider.jsonprovider method)": [[1, "flask.json.provider.JSONProvider.dump", false]], "dump() (in module flask.json)": [[1, "flask.json.dump", false]], "dumps() (flask.json.provider.defaultjsonprovider method)": [[1, "flask.json.provider.DefaultJSONProvider.dumps", false]], "dumps() (flask.json.provider.jsonprovider method)": [[1, "flask.json.provider.JSONProvider.dumps", false]], "dumps() (flask.json.tag.taggedjsonserializer method)": [[1, "flask.json.tag.TaggedJSONSerializer.dumps", false], [1, "id8", false]], "dumps() (in module flask.json)": [[1, "flask.json.dumps", false]], "endpoint (flask.wrappers.request property)": [[0, "flask.wrappers.Request.endpoint", false]], "ensure_ascii (flask.json.provider.defaultjsonprovider attribute)": [[1, "flask.json.provider.DefaultJSONProvider.ensure_ascii", false]], "ensure_sync() (flask.app.flask method)": [[0, "flask.app.Flask.ensure_sync", false]], "environbuilder (class in flask.testing)": [[0, "flask.testing.EnvironBuilder", false]], "environment (class in flask.templating)": [[0, "flask.templating.Environment", false]], "environment variable": [[0, "index-0", false], [0, "index-1", false], [0, "index-2", false]], "explain_template_loading_attempts() (in module flask.debughelpers)": [[0, "flask.debughelpers.explain_template_loading_attempts", false]], "finalize_request() (flask.app.flask method)": [[0, "flask.app.Flask.finalize_request", false]], "find_app_by_string() (in module flask.cli)": [[0, "flask.cli.find_app_by_string", false]], "find_best_app() (in module flask.cli)": [[0, "flask.cli.find_best_app", false]], "flash() (in module flask.helpers)": [[0, "flask.helpers.flash", false]], "flask": [[0, "module-flask", false]], "flask (class in flask.app)": [[0, "flask.app.Flask", false]], "flask.app": [[0, "module-flask.app", false]], "flask.blueprints": [[0, "module-flask.blueprints", false]], "flask.cli": [[0, "module-flask.cli", false]], "flask.config": [[0, "module-flask.config", false]], "flask.ctx": [[0, "module-flask.ctx", false]], "flask.debughelpers": [[0, "module-flask.debughelpers", false]], "flask.globals": [[0, "module-flask.globals", false]], "flask.helpers": [[0, "module-flask.helpers", false]], "flask.json": [[1, "module-flask.json", false]], "flask.json.provider": [[1, "module-flask.json.provider", false]], "flask.json.tag": [[1, "module-flask.json.tag", false]], "flask.logging": [[0, "module-flask.logging", false]], "flask.sessions": [[0, "module-flask.sessions", false]], "flask.signals": [[0, "module-flask.signals", false]], "flask.templating": [[0, "module-flask.templating", false]], "flask.testing": [[0, "module-flask.testing", false]], "flask.typing": [[0, "module-flask.typing", false]], "flask.views": [[0, "module-flask.views", false]], "flask.wrappers": [[0, "module-flask.wrappers", false]], "flask_debug": [[0, "index-0", false], [0, "index-1", false]], "flask_skip_dotenv": [[0, "index-2", false]], "flaskclient (class in flask.testing)": [[0, "flask.testing.FlaskClient", false]], "flaskclirunner (class in flask.testing)": [[0, "flask.testing.FlaskCliRunner", false]], "flaskgroup (class in flask.cli)": [[0, "flask.cli.FlaskGroup", false]], "formdataroutingredirect": [[0, "flask.debughelpers.FormDataRoutingRedirect", false]], "from_envvar() (flask.config.config method)": [[0, "flask.config.Config.from_envvar", false]], "from_file() (flask.config.config method)": [[0, "flask.config.Config.from_file", false]], "from_mapping() (flask.config.config method)": [[0, "flask.config.Config.from_mapping", false]], "from_object() (flask.config.config method)": [[0, "flask.config.Config.from_object", false]], "from_prefixed_env() (flask.config.config method)": [[0, "flask.config.Config.from_prefixed_env", false]], "from_pyfile() (flask.config.config method)": [[0, "flask.config.Config.from_pyfile", false]], "full_dispatch_request() (flask.app.flask method)": [[0, "flask.app.Flask.full_dispatch_request", false]], "get() (flask.sessions.securecookiesession method)": [[0, "flask.sessions.SecureCookieSession.get", false]], "get_command() (flask.cli.flaskgroup method)": [[0, "flask.cli.FlaskGroup.get_command", false]], "get_cookie_domain() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_cookie_domain", false]], "get_cookie_httponly() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_cookie_httponly", false]], "get_cookie_name() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_cookie_name", false]], "get_cookie_partitioned() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_cookie_partitioned", false]], "get_cookie_path() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_cookie_path", false]], "get_cookie_samesite() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_cookie_samesite", false]], "get_cookie_secure() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_cookie_secure", false]], "get_debug_flag() (in module flask.helpers)": [[0, "flask.helpers.get_debug_flag", false]], "get_expiration_time() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.get_expiration_time", false]], "get_flashed_messages() (in module flask.helpers)": [[0, "flask.helpers.get_flashed_messages", false]], "get_load_dotenv() (in module flask.helpers)": [[0, "flask.helpers.get_load_dotenv", false]], "get_namespace() (flask.config.config method)": [[0, "flask.config.Config.get_namespace", false]], "get_send_file_max_age() (flask.app.flask method)": [[0, "flask.app.Flask.get_send_file_max_age", false]], "get_send_file_max_age() (flask.blueprints.blueprint method)": [[0, "flask.blueprints.Blueprint.get_send_file_max_age", false]], "get_signing_serializer() (flask.sessions.securecookiesessioninterface method)": [[0, "flask.sessions.SecureCookieSessionInterface.get_signing_serializer", false]], "get_source() (flask.templating.dispatchingjinjaloader method)": [[0, "flask.templating.DispatchingJinjaLoader.get_source", false]], "get_template_attribute() (in module flask.helpers)": [[0, "flask.helpers.get_template_attribute", false]], "get_version() (in module flask.cli)": [[0, "flask.cli.get_version", false]], "group() (flask.cli.appgroup method)": [[0, "flask.cli.AppGroup.group", false]], "handle_exception() (flask.app.flask method)": [[0, "flask.app.Flask.handle_exception", false]], "handle_http_exception() (flask.app.flask method)": [[0, "flask.app.Flask.handle_http_exception", false]], "handle_user_exception() (flask.app.flask method)": [[0, "flask.app.Flask.handle_user_exception", false]], "has_app_context() (in module flask.ctx)": [[0, "flask.ctx.has_app_context", false]], "has_level_handler() (in module flask.logging)": [[0, "flask.logging.has_level_handler", false]], "has_request_context() (in module flask.ctx)": [[0, "flask.ctx.has_request_context", false]], "init_every_request (flask.views.view attribute)": [[0, "flask.views.View.init_every_request", false]], "invoke() (flask.testing.flaskclirunner method)": [[0, "flask.testing.FlaskCliRunner.invoke", false]], "is_null_session() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.is_null_session", false]], "json_dumps() (flask.testing.environbuilder method)": [[0, "flask.testing.EnvironBuilder.json_dumps", false]], "json_module (flask.wrappers.request attribute)": [[0, "flask.wrappers.Request.json_module", false]], "json_module (flask.wrappers.response attribute)": [[0, "flask.wrappers.Response.json_module", false]], "jsonify() (in module flask.json)": [[1, "flask.json.jsonify", false]], "jsonprovider (class in flask.json.provider)": [[1, "flask.json.provider.JSONProvider", false]], "jsontag (class in flask.json.tag)": [[1, "flask.json.tag.JSONTag", false], [1, "id0", false]], "key (flask.json.tag.jsontag attribute)": [[1, "flask.json.tag.JSONTag.key", false], [1, "id2", false]], "key (flask.json.tag.tagbytes attribute)": [[1, "flask.json.tag.TagBytes.key", false]], "key (flask.json.tag.tagdatetime attribute)": [[1, "flask.json.tag.TagDateTime.key", false]], "key (flask.json.tag.tagdict attribute)": [[1, "flask.json.tag.TagDict.key", false]], "key (flask.json.tag.tagmarkup attribute)": [[1, "flask.json.tag.TagMarkup.key", false]], "key (flask.json.tag.tagtuple attribute)": [[1, "flask.json.tag.TagTuple.key", false]], "key (flask.json.tag.taguuid attribute)": [[1, "flask.json.tag.TagUUID.key", false]], "key_derivation (flask.sessions.securecookiesessioninterface attribute)": [[0, "flask.sessions.SecureCookieSessionInterface.key_derivation", false]], "list_commands() (flask.cli.flaskgroup method)": [[0, "flask.cli.FlaskGroup.list_commands", false]], "list_templates() (flask.templating.dispatchingjinjaloader method)": [[0, "flask.templating.DispatchingJinjaLoader.list_templates", false]], "load() (flask.json.provider.jsonprovider method)": [[1, "flask.json.provider.JSONProvider.load", false]], "load() (in module flask.json)": [[1, "flask.json.load", false]], "load_app() (flask.cli.scriptinfo method)": [[0, "flask.cli.ScriptInfo.load_app", false]], "load_dotenv() (in module flask.cli)": [[0, "flask.cli.load_dotenv", false]], "load_dotenv_defaults (flask.cli.scriptinfo attribute)": [[0, "flask.cli.ScriptInfo.load_dotenv_defaults", false]], "loads() (flask.json.provider.defaultjsonprovider method)": [[1, "flask.json.provider.DefaultJSONProvider.loads", false]], "loads() (flask.json.provider.jsonprovider method)": [[1, "flask.json.provider.JSONProvider.loads", false]], "loads() (flask.json.tag.taggedjsonserializer method)": [[1, "flask.json.tag.TaggedJSONSerializer.loads", false], [1, "id9", false]], "loads() (in module flask.json)": [[1, "flask.json.loads", false]], "locate_app() (in module flask.cli)": [[0, "flask.cli.locate_app", false]], "log_exception() (flask.app.flask method)": [[0, "flask.app.Flask.log_exception", false]], "main() (in module flask.cli)": [[0, "flask.cli.main", false]], "make_context() (flask.cli.flaskgroup method)": [[0, "flask.cli.FlaskGroup.make_context", false]], "make_default_options_response() (flask.app.flask method)": [[0, "flask.app.Flask.make_default_options_response", false]], "make_null_session() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.make_null_session", false]], "make_response() (flask.app.flask method)": [[0, "flask.app.Flask.make_response", false]], "make_response() (in module flask.helpers)": [[0, "flask.helpers.make_response", false]], "make_shell_context() (flask.app.flask method)": [[0, "flask.app.Flask.make_shell_context", false]], "match_request() (flask.ctx.requestcontext method)": [[0, "flask.ctx.RequestContext.match_request", false]], "max_content_length (flask.wrappers.request property)": [[0, "flask.wrappers.Request.max_content_length", false]], "max_cookie_size (flask.wrappers.response property)": [[0, "flask.wrappers.Response.max_cookie_size", false]], "max_form_memory_size (flask.wrappers.request property)": [[0, "flask.wrappers.Request.max_form_memory_size", false]], "max_form_parts (flask.wrappers.request property)": [[0, "flask.wrappers.Request.max_form_parts", false]], "methods (flask.views.view attribute)": [[0, "flask.views.View.methods", false]], "methodview (class in flask.views)": [[0, "flask.views.MethodView", false]], "mimetype (flask.json.provider.defaultjsonprovider attribute)": [[1, "flask.json.provider.DefaultJSONProvider.mimetype", false]], "modified (flask.sessions.securecookiesession attribute)": [[0, "flask.sessions.SecureCookieSession.modified", false]], "modified (flask.sessions.sessionmixin attribute)": [[0, "flask.sessions.SessionMixin.modified", false]], "module": [[0, "module-flask", false], [0, "module-flask.app", false], [0, "module-flask.blueprints", false], [0, "module-flask.cli", false], [0, "module-flask.config", false], [0, "module-flask.ctx", false], [0, "module-flask.debughelpers", false], [0, "module-flask.globals", false], [0, "module-flask.helpers", false], [0, "module-flask.logging", false], [0, "module-flask.sessions", false], [0, "module-flask.signals", false], [0, "module-flask.templating", false], [0, "module-flask.testing", false], [0, "module-flask.typing", false], [0, "module-flask.views", false], [0, "module-flask.wrappers", false], [1, "module-flask.json", false], [1, "module-flask.json.provider", false], [1, "module-flask.json.tag", false]], "name (flask.cli.certparamtype attribute)": [[0, "flask.cli.CertParamType.name", false]], "new (flask.sessions.sessionmixin attribute)": [[0, "flask.sessions.SessionMixin.new", false]], "noappexception": [[0, "flask.cli.NoAppException", false]], "null_session_class (flask.sessions.sessioninterface attribute)": [[0, "flask.sessions.SessionInterface.null_session_class", false]], "nullsession (class in flask.sessions)": [[0, "flask.sessions.NullSession", false]], "on_json_loading_failed() (flask.wrappers.request method)": [[0, "flask.wrappers.Request.on_json_loading_failed", false]], "open() (flask.testing.flaskclient method)": [[0, "flask.testing.FlaskClient.open", false]], "open_instance_resource() (flask.app.flask method)": [[0, "flask.app.Flask.open_instance_resource", false]], "open_resource() (flask.app.flask method)": [[0, "flask.app.Flask.open_resource", false]], "open_resource() (flask.blueprints.blueprint method)": [[0, "flask.blueprints.Blueprint.open_resource", false]], "open_session() (flask.sessions.securecookiesessioninterface method)": [[0, "flask.sessions.SecureCookieSessionInterface.open_session", false]], "open_session() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.open_session", false]], "order (flask.json.tag.taggedjsonserializer attribute)": [[1, "flask.json.tag.TaggedJSONSerializer.order", false]], "parse_args() (flask.cli.flaskgroup method)": [[0, "flask.cli.FlaskGroup.parse_args", false]], "passdict (class in flask.json.tag)": [[1, "flask.json.tag.PassDict", false]], "passlist (class in flask.json.tag)": [[1, "flask.json.tag.PassList", false]], "permanent (flask.sessions.sessionmixin property)": [[0, "flask.sessions.SessionMixin.permanent", false]], "pickle_based (flask.sessions.sessioninterface attribute)": [[0, "flask.sessions.SessionInterface.pickle_based", false]], "pop() (flask.ctx.appcontext method)": [[0, "flask.ctx.AppContext.pop", false]], "pop() (flask.ctx.requestcontext method)": [[0, "flask.ctx.RequestContext.pop", false]], "pop() (flask.sessions.nullsession method)": [[0, "flask.sessions.NullSession.pop", false]], "popitem() (flask.sessions.nullsession method)": [[0, "flask.sessions.NullSession.popitem", false]], "prepare_import() (in module flask.cli)": [[0, "flask.cli.prepare_import", false]], "preprocess_request() (flask.app.flask method)": [[0, "flask.app.Flask.preprocess_request", false]], "process_response() (flask.app.flask method)": [[0, "flask.app.Flask.process_response", false]], "provide_automatic_options (flask.views.view attribute)": [[0, "flask.views.View.provide_automatic_options", false]], "push() (flask.ctx.appcontext method)": [[0, "flask.ctx.AppContext.push", false]], "push() (flask.ctx.requestcontext method)": [[0, "flask.ctx.RequestContext.push", false]], "redirect() (in module flask.helpers)": [[0, "flask.helpers.redirect", false]], "register() (flask.json.tag.taggedjsonserializer method)": [[1, "flask.json.tag.TaggedJSONSerializer.register", false], [1, "id10", false]], "render_template() (in module flask.templating)": [[0, "flask.templating.render_template", false]], "render_template_string() (in module flask.templating)": [[0, "flask.templating.render_template_string", false]], "request (class in flask.wrappers)": [[0, "flask.wrappers.Request", false]], "request_class (flask.app.flask attribute)": [[0, "flask.app.Flask.request_class", false]], "request_context() (flask.app.flask method)": [[0, "flask.app.Flask.request_context", false]], "requestcontext (class in flask.ctx)": [[0, "flask.ctx.RequestContext", false]], "response (class in flask.wrappers)": [[0, "flask.wrappers.Response", false]], "response() (flask.json.provider.defaultjsonprovider method)": [[1, "flask.json.provider.DefaultJSONProvider.response", false]], "response() (flask.json.provider.jsonprovider method)": [[1, "flask.json.provider.JSONProvider.response", false]], "response_class (flask.app.flask attribute)": [[0, "flask.app.Flask.response_class", false]], "rfc": [[0, "index-3", false], [1, "index-0", false]], "rfc 2231": [[0, "index-3", false]], "rfc 822": [[1, "index-0", false]], "routing_exception (flask.wrappers.request attribute)": [[0, "flask.wrappers.Request.routing_exception", false]], "run() (flask.app.flask method)": [[0, "flask.app.Flask.run", false]], "salt (flask.sessions.securecookiesessioninterface attribute)": [[0, "flask.sessions.SecureCookieSessionInterface.salt", false]], "save_session() (flask.sessions.securecookiesessioninterface method)": [[0, "flask.sessions.SecureCookieSessionInterface.save_session", false]], "save_session() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.save_session", false]], "scriptinfo (class in flask.cli)": [[0, "flask.cli.ScriptInfo", false]], "securecookiesession (class in flask.sessions)": [[0, "flask.sessions.SecureCookieSession", false]], "securecookiesessioninterface (class in flask.sessions)": [[0, "flask.sessions.SecureCookieSessionInterface", false]], "send_file() (in module flask.helpers)": [[0, "flask.helpers.send_file", false]], "send_from_directory() (in module flask.helpers)": [[0, "flask.helpers.send_from_directory", false]], "send_static_file() (flask.app.flask method)": [[0, "flask.app.Flask.send_static_file", false]], "send_static_file() (flask.blueprints.blueprint method)": [[0, "flask.blueprints.Blueprint.send_static_file", false]], "separatedpathtype (class in flask.cli)": [[0, "flask.cli.SeparatedPathType", false]], "serializer (flask.json.tag.jsontag attribute)": [[1, "flask.json.tag.JSONTag.serializer", false]], "serializer (flask.sessions.securecookiesessioninterface attribute)": [[0, "flask.sessions.SecureCookieSessionInterface.serializer", false]], "session_class (flask.sessions.securecookiesessioninterface attribute)": [[0, "flask.sessions.SecureCookieSessionInterface.session_class", false]], "session_interface (flask.app.flask attribute)": [[0, "flask.app.Flask.session_interface", false]], "session_transaction() (flask.testing.flaskclient method)": [[0, "flask.testing.FlaskClient.session_transaction", false]], "sessioninterface (class in flask.sessions)": [[0, "flask.sessions.SessionInterface", false]], "sessionmixin (class in flask.sessions)": [[0, "flask.sessions.SessionMixin", false]], "setdefault() (flask.sessions.nullsession method)": [[0, "flask.sessions.NullSession.setdefault", false]], "setdefault() (flask.sessions.securecookiesession method)": [[0, "flask.sessions.SecureCookieSession.setdefault", false]], "should_set_cookie() (flask.sessions.sessioninterface method)": [[0, "flask.sessions.SessionInterface.should_set_cookie", false]], "show_server_banner() (in module flask.cli)": [[0, "flask.cli.show_server_banner", false]], "sort_keys (flask.json.provider.defaultjsonprovider attribute)": [[1, "flask.json.provider.DefaultJSONProvider.sort_keys", false]], "stream_template() (in module flask.templating)": [[0, "flask.templating.stream_template", false]], "stream_template_string() (in module flask.templating)": [[0, "flask.templating.stream_template_string", false]], "stream_with_context() (in module flask.helpers)": [[0, "flask.helpers.stream_with_context", false]], "tag() (flask.json.tag.jsontag method)": [[1, "flask.json.tag.JSONTag.tag", false], [1, "id3", false]], "tag() (flask.json.tag.passdict method)": [[1, "flask.json.tag.PassDict.tag", false]], "tag() (flask.json.tag.passlist method)": [[1, "flask.json.tag.PassList.tag", false]], "tag() (flask.json.tag.taggedjsonserializer method)": [[1, "flask.json.tag.TaggedJSONSerializer.tag", false], [1, "id11", false]], "tagbytes (class in flask.json.tag)": [[1, "flask.json.tag.TagBytes", false]], "tagdatetime (class in flask.json.tag)": [[1, "flask.json.tag.TagDateTime", false]], "tagdict (class in flask.json.tag)": [[1, "flask.json.tag.TagDict", false]], "taggedjsonserializer (class in flask.json.tag)": [[1, "flask.json.tag.TaggedJSONSerializer", false], [1, "id6", false]], "tagmarkup (class in flask.json.tag)": [[1, "flask.json.tag.TagMarkup", false]], "tags (flask.json.tag.taggedjsonserializer attribute)": [[1, "flask.json.tag.TaggedJSONSerializer.tags", false]], "tagtuple (class in flask.json.tag)": [[1, "flask.json.tag.TagTuple", false]], "taguuid (class in flask.json.tag)": [[1, "flask.json.tag.TagUUID", false]], "test_cli_runner() (flask.app.flask method)": [[0, "flask.app.Flask.test_cli_runner", false]], "test_client() (flask.app.flask method)": [[0, "flask.app.Flask.test_client", false]], "test_request_context() (flask.app.flask method)": [[0, "flask.app.Flask.test_request_context", false]], "to_json() (flask.json.tag.jsontag method)": [[1, "flask.json.tag.JSONTag.to_json", false], [1, "id4", false]], "to_json() (flask.json.tag.passdict method)": [[1, "flask.json.tag.PassDict.to_json", false]], "to_json() (flask.json.tag.passlist method)": [[1, "flask.json.tag.PassList.to_json", false]], "to_json() (flask.json.tag.tagbytes method)": [[1, "flask.json.tag.TagBytes.to_json", false]], "to_json() (flask.json.tag.tagdatetime method)": [[1, "flask.json.tag.TagDateTime.to_json", false]], "to_json() (flask.json.tag.tagdict method)": [[1, "flask.json.tag.TagDict.to_json", false]], "to_json() (flask.json.tag.tagmarkup method)": [[1, "flask.json.tag.TagMarkup.to_json", false]], "to_json() (flask.json.tag.tagtuple method)": [[1, "flask.json.tag.TagTuple.to_json", false]], "to_json() (flask.json.tag.taguuid method)": [[1, "flask.json.tag.TagUUID.to_json", false]], "to_python() (flask.json.tag.jsontag method)": [[1, "flask.json.tag.JSONTag.to_python", false], [1, "id5", false]], "to_python() (flask.json.tag.tagbytes method)": [[1, "flask.json.tag.TagBytes.to_python", false]], "to_python() (flask.json.tag.tagdatetime method)": [[1, "flask.json.tag.TagDateTime.to_python", false]], "to_python() (flask.json.tag.tagdict method)": [[1, "flask.json.tag.TagDict.to_python", false]], "to_python() (flask.json.tag.tagmarkup method)": [[1, "flask.json.tag.TagMarkup.to_python", false]], "to_python() (flask.json.tag.tagtuple method)": [[1, "flask.json.tag.TagTuple.to_python", false]], "to_python() (flask.json.tag.taguuid method)": [[1, "flask.json.tag.TagUUID.to_python", false]], "unexpectedunicodeerror": [[0, "flask.debughelpers.UnexpectedUnicodeError", false]], "untag() (flask.json.tag.taggedjsonserializer method)": [[1, "flask.json.tag.TaggedJSONSerializer.untag", false], [1, "id12", false]], "update() (flask.sessions.nullsession method)": [[0, "flask.sessions.NullSession.update", false]], "update_template_context() (flask.app.flask method)": [[0, "flask.app.Flask.update_template_context", false]], "url_for() (flask.app.flask method)": [[0, "flask.app.Flask.url_for", false]], "url_for() (in module flask.helpers)": [[0, "flask.helpers.url_for", false]], "url_rule (flask.wrappers.request attribute)": [[0, "flask.wrappers.Request.url_rule", false]], "view (class in flask.views)": [[0, "flask.views.View", false]], "view_args (flask.wrappers.request attribute)": [[0, "flask.wrappers.Request.view_args", false]], "with_appcontext() (in module flask.cli)": [[0, "flask.cli.with_appcontext", false]], "wsgi_app() (flask.app.flask method)": [[0, "flask.app.Flask.wsgi_app", false]]}, "objects": {"": [[0, 0, 0, "-", "flask"]], "flask": [[0, 0, 0, "-", "app"], [0, 0, 0, "-", "blueprints"], [0, 0, 0, "-", "cli"], [0, 0, 0, "-", "config"], [0, 0, 0, "-", "ctx"], [0, 0, 0, "-", "debughelpers"], [0, 0, 0, "-", "globals"], [0, 0, 0, "-", "helpers"], [1, 0, 0, "-", "json"], [0, 0, 0, "-", "logging"], [0, 0, 0, "-", "sessions"], [0, 0, 0, "-", "signals"], [0, 0, 0, "-", "templating"], [0, 0, 0, "-", "testing"], [0, 0, 0, "-", "typing"], [0, 0, 0, "-", "views"], [0, 0, 0, "-", "wrappers"]], "flask.app": [[0, 1, 1, "", "Flask"]], "flask.app.Flask": [[0, 2, 1, "", "app_context"], [0, 2, 1, "", "async_to_sync"], [0, 3, 1, "", "cli"], [0, 2, 1, "", "create_jinja_environment"], [0, 2, 1, "", "create_url_adapter"], [0, 3, 1, "", "default_config"], [0, 2, 1, "", "dispatch_request"], [0, 2, 1, "", "do_teardown_appcontext"], [0, 2, 1, "", "do_teardown_request"], [0, 2, 1, "", "ensure_sync"], [0, 2, 1, "", "finalize_request"], [0, 2, 1, "", "full_dispatch_request"], [0, 2, 1, "", "get_send_file_max_age"], [0, 2, 1, "", "handle_exception"], [0, 2, 1, "", "handle_http_exception"], [0, 2, 1, "", "handle_user_exception"], [0, 2, 1, "", "log_exception"], [0, 2, 1, "", "make_default_options_response"], [0, 2, 1, "", "make_response"], [0, 2, 1, "", "make_shell_context"], [0, 2, 1, "", "open_instance_resource"], [0, 2, 1, "", "open_resource"], [0, 2, 1, "", "preprocess_request"], [0, 2, 1, "", "process_response"], [0, 3, 1, "", "request_class"], [0, 2, 1, "", "request_context"], [0, 3, 1, "", "response_class"], [0, 2, 1, "", "run"], [0, 2, 1, "", "send_static_file"], [0, 3, 1, "", "session_interface"], [0, 2, 1, "", "test_cli_runner"], [0, 2, 1, "", "test_client"], [0, 2, 1, "", "test_request_context"], [0, 2, 1, "", "update_template_context"], [0, 2, 1, "", "url_for"], [0, 2, 1, "", "wsgi_app"]], "flask.blueprints": [[0, 1, 1, "", "Blueprint"]], "flask.blueprints.Blueprint": [[0, 3, 1, "", "cli"], [0, 2, 1, "", "get_send_file_max_age"], [0, 2, 1, "", "open_resource"], [0, 2, 1, "", "send_static_file"]], "flask.cli": [[0, 1, 1, "", "AppGroup"], [0, 1, 1, "", "CertParamType"], [0, 1, 1, "", "FlaskGroup"], [0, 4, 1, "", "NoAppException"], [0, 1, 1, "", "ScriptInfo"], [0, 1, 1, "", "SeparatedPathType"], [0, 5, 1, "", "find_app_by_string"], [0, 5, 1, "", "find_best_app"], [0, 5, 1, "", "get_version"], [0, 5, 1, "", "load_dotenv"], [0, 5, 1, "", "locate_app"], [0, 5, 1, "", "main"], [0, 5, 1, "", "prepare_import"], [0, 5, 1, "", "show_server_banner"], [0, 5, 1, "", "with_appcontext"]], "flask.cli.AppGroup": [[0, 2, 1, "", "command"], [0, 2, 1, "", "group"]], "flask.cli.CertParamType": [[0, 2, 1, "", "convert"], [0, 3, 1, "", "name"]], "flask.cli.FlaskGroup": [[0, 2, 1, "", "get_command"], [0, 2, 1, "", "list_commands"], [0, 2, 1, "", "make_context"], [0, 2, 1, "", "parse_args"]], "flask.cli.ScriptInfo": [[0, 3, 1, "", "app_import_path"], [0, 3, 1, "", "create_app"], [0, 3, 1, "", "data"], [0, 2, 1, "", "load_app"], [0, 3, 1, "", "load_dotenv_defaults"]], "flask.cli.SeparatedPathType": [[0, 2, 1, "", "convert"]], "flask.config": [[0, 1, 1, "", "Config"], [0, 1, 1, "", "ConfigAttribute"]], "flask.config.Config": [[0, 2, 1, "", "from_envvar"], [0, 2, 1, "", "from_file"], [0, 2, 1, "", "from_mapping"], [0, 2, 1, "", "from_object"], [0, 2, 1, "", "from_prefixed_env"], [0, 2, 1, "", "from_pyfile"], [0, 2, 1, "", "get_namespace"]], "flask.ctx": [[0, 1, 1, "", "AppContext"], [0, 1, 1, "", "RequestContext"], [0, 5, 1, "", "after_this_request"], [0, 5, 1, "", "copy_current_request_context"], [0, 5, 1, "", "has_app_context"], [0, 5, 1, "", "has_request_context"]], "flask.ctx.AppContext": [[0, 2, 1, "", "pop"], [0, 2, 1, "", "push"]], "flask.ctx.RequestContext": [[0, 2, 1, "", "copy"], [0, 2, 1, "", "match_request"], [0, 2, 1, "", "pop"], [0, 2, 1, "", "push"]], "flask.debughelpers": [[0, 4, 1, "", "DebugFilesKeyError"], [0, 4, 1, "", "FormDataRoutingRedirect"], [0, 4, 1, "", "UnexpectedUnicodeError"], [0, 5, 1, "", "explain_template_loading_attempts"]], "flask.helpers": [[0, 5, 1, "", "abort"], [0, 5, 1, "", "flash"], [0, 5, 1, "", "get_debug_flag"], [0, 5, 1, "", "get_flashed_messages"], [0, 5, 1, "", "get_load_dotenv"], [0, 5, 1, "", "get_template_attribute"], [0, 5, 1, "", "make_response"], [0, 5, 1, "", "redirect"], [0, 5, 1, "", "send_file"], [0, 5, 1, "", "send_from_directory"], [0, 5, 1, "", "stream_with_context"], [0, 5, 1, "", "url_for"]], "flask.json": [[1, 5, 1, "", "dump"], [1, 5, 1, "", "dumps"], [1, 5, 1, "", "jsonify"], [1, 5, 1, "", "load"], [1, 5, 1, "", "loads"], [1, 0, 0, "-", "provider"], [1, 0, 0, "-", "tag"]], "flask.json.provider": [[1, 1, 1, "", "DefaultJSONProvider"], [1, 1, 1, "", "JSONProvider"]], "flask.json.provider.DefaultJSONProvider": [[1, 3, 1, "", "compact"], [1, 2, 1, "", "default"], [1, 2, 1, "", "dumps"], [1, 3, 1, "", "ensure_ascii"], [1, 2, 1, "", "loads"], [1, 3, 1, "", "mimetype"], [1, 2, 1, "", "response"], [1, 3, 1, "", "sort_keys"]], "flask.json.provider.JSONProvider": [[1, 2, 1, "", "dump"], [1, 2, 1, "", "dumps"], [1, 2, 1, "", "load"], [1, 2, 1, "", "loads"], [1, 2, 1, "", "response"]], "flask.json.tag": [[1, 1, 1, "id0", "JSONTag"], [1, 1, 1, "", "PassDict"], [1, 1, 1, "", "PassList"], [1, 1, 1, "", "TagBytes"], [1, 1, 1, "", "TagDateTime"], [1, 1, 1, "", "TagDict"], [1, 1, 1, "", "TagMarkup"], [1, 1, 1, "", "TagTuple"], [1, 1, 1, "", "TagUUID"], [1, 1, 1, "id6", "TaggedJSONSerializer"]], "flask.json.tag.JSONTag": [[1, 2, 1, "id1", "check"], [1, 3, 1, "id2", "key"], [1, 3, 1, "", "serializer"], [1, 2, 1, "id3", "tag"], [1, 2, 1, "id4", "to_json"], [1, 2, 1, "id5", "to_python"]], "flask.json.tag.PassDict": [[1, 2, 1, "", "check"], [1, 2, 1, "", "tag"], [1, 2, 1, "", "to_json"]], "flask.json.tag.PassList": [[1, 2, 1, "", "check"], [1, 2, 1, "", "tag"], [1, 2, 1, "", "to_json"]], "flask.json.tag.TagBytes": [[1, 2, 1, "", "check"], [1, 3, 1, "", "key"], [1, 2, 1, "", "to_json"], [1, 2, 1, "", "to_python"]], "flask.json.tag.TagDateTime": [[1, 2, 1, "", "check"], [1, 3, 1, "", "key"], [1, 2, 1, "", "to_json"], [1, 2, 1, "", "to_python"]], "flask.json.tag.TagDict": [[1, 2, 1, "", "check"], [1, 3, 1, "", "key"], [1, 2, 1, "", "to_json"], [1, 2, 1, "", "to_python"]], "flask.json.tag.TagMarkup": [[1, 2, 1, "", "check"], [1, 3, 1, "", "key"], [1, 2, 1, "", "to_json"], [1, 2, 1, "", "to_python"]], "flask.json.tag.TagTuple": [[1, 2, 1, "", "check"], [1, 3, 1, "", "key"], [1, 2, 1, "", "to_json"], [1, 2, 1, "", "to_python"]], "flask.json.tag.TagUUID": [[1, 2, 1, "", "check"], [1, 3, 1, "", "key"], [1, 2, 1, "", "to_json"], [1, 2, 1, "", "to_python"]], "flask.json.tag.TaggedJSONSerializer": [[1, 3, 1, "id7", "default_tags"], [1, 2, 1, "id8", "dumps"], [1, 2, 1, "id9", "loads"], [1, 3, 1, "", "order"], [1, 2, 1, "id10", "register"], [1, 2, 1, "id11", "tag"], [1, 3, 1, "", "tags"], [1, 2, 1, "id12", "untag"]], "flask.logging": [[0, 5, 1, "", "create_logger"], [0, 6, 1, "", "default_handler"], [0, 5, 1, "", "has_level_handler"]], "flask.sessions": [[0, 1, 1, "", "NullSession"], [0, 1, 1, "", "SecureCookieSession"], [0, 1, 1, "", "SecureCookieSessionInterface"], [0, 1, 1, "", "SessionInterface"], [0, 1, 1, "", "SessionMixin"]], "flask.sessions.NullSession": [[0, 2, 1, "", "clear"], [0, 2, 1, "", "pop"], [0, 2, 1, "", "popitem"], [0, 2, 1, "", "setdefault"], [0, 2, 1, "", "update"]], "flask.sessions.SecureCookieSession": [[0, 3, 1, "", "accessed"], [0, 2, 1, "", "get"], [0, 3, 1, "", "modified"], [0, 2, 1, "", "setdefault"]], "flask.sessions.SecureCookieSessionInterface": [[0, 2, 1, "", "digest_method"], [0, 2, 1, "", "get_signing_serializer"], [0, 3, 1, "", "key_derivation"], [0, 2, 1, "", "open_session"], [0, 3, 1, "", "salt"], [0, 2, 1, "", "save_session"], [0, 3, 1, "", "serializer"], [0, 3, 1, "", "session_class"]], "flask.sessions.SessionInterface": [[0, 2, 1, "", "get_cookie_domain"], [0, 2, 1, "", "get_cookie_httponly"], [0, 2, 1, "", "get_cookie_name"], [0, 2, 1, "", "get_cookie_partitioned"], [0, 2, 1, "", "get_cookie_path"], [0, 2, 1, "", "get_cookie_samesite"], [0, 2, 1, "", "get_cookie_secure"], [0, 2, 1, "", "get_expiration_time"], [0, 2, 1, "", "is_null_session"], [0, 2, 1, "", "make_null_session"], [0, 3, 1, "", "null_session_class"], [0, 2, 1, "", "open_session"], [0, 3, 1, "", "pickle_based"], [0, 2, 1, "", "save_session"], [0, 2, 1, "", "should_set_cookie"]], "flask.sessions.SessionMixin": [[0, 3, 1, "", "accessed"], [0, 3, 1, "", "modified"], [0, 3, 1, "", "new"], [0, 7, 1, "", "permanent"]], "flask.templating": [[0, 1, 1, "", "DispatchingJinjaLoader"], [0, 1, 1, "", "Environment"], [0, 5, 1, "", "render_template"], [0, 5, 1, "", "render_template_string"], [0, 5, 1, "", "stream_template"], [0, 5, 1, "", "stream_template_string"]], "flask.templating.DispatchingJinjaLoader": [[0, 2, 1, "", "get_source"], [0, 2, 1, "", "list_templates"]], "flask.testing": [[0, 1, 1, "", "EnvironBuilder"], [0, 1, 1, "", "FlaskCliRunner"], [0, 1, 1, "", "FlaskClient"]], "flask.testing.EnvironBuilder": [[0, 2, 1, "", "json_dumps"]], "flask.testing.FlaskCliRunner": [[0, 2, 1, "", "invoke"]], "flask.testing.FlaskClient": [[0, 3, 1, "", "application"], [0, 2, 1, "", "open"], [0, 2, 1, "", "session_transaction"]], "flask.views": [[0, 1, 1, "", "MethodView"], [0, 1, 1, "", "View"]], "flask.views.MethodView": [[0, 2, 1, "", "dispatch_request"]], "flask.views.View": [[0, 2, 1, "", "as_view"], [0, 3, 1, "", "decorators"], [0, 2, 1, "", "dispatch_request"], [0, 3, 1, "", "init_every_request"], [0, 3, 1, "", "methods"], [0, 3, 1, "", "provide_automatic_options"]], "flask.wrappers": [[0, 1, 1, "", "Request"], [0, 1, 1, "", "Response"]], "flask.wrappers.Request": [[0, 7, 1, "", "blueprint"], [0, 7, 1, "", "blueprints"], [0, 7, 1, "", "endpoint"], [0, 3, 1, "", "json_module"], [0, 7, 1, "", "max_content_length"], [0, 7, 1, "", "max_form_memory_size"], [0, 7, 1, "", "max_form_parts"], [0, 2, 1, "", "on_json_loading_failed"], [0, 3, 1, "", "routing_exception"], [0, 3, 1, "", "url_rule"], [0, 3, 1, "", "view_args"]], "flask.wrappers.Response": [[0, 3, 1, "", "autocorrect_location_header"], [0, 3, 1, "", "default_mimetype"], [0, 3, 1, "", "json_module"], [0, 7, 1, "", "max_cookie_size"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "exception", "Python exception"], "5": ["py", "function", "Python function"], "6": ["py", "data", "Python data"], "7": ["py", "property", "Python property"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:attribute", "4": "py:exception", "5": "py:function", "6": "py:data", "7": "py:property"}, "terms": {"": [0, 1], "0": [0, 1], "1": [0, 1], "10": 0, "1000": 0, "11": [0, 1], "12": 0, "127": 0, "1_000": 0, "2": [0, 1], "2231": 0, "3": [0, 1], "302": 0, "307": 0, "308": 0, "31": 0, "4": 0, "400": 0, "404": 0, "4093": 0, "413": 0, "415": 0, "42": 0, "5": 0, "500": 0, "5000": 0, "500000": 0, "500_000": 0, "6": 0, "7": 0, "8": [0, 1], "822": 1, "9": 0, "A": [0, 1], "As": 0, "By": 0, "For": 0, "If": [0, 1], "In": [0, 1], "It": [0, 1], "No": 0, "Not": 0, "On": 0, "Or": 0, "The": [0, 1], "Then": 0, "There": 0, "To": [0, 1], "Will": [0, 1], "_": 0, "__": [0, 1], "__call__": 0, "__html__": 1, "__init__": 0, "__name__": 0, "__slots__": 1, "_anchor": 0, "_app": 1, "_authent": 0, "_cider": 0, "_extern": 0, "_method": 0, "_perman": 0, "_scheme": 0, "abil": 0, "abl": 0, "abort": [0, 3], "absolut": 0, "accept": 0, "access": [0, 3], "accord": 0, "across": 0, "act": 0, "action": 0, "activ": [0, 1], "actual": 0, "ad": [0, 1], "adapt": 0, "add": [0, 1], "add_default_command": 0, "add_etag": 0, "add_head": 0, "add_url_rul": 0, "add_version_opt": 0, "addit": [0, 1], "addition": 0, "adhoc": 0, "advanc": 0, "advantag": 0, "after": 0, "after_request": 0, "after_this_request": [0, 3], "against": 0, "aid": 0, "alia": 0, "all": [0, 1], "allow": [0, 1], "allow_dash": 0, "alreadi": [0, 1], "also": [0, 1], "altern": 0, "alwai": [0, 1], "an": [0, 1], "anchor": 0, "ancient": 1, "ani": [0, 1], "answer": 0, "anystr": 0, "anyth": [0, 1], "api": [0, 1], "app": [1, 3], "app_context": [0, 3], "app_import_path": [0, 3], "app_nam": 0, "appcontext": [0, 3], "appcontext_tearing_down": 0, "appear": 0, "append": [0, 1], "appgroup": [0, 3], "appli": [0, 1], "applic": [0, 1, 3], "application_root": 0, "appropri": 0, "ar": [0, 1], "arbitrari": 0, "arg": [0, 1], "argument": [0, 1], "around": [0, 1], "arrai": 1, "arriv": 0, "as_attach": 0, "as_tupl": 0, "as_view": [0, 3], "ascii": [0, 1], "asctim": 0, "asdict": 1, "ask": 0, "assert": 0, "assertionerror": 0, "assign": 0, "associ": 0, "assum": 0, "assumpt": 0, "async": 0, "async_to_sync": [0, 3], "attach": 0, "attachment_filenam": 0, "attempt": 0, "attr": 0, "attribut": [0, 1], "authent": 0, "auto": 0, "auto_reload": 0, "autocorrect_location_head": [0, 3], "automat": [0, 1], "avail": [0, 1], "avoid": 0, "await": 0, "b": [0, 1], "back": [0, 1], "backend": 0, "bad": 0, "badli": 0, "badrequest": 0, "base": [0, 1], "base_url": 0, "baseexcept": 0, "baseload": 0, "basic": 0, "becaus": 0, "becom": 0, "been": 0, "befor": [0, 1], "before_request": 0, "before_request_func": 0, "begin": 0, "behav": 0, "behavior": [0, 1], "being": 0, "belong": 0, "below": 0, "best": 0, "better": [0, 1], "bienvenido": 2, "bigger": 0, "binari": [0, 1], "binaryio": 0, "bind": [0, 1], "block": 0, "blueprint": 3, "bodi": 0, "bool": [0, 1], "boolean": 0, "both": [0, 1], "bottom": 0, "bound": 0, "broader": 0, "browser": [0, 1], "buffer": 0, "build": 0, "builderror": 0, "built": [0, 1], "byte": [0, 1], "bytesio": 0, "c": 0, "cabc": 0, "cach": [0, 1], "cache_timeout": 0, "calcul": 0, "call": [0, 1], "callabl": 0, "callback": 0, "callbackdict": 0, "can": [0, 1], "cannot": 0, "care": 0, "case": [0, 1], "catch": 0, "categori": 0, "category_filt": 0, "caus": 0, "central": 0, "cert": 0, "certain": 0, "certparamtyp": [0, 3], "cfg": 0, "chain": 0, "chang": [0, 1], "chapter": 0, "charact": 1, "check": [0, 1], "class": [0, 1], "class_arg": 0, "class_kwarg": 0, "classmethod": 0, "classvar": 0, "cleanup": 0, "clear": [0, 3], "cli": 3, "cli_group": 0, "click": 0, "client": [0, 1], "clirunn": 0, "close": 0, "closur": 0, "code": 0, "coerc": 0, "collect": 0, "com": 0, "combin": 0, "come": 0, "command": [0, 3], "common": 0, "commonli": 0, "compact": [0, 1], "compactli": 1, "compat": [0, 1], "complain": 0, "complex": 0, "concurr": 0, "condit": 0, "config": 3, "configattribut": [0, 3], "configur": [0, 1], "confus": 0, "conn": 0, "connect": 0, "consid": 0, "consist": 0, "constructor": 0, "contain": 0, "content": 3, "content_typ": 0, "context": [0, 1], "context_class": 0, "continu": 0, "control": [0, 1], "convert": [0, 1, 3], "cooki": 0, "cool": 0, "copi": [0, 3], "copy_current_request_context": [0, 3], "coroutin": 0, "correct": [0, 1], "correspond": 0, "could": 0, "counter": 0, "counterapi": 0, "craft": 0, "creat": [0, 1], "create_app": [0, 3], "create_jinja_environ": [0, 3], "create_logg": [0, 3], "create_url_adapt": [0, 3], "ctx": 3, "current": [0, 1], "current_app": [0, 1], "custom": [0, 1], "customcli": 0, "d": [0, 1], "dai": 0, "data": [0, 1, 3], "databas": 0, "dataclass": 1, "date": [0, 1], "datetim": [0, 1], "db": 0, "deal": 0, "debug": [0, 1], "debugfileskeyerror": [0, 3], "debugg": 0, "debughelp": 3, "decid": 0, "decim": 1, "decis": 0, "decor": [0, 3], "def": [0, 1], "default": [0, 1], "default_config": [0, 3], "default_handl": [0, 3], "default_mimetyp": [0, 3], "default_tag": [0, 1], "defaultjsonprovid": [0, 1], "defer": 0, "defin": [0, 1], "del": 2, "delai": 0, "delet": 0, "depend": 0, "deploi": 0, "deriv": 0, "descript": 0, "deseri": 1, "detail": 0, "detect": 0, "determin": 0, "dev": 0, "develop": 0, "di": 1, "dict": [0, 1], "dictionari": 0, "did": 0, "didn": 0, "differ": [0, 1], "digest_method": [0, 3], "dir_okai": 0, "direct_passthrough": 0, "directli": [0, 1], "directori": 0, "disabl": [0, 1], "disappear": 0, "discov": 0, "dispatch": 0, "dispatch_request": [0, 3], "dispatchingjinjaload": [0, 3], "displai": 0, "disposit": 0, "distinct": 0, "do": 0, "do_some_work": 0, "do_teardown_appcontext": [0, 3], "do_teardown_request": [0, 3], "doc": 0, "document": 0, "doe": [0, 1], "doesn": 0, "domain": 0, "don": [0, 1], "done": 0, "dotenv": 0, "doubl": 0, "down": 0, "download_fil": 0, "download_nam": 0, "downsid": 0, "drop": 0, "due": 0, "dump": [0, 1], "duplic": 0, "dure": [0, 1], "e": 0, "each": 0, "easier": [0, 1], "edgar": 0, "effect": 0, "effici": 0, "either": [0, 1], "els": 0, "elsewher": 0, "email": 0, "empti": [0, 1], "enabl": [0, 1], "encod": [0, 1], "encount": 0, "end": [0, 1], "endmacro": 0, "endpoint": [0, 3], "enforc": 0, "ensur": 0, "ensure_ascii": [0, 1], "ensure_sync": [0, 3], "env": 0, "environ": [0, 3], "environ_bas": 0, "environbuild": [0, 3], "equival": 0, "error": 0, "escap": 1, "especi": 0, "etag": 0, "etc": 0, "evalu": 0, "even": 0, "event": 0, "everi": 0, "everyth": 0, "exact": 0, "exactli": 0, "exampl": [0, 1], "exc": 0, "exc_info": 0, "exceed": 0, "except": 0, "execut": 0, "executescript": 0, "exist": [0, 1], "exit": 0, "expand": 0, "expect": 0, "experi": 0, "expir": 0, "explain_template_load": 0, "explain_template_loading_attempt": [0, 3], "explicitli": 0, "export": 0, "extend": [0, 1], "extens": 0, "extern": 0, "extra": [0, 1], "f": 0, "fail": 0, "failur": 0, "fall": 0, "fals": [0, 1], "few": 0, "field": 0, "file": [0, 1], "file_okai": 0, "file_wrapp": 0, "filenam": 0, "filename_or_fp": 0, "filesystem": 0, "filewrapp": 0, "fill": 0, "filter": 0, "final": 0, "finalize_request": [0, 3], "find": 0, "find_app_by_str": [0, 3], "find_best_app": [0, 3], "first": 1, "flag": 0, "flash": [0, 3], "flask_": 0, "flask_debug": 0, "flask_skip_dotenv": 0, "flaskclient": [0, 3], "flaskclirunn": [0, 3], "flaskenv": 0, "flaskgroup": [0, 3], "float": 0, "folder": 0, "follow": [0, 1], "follow_redirect": 0, "foo": 0, "forc": [0, 1], "form": 0, "format": [0, 1], "formdataroutingredirect": [0, 3], "forward": 0, "found": 0, "fp": 1, "from": [0, 1], "from_envvar": [0, 3], "from_error_handl": 0, "from_fil": [0, 3], "from_map": [0, 3], "from_object": [0, 3], "from_prefixed_env": [0, 3], "from_pyfil": [0, 3], "front": 1, "frontend": 0, "ft": 0, "fulfil": 0, "full": 0, "full_dispatch_request": [0, 3], "func": 0, "function": [0, 1], "further": 0, "futur": 0, "g": 0, "gener": 0, "generate_report": 0, "generator_or_funct": 0, "get": [0, 1, 3], "get_command": [0, 3], "get_convert": 0, "get_cookie_domain": [0, 3], "get_cookie_httponli": [0, 3], "get_cookie_nam": [0, 3], "get_cookie_partit": [0, 3], "get_cookie_path": [0, 3], "get_cookie_samesit": [0, 3], "get_cookie_secur": [0, 3], "get_debug_flag": [0, 3], "get_expiration_tim": [0, 3], "get_flashed_messag": [0, 3], "get_json": 0, "get_load_dotenv": [0, 3], "get_namespac": [0, 3], "get_send_file_max_ag": [0, 3], "get_signing_seri": [0, 3], "get_sourc": [0, 3], "get_template_attribut": [0, 3], "get_vers": [0, 3], "gevent": 0, "give": [0, 1], "given": [0, 1], "global": 3, "go": 0, "got_request_except": 0, "greenlet": 0, "group": [0, 3], "guarante": 0, "guess": 0, "guid": 0, "ha": 0, "handl": [0, 1], "handle_except": [0, 3], "handle_http_except": [0, 3], "handle_url_build_error": 0, "handle_user_except": [0, 3], "handler": 0, "happen": 0, "hard": 0, "hardcod": 0, "has_app_context": [0, 3], "has_level_handl": [0, 3], "has_request_context": [0, 3], "hash": 0, "have": [0, 1], "head": 0, "header": 0, "hello": 0, "help": 0, "helper": 3, "here": 0, "histori": 0, "hmac": 0, "home": 0, "hook": 0, "host": 0, "host_match": 0, "hostnam": 0, "hour": 0, "how": [0, 1], "howev": 0, "html": 0, "http": [0, 1], "httpexcept": 0, "httponli": 0, "httpstatu": 0, "i": [0, 1], "idea": 0, "ideal": 0, "identifi": 1, "ignor": 0, "imag": 0, "image_store_": 0, "image_store_base_url": 0, "image_store_config": 0, "image_store_path": 0, "image_store_typ": 0, "img": 0, "immedi": 0, "implement": [0, 1], "implicitli": 0, "import": [0, 1], "import_nam": 0, "improv": 0, "includ": 0, "incorrect": 0, "indent": [0, 1], "index": [0, 1], "indic": 0, "infer": 0, "infinit": 0, "info": 0, "info_nam": 0, "inform": 0, "inherit": 0, "init": 0, "init_db": 0, "init_every_request": [0, 3], "initi": 0, "inject": 0, "inlin": 0, "input": 0, "insecur": 0, "insert": [0, 1], "insid": 0, "inspect": 0, "instal": 0, "instanc": [0, 1], "instance_path": 0, "instance_relative_config": 0, "instanti": [0, 1], "instead": 0, "int": [0, 1], "integ": 0, "intend": 0, "interact": 0, "interest": 0, "interfac": 0, "intermedi": [0, 1], "intern": [0, 1], "internalservererror": 0, "interpret": 0, "invalid": 0, "invoc": 0, "invok": [0, 3], "io": [0, 1], "is_null_sess": [0, 3], "isinst": 1, "isn": 0, "isol": 0, "item": [0, 1], "iter": 0, "iteritem": 1, "its": [0, 1], "itsdanger": [0, 1], "itself": 0, "jinja": 0, "jinja2": 0, "jinja_opt": 0, "job": 0, "json": [0, 3], "json_dump": [0, 3], "json_modul": [0, 3], "json_provider_class": 1, "jsonifi": [0, 1], "jsonprovid": [0, 1], "jsontag": [0, 1], "just": 0, "k": [0, 1], "kei": [0, 1], "key_deriv": [0, 3], "keyerror": [0, 1], "keyword": [0, 1], "kick": 0, "kind": 0, "know": [0, 1], "knowledg": 0, "kwarg": [0, 1], "la": 2, "lack": 0, "last": 0, "last_modifi": 0, "later": 1, "latin": 0, "launch": 0, "lax": 0, "leak": 0, "least": [0, 1], "left": 0, "length": 0, "less": 0, "let": 1, "level": [0, 1], "levelnam": 0, "librari": 1, "lifetim": 0, "lifo": 0, "like": 0, "likewis": 0, "limit": 0, "line": 0, "link": 0, "linux": 0, "list": [0, 1], "list_command": [0, 3], "list_templ": [0, 3], "listen": 0, "liter": 0, "load": [0, 1], "load_app": [0, 3], "load_default": 0, "load_dotenv": [0, 3], "load_dotenv_default": [0, 3], "loader": 0, "local": 0, "locat": 0, "locate_app": [0, 3], "lock": 0, "log": 3, "log_except": [0, 3], "logger": 0, "long": 0, "longer": 0, "look": 0, "lookup": 0, "lose": 0, "lossless": 1, "lost": 0, "lot": 0, "lowercas": 0, "m": 1, "macro": 0, "made": 0, "mai": [0, 1], "main": [0, 3], "mainli": 0, "maintain": 0, "make": [0, 1], "make_context": [0, 3], "make_default_options_respons": [0, 3], "make_null_sess": [0, 3], "make_respons": [0, 3], "make_shell_context": [0, 3], "malici": 0, "manag": 0, "manual": 0, "map": 0, "mapadapt": 0, "mark": 1, "markup": 1, "match": [0, 1], "match_request": [0, 3], "max_ag": 0, "max_content_length": [0, 3], "max_cookie_s": [0, 3], "max_form_memory_s": [0, 3], "max_form_part": [0, 3], "maximum": 0, "mean": 0, "meet": 0, "memori": 0, "messag": 0, "method": [0, 1, 3], "methodnotallow": 0, "methodview": [0, 3], "middlewar": 0, "might": 0, "mime": 0, "mimetyp": [0, 1], "miss": 0, "mixin": 0, "mode": [0, 1], "model": 0, "modif": 0, "modifi": [0, 3], "modul": 3, "module_nam": 0, "moduletyp": 0, "moment": 0, "more": [0, 1], "most": 0, "mostli": 0, "move": 0, "mro": 0, "much": 0, "multipart": 0, "multipl": [0, 1], "must": [0, 1], "mutabl": 0, "mutablemap": 0, "mymiddlewar": 0, "mysessioninterfac": 0, "name": [0, 3], "namespac": 0, "nearest": 0, "necessari": [0, 1], "necessarili": 0, "need": [0, 1], "nest": 0, "never": 0, "new": [0, 1, 3], "newlin": 1, "next": 0, "nicer": 0, "noappexcept": [0, 3], "non": [0, 1], "none": [0, 1], "noreturn": 0, "normal": 0, "not_found": 0, "note": 0, "notfound": 0, "noth": 0, "notset": 0, "now": 0, "null": 0, "null_session_class": [0, 3], "nullsess": [0, 3], "number": 0, "o": [0, 1], "obj": [0, 1], "object": [0, 1], "occur": 0, "od": 1, "off": 0, "offer": 0, "often": 0, "old": 0, "on_json_loading_fail": [0, 3], "onc": 0, "one": 0, "ones": 0, "onli": [0, 1], "onward": 0, "op": 0, "open": [0, 1, 3], "open_instance_resourc": [0, 3], "open_resourc": [0, 3], "open_sess": [0, 3], "oper": 1, "option": 0, "order": [0, 1], "ordereddict": 1, "origin": [0, 1], "original_except": 0, "other": [0, 1], "otherwis": [0, 1], "out": [0, 1], "outlin": 0, "output": 1, "outsid": 0, "over": 0, "overrid": [0, 1], "overridden": 0, "overwrit": [0, 1], "own": 0, "packag": [2, 3], "page": 0, "pain": 0, "pair": [0, 1], "parachut": 0, "param": 0, "paramet": 1, "paramtyp": 0, "parent": 0, "pars": 0, "parse_arg": [0, 3], "parser": 0, "part": 0, "partit": 0, "pass": [0, 1], "passdict": [0, 1], "passlist": [0, 1], "path": 0, "path_or_fil": 0, "path_typ": 0, "pathlik": 0, "pattern": 0, "payload": 0, "per": 0, "perform": [0, 1], "perman": [0, 3], "permanent_session_lifetim": 0, "pick": 0, "pickl": 0, "pickle_bas": [0, 3], "place": [0, 1], "plai": 0, "plain": 0, "plu": 0, "point": 0, "pointer": 0, "pop": [0, 3], "popitem": [0, 3], "popul": 0, "populate_request": 0, "port": 0, "posit": [0, 1], "possibl": 0, "post": 0, "postprocess": 0, "pre": 0, "preced": 0, "prefer": 0, "preferred_url_schem": 0, "prefix": 0, "prepare_import": [0, 3], "prepend": 0, "preprocess_request": [0, 3], "present": 0, "preserv": 0, "preset": 0, "prevent": 0, "previous": 0, "probabl": 0, "process": [0, 1], "process_respons": [0, 3], "processor": 0, "prod": 0, "product": 0, "prompt": 0, "propag": 0, "propagate_except": 0, "proper": 0, "properli": 0, "properti": 0, "provid": [0, 3], "provide_automatic_opt": [0, 3], "proxi": [0, 1], "proyecto": 2, "public": 0, "pull": 0, "push": [0, 3], "py": 0, "python": [0, 1], "queri": 0, "quickli": 0, "quit": 0, "r": 0, "rais": [0, 1], "raise_if_not_found": 0, "rang": 0, "rather": [0, 1], "rb": 0, "re": 0, "read": [0, 1], "readabl": 0, "reader": 0, "real": 0, "reason": 0, "receiv": 0, "recommend": 0, "reconstruct": 0, "redirect": [0, 3], "refer": 0, "referenc": 0, "reflect": 0, "regard": 0, "regist": [0, 1], "registr": 0, "registri": 0, "regular": 0, "rel": 0, "relat": 0, "reliabl": 0, "reload": 0, "remain": 0, "rememb": 0, "remote_addr": 0, "remov": [0, 1], "render": 0, "render_templ": [0, 3], "render_template_str": [0, 3], "replac": [0, 1], "repo": 0, "report": 0, "repres": [0, 1], "represent": 1, "reqcontext": 0, "request": [0, 1, 3], "request_class": [0, 3], "request_context": [0, 3], "request_tearing_down": 0, "requestcontext": [0, 3], "requestentitytoolarg": 0, "requir": [0, 1], "rerais": 0, "resolv": 0, "resolve_path": 0, "resourc": 0, "respons": [0, 1, 3], "response_class": [0, 3], "responsereturnvalu": 0, "rest": 0, "restor": 0, "restrict": 0, "result": [0, 1], "retain": 0, "return": [0, 1], "revers": 0, "rfc": [0, 1], "right": 0, "risk": 1, "role": 0, "root": 0, "root_path": 0, "rout": 0, "routecal": 0, "routing_except": [0, 3], "routingexcept": 0, "rt": 0, "rule": 0, "run": [0, 3], "run_simpl": 0, "runner": 0, "rv": 0, "safe": 0, "safe_join": 0, "salt": [0, 3], "same": [0, 1], "samesit": 0, "save": 0, "save_sess": [0, 3], "scaffold": 0, "schema": 0, "scheme": 0, "screen": 0, "script": 0, "scriptinfo": [0, 3], "search": 0, "second": 0, "secret": 0, "secret_kei": 0, "secret_key_fallback": 0, "secur": [0, 1], "securecooki": 0, "securecookiesess": [0, 3], "securecookiesessioninterfac": [0, 1, 3], "see": [0, 1], "seek": 0, "self": [0, 1], "send": 0, "send_fil": [0, 3], "send_file_max_age_default": 0, "send_from_directori": [0, 3], "send_static_fil": [0, 3], "sendfil": 0, "sens": 0, "sent": 0, "separ": 0, "separatedpathtyp": [0, 3], "sequenc": [0, 1], "serial": [0, 1, 3], "serv": 0, "server": 0, "server_nam": 0, "session": [1, 3], "session_class": [0, 3], "session_cookie_domain": 0, "session_cookie_httponli": 0, "session_cookie_nam": 0, "session_cookie_partit": 0, "session_cookie_path": 0, "session_cookie_samesit": 0, "session_cookie_secur": 0, "session_interfac": [0, 1, 3], "session_refresh_each_request": 0, "session_transact": [0, 3], "sessioninterfac": [0, 3], "sessionmixin": [0, 3], "set": [0, 1], "set_debug_flag": 0, "setdefault": [0, 3], "setup": 0, "sha1": 0, "shallow": 0, "shell": 0, "shortcut": 0, "should": [0, 1], "should_set_cooki": [0, 3], "show": 0, "show_server_bann": [0, 3], "sign": 0, "signal": 3, "signatur": 0, "silenc": 0, "silent": 0, "similar": 0, "similarli": 0, "sinc": [0, 1], "singl": [0, 1], "situat": [0, 1], "size": [0, 1], "skip": 0, "slash": 0, "so": [0, 1], "software_d": 0, "some": [0, 1], "someth": 0, "sometim": 0, "somewher": 0, "sort": [0, 1], "sort_kei": [0, 1], "sourc": 0, "space": 1, "spawn": 0, "special": [0, 1], "specif": [0, 1], "specifi": 0, "split": 0, "sql": 0, "sqlalchemi": 0, "src": 0, "sslcontext": 0, "standard": [0, 1], "start": 0, "start_respons": 0, "startrespons": 0, "startup": 0, "state": 0, "statement": 0, "static": [0, 1], "static_fold": 0, "static_host": 0, "static_url_path": 0, "statu": 0, "stderr": 0, "step": 1, "still": 0, "stop": 0, "store": [0, 1], "str": [0, 1], "stream": 0, "stream_templ": [0, 3], "stream_template_str": [0, 3], "stream_with_context": [0, 3], "streamed_respons": 0, "streamhandl": 0, "strict": 0, "string": [0, 1], "structur": 1, "subclass": [0, 1], "subcommand": 0, "subdomain": 0, "subdomain_match": 0, "submodul": 3, "subpackag": 3, "subset": 0, "successfulli": 0, "suffix": 1, "super": 0, "support": [0, 1], "suppress": 0, "sure": 0, "sync": 0, "synchron": 0, "syntax": 0, "system": [0, 1], "t": [0, 1], "tag": [0, 3], "tag_class": 1, "tagbyt": [0, 1], "tagdatetim": [0, 1], "tagdict": [0, 1], "taggedjsonseri": [0, 1], "tagmarkup": [0, 1], "tagordereddict": 1, "tagtupl": [0, 1], "taguuid": [0, 1], "take": 0, "teardown": 0, "teardown_appcontext": 0, "teardown_request": 0, "tell": 0, "templat": 3, "template_fold": 0, "template_nam": 0, "template_name_or_list": 0, "templatenotfound": 0, "templates_auto_reload": 0, "temporari": 0, "termin": 0, "test": 3, "test_cli_runn": [0, 3], "test_cli_runner_class": 0, "test_client": [0, 3], "test_client_class": 0, "test_request_context": [0, 3], "testrespons": 0, "text": [0, 1], "textiobas": 0, "than": [0, 1], "thank": 0, "thei": [0, 1], "them": 0, "thi": [0, 1], "thing": 0, "think": 0, "those": 0, "though": 0, "thread": 0, "through": 0, "time": 0, "timedelta": 0, "to_json": [0, 1], "to_python": [0, 1], "toml": 0, "tomllib": 0, "top": [0, 1], "toplevel": 0, "traceback": 0, "tracebacktyp": 0, "track": 0, "transact": 0, "trap_bad_request_error": 0, "trap_http_except": 0, "treat": 1, "tri": 0, "tricki": 0, "trigger": 0, "trim_namespac": 0, "true": [0, 1], "trust": 0, "trusted_host": 0, "truth": 0, "try": 0, "tupl": [0, 1], "two": 0, "type": [1, 3], "typecheck": 0, "typeerror": [0, 1], "typic": 0, "u": 1, "ucr": 0, "unavail": 0, "unbind": 0, "unchang": 0, "under": 0, "underli": [0, 1], "underscor": 0, "understand": 0, "unexpect": 0, "unexpectedunicodeerror": [0, 3], "unhandl": 0, "unicod": 0, "unicodeerror": 0, "unit": 0, "unknown": 0, "unless": 0, "unlik": 0, "unreli": 0, "untag": [0, 1], "until": 0, "up": 0, "updat": [0, 3], "update_template_context": [0, 3], "upload": 0, "upload_fold": 0, "upper": 0, "uppercas": 0, "uptod": 0, "upward": 0, "url": 0, "url_default": 0, "url_for": [0, 3], "url_map": 0, "url_prefix": 0, "url_rul": [0, 3], "url_schem": 0, "url_value_preprocessor": 0, "urlsafetimedseri": 0, "us": [0, 1], "usag": 0, "usageerror": 0, "use_cooki": 0, "use_debugg": 0, "use_evalex": 0, "use_reload": 0, "use_x_sendfil": 0, "user": 0, "usernam": 0, "usual": 0, "utf": [0, 1], "util": 0, "uuid": 1, "v": [0, 1], "valid": [0, 1], "valid_method": 0, "valu": [0, 1], "valueerror": 0, "var": 0, "variabl": 0, "variable_nam": 0, "variou": 0, "veri": 0, "version": [0, 1], "via": 0, "view": [1, 3], "view_arg": [0, 3], "view_func": 0, "view_funct": 0, "view_rv": 0, "visibl": 0, "vodka": 0, "wa": [0, 1], "wai": 0, "wait": 0, "want": 0, "warn": 0, "wasn": 0, "we": [0, 1], "weakref": 1, "web": 0, "webserv": 0, "websit": 0, "well": 0, "were": 0, "werkzeug": 0, "what": 0, "when": [0, 1], "whenev": 0, "where": 0, "whether": 0, "which": 0, "while": 0, "whose": 1, "why": 0, "wide": 0, "window": 0, "with_appcontext": [0, 3], "with_categori": 0, "within": 0, "without": [0, 1], "won": 0, "work": 0, "worker": 0, "world": 0, "would": 0, "wrap": 0, "wrapper": 3, "writabl": 0, "write": [0, 1], "written": 0, "wsgi": 0, "wsgi_app": [0, 3], "wsgi_errors_stream": 0, "wsgienviron": 0, "x": 0, "yet": 0, "yield": 0, "you": 0, "your": 0, "yourappl": 0, "yourapplication_set": 0, "yourconfig": 0, "yourself": 0}, "titles": ["flask package", "flask.json package", "Flask - Documentaci\u00f3n Personalizada", "flask"], "titleterms": {"about": 0, "app": 0, "blueprint": 0, "cli": 0, "config": 0, "contenido": 2, "content": [0, 1], "ctx": 0, "debughelp": 0, "documentaci\u00f3n": 2, "first": 0, "flask": [0, 1, 2, 3], "global": 0, "helper": 0, "json": 1, "keep": 0, "log": 0, "mind": 0, "modul": [0, 1], "packag": [0, 1], "paramet": 0, "personalizada": 2, "provid": 1, "session": 0, "signal": 0, "submodul": [0, 1], "subpackag": 0, "tag": 1, "templat": 0, "test": 0, "type": 0, "view": 0, "wrapper": 0}}) \ No newline at end of file diff --git a/docs/changes.rst b/docs/changes.rst deleted file mode 100644 index 955deaf2..00000000 --- a/docs/changes.rst +++ /dev/null @@ -1,4 +0,0 @@ -Changes -======= - -.. include:: ../CHANGES.rst diff --git a/docs/cli.rst b/docs/cli.rst deleted file mode 100644 index a72e6d51..00000000 --- a/docs/cli.rst +++ /dev/null @@ -1,556 +0,0 @@ -.. currentmodule:: flask - -Command Line Interface -====================== - -Installing Flask installs the ``flask`` script, a `Click`_ command line -interface, in your virtualenv. Executed from the terminal, this script gives -access to built-in, extension, and application-defined commands. The ``--help`` -option will give more information about any commands and options. - -.. _Click: https://click.palletsprojects.com/ - - -Application Discovery ---------------------- - -The ``flask`` command is installed by Flask, not your application; it must be -told where to find your application in order to use it. The ``--app`` -option is used to specify how to load the application. - -While ``--app`` supports a variety of options for specifying your -application, most use cases should be simple. Here are the typical values: - -(nothing) - The name "app" or "wsgi" is imported (as a ".py" file, or package), - automatically detecting an app (``app`` or ``application``) or - factory (``create_app`` or ``make_app``). - -``--app hello`` - The given name is imported, automatically detecting an app (``app`` - or ``application``) or factory (``create_app`` or ``make_app``). - ----- - -``--app`` has three parts: an optional path that sets the current working -directory, a Python file or dotted import path, and an optional variable -name of the instance or factory. If the name is a factory, it can optionally -be followed by arguments in parentheses. The following values demonstrate these -parts: - -``--app src/hello`` - Sets the current working directory to ``src`` then imports ``hello``. - -``--app hello.web`` - Imports the path ``hello.web``. - -``--app hello:app2`` - Uses the ``app2`` Flask instance in ``hello``. - -``--app 'hello:create_app("dev")'`` - The ``create_app`` factory in ``hello`` is called with the string ``'dev'`` - as the argument. - -If ``--app`` is not set, the command will try to import "app" or -"wsgi" (as a ".py" file, or package) and try to detect an application -instance or factory. - -Within the given import, the command looks for an application instance named -``app`` or ``application``, then any application instance. If no instance is -found, the command looks for a factory function named ``create_app`` or -``make_app`` that returns an instance. - -If parentheses follow the factory name, their contents are parsed as -Python literals and passed as arguments and keyword arguments to the -function. This means that strings must still be in quotes. - - -Run the Development Server --------------------------- - -The :func:`run ` command will start the development server. It -replaces the :meth:`Flask.run` method in most cases. :: - - $ flask --app hello run - * Serving Flask app "hello" - * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) - -.. warning:: Do not use this command to run your application in production. - Only use the development server during development. The development server - is provided for convenience, but is not designed to be particularly secure, - stable, or efficient. See :doc:`/deploying/index` for how to run in production. - -If another program is already using port 5000, you'll see -``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the -server tries to start. See :ref:`address-already-in-use` for how to -handle that. - - -Debug Mode -~~~~~~~~~~ - -In debug mode, the ``flask run`` command will enable the interactive debugger and the -reloader by default, and make errors easier to see and debug. To enable debug mode, use -the ``--debug`` option. - -.. code-block:: console - - $ flask --app hello run --debug - * Serving Flask app "hello" - * Debug mode: on - * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) - * Restarting with inotify reloader - * Debugger is active! - * Debugger PIN: 223-456-919 - -The ``--debug`` option can also be passed to the top level ``flask`` command to enable -debug mode for any command. The following two ``run`` calls are equivalent. - -.. code-block:: console - - $ flask --app hello --debug run - $ flask --app hello run --debug - - -Watch and Ignore Files with the Reloader -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using debug mode, the reloader will trigger whenever your Python code or imported -modules change. The reloader can watch additional files with the ``--extra-files`` -option. Multiple paths are separated with ``:``, or ``;`` on Windows. - -.. code-block:: text - - $ flask run --extra-files file1:dirA/file2:dirB/ - * Running on http://127.0.0.1:8000/ - * Detected change in '/path/to/file1', reloading - -The reloader can also ignore files using :mod:`fnmatch` patterns with the -``--exclude-patterns`` option. Multiple patterns are separated with ``:``, or ``;`` on -Windows. - - -Open a Shell ------------- - -To explore the data in your application, you can start an interactive Python -shell with the :func:`shell ` command. An application -context will be active, and the app instance will be imported. :: - - $ flask shell - Python 3.10.0 (default, Oct 27 2021, 06:59:51) [GCC 11.1.0] on linux - App: example [production] - Instance: /home/david/Projects/pallets/flask/instance - >>> - -Use :meth:`~Flask.shell_context_processor` to add other automatic imports. - - -.. _dotenv: - -Environment Variables From dotenv ---------------------------------- - -The ``flask`` command supports setting any option for any command with -environment variables. The variables are named like ``FLASK_OPTION`` or -``FLASK_COMMAND_OPTION``, for example ``FLASK_APP`` or -``FLASK_RUN_PORT``. - -Rather than passing options every time you run a command, or environment -variables every time you open a new terminal, you can use Flask's dotenv -support to set environment variables automatically. - -If `python-dotenv`_ is installed, running the ``flask`` command will set -environment variables defined in the files ``.env`` and ``.flaskenv``. -You can also specify an extra file to load with the ``--env-file`` -option. Dotenv files can be used to avoid having to set ``--app`` or -``FLASK_APP`` manually, and to set configuration using environment -variables similar to how some deployment services work. - -Variables set on the command line are used over those set in :file:`.env`, -which are used over those set in :file:`.flaskenv`. :file:`.flaskenv` should be -used for public variables, such as ``FLASK_APP``, while :file:`.env` should not -be committed to your repository so that it can set private variables. - -Directories are scanned upwards from the directory you call ``flask`` -from to locate the files. - -The files are only loaded by the ``flask`` command or calling -:meth:`~Flask.run`. If you would like to load these files when running in -production, you should call :func:`~cli.load_dotenv` manually. - -.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme - - -Setting Command Options -~~~~~~~~~~~~~~~~~~~~~~~ - -Click is configured to load default values for command options from -environment variables. The variables use the pattern -``FLASK_COMMAND_OPTION``. For example, to set the port for the run -command, instead of ``flask run --port 8000``: - -.. tabs:: - - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_RUN_PORT=8000 - $ flask run - * Running on http://127.0.0.1:8000/ - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_RUN_PORT 8000 - $ flask run - * Running on http://127.0.0.1:8000/ - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_RUN_PORT=8000 - > flask run - * Running on http://127.0.0.1:8000/ - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_RUN_PORT = 8000 - > flask run - * Running on http://127.0.0.1:8000/ - -These can be added to the ``.flaskenv`` file just like ``FLASK_APP`` to -control default command options. - - -Disable dotenv -~~~~~~~~~~~~~~ - -The ``flask`` command will show a message if it detects dotenv files but -python-dotenv is not installed. - -.. code-block:: bash - - $ flask run - * Tip: There are .env files present. Do "pip install python-dotenv" to use them. - -You can tell Flask not to load dotenv files even when python-dotenv is -installed by setting the ``FLASK_SKIP_DOTENV`` environment variable. -This can be useful if you want to load them manually, or if you're using -a project runner that loads them already. Keep in mind that the -environment variables must be set before the app loads or it won't -configure as expected. - -.. tabs:: - - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_SKIP_DOTENV=1 - $ flask run - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_SKIP_DOTENV 1 - $ flask run - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_SKIP_DOTENV=1 - > flask run - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_SKIP_DOTENV = 1 - > flask run - - -Environment Variables From virtualenv -------------------------------------- - -If you do not want to install dotenv support, you can still set environment -variables by adding them to the end of the virtualenv's :file:`activate` -script. Activating the virtualenv will set the variables. - -.. tabs:: - - .. group-tab:: Bash - - Unix Bash, :file:`.venv/bin/activate`:: - - $ export FLASK_APP=hello - - .. group-tab:: Fish - - Fish, :file:`.venv/bin/activate.fish`:: - - $ set -x FLASK_APP hello - - .. group-tab:: CMD - - Windows CMD, :file:`.venv\\Scripts\\activate.bat`:: - - > set FLASK_APP=hello - - .. group-tab:: Powershell - - Windows Powershell, :file:`.venv\\Scripts\\activate.ps1`:: - - > $env:FLASK_APP = "hello" - -It is preferred to use dotenv support over this, since :file:`.flaskenv` can be -committed to the repository so that it works automatically wherever the project -is checked out. - - -Custom Commands ---------------- - -The ``flask`` command is implemented using `Click`_. See that project's -documentation for full information about writing commands. - -This example adds the command ``create-user`` that takes the argument -``name``. :: - - import click - from flask import Flask - - app = Flask(__name__) - - @app.cli.command("create-user") - @click.argument("name") - def create_user(name): - ... - -:: - - $ flask create-user admin - -This example adds the same command, but as ``user create``, a command in a -group. This is useful if you want to organize multiple related commands. :: - - import click - from flask import Flask - from flask.cli import AppGroup - - app = Flask(__name__) - user_cli = AppGroup('user') - - @user_cli.command('create') - @click.argument('name') - def create_user(name): - ... - - app.cli.add_command(user_cli) - -:: - - $ flask user create demo - -See :ref:`testing-cli` for an overview of how to test your custom -commands. - - -Registering Commands with Blueprints -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If your application uses blueprints, you can optionally register CLI -commands directly onto them. When your blueprint is registered onto your -application, the associated commands will be available to the ``flask`` -command. By default, those commands will be nested in a group matching -the name of the blueprint. - -.. code-block:: python - - from flask import Blueprint - - bp = Blueprint('students', __name__) - - @bp.cli.command('create') - @click.argument('name') - def create(name): - ... - - app.register_blueprint(bp) - -.. code-block:: text - - $ flask students create alice - -You can alter the group name by specifying the ``cli_group`` parameter -when creating the :class:`Blueprint` object, or later with -:meth:`app.register_blueprint(bp, cli_group='...') `. -The following are equivalent: - -.. code-block:: python - - bp = Blueprint('students', __name__, cli_group='other') - # or - app.register_blueprint(bp, cli_group='other') - -.. code-block:: text - - $ flask other create alice - -Specifying ``cli_group=None`` will remove the nesting and merge the -commands directly to the application's level: - -.. code-block:: python - - bp = Blueprint('students', __name__, cli_group=None) - # or - app.register_blueprint(bp, cli_group=None) - -.. code-block:: text - - $ flask create alice - - -Application Context -~~~~~~~~~~~~~~~~~~~ - -Commands added using the Flask app's :attr:`~Flask.cli` or -:class:`~flask.cli.FlaskGroup` :meth:`~cli.AppGroup.command` decorator -will be executed with an application context pushed, so your custom -commands and parameters have access to the app and its configuration. The -:func:`~cli.with_appcontext` decorator can be used to get the same -behavior, but is not needed in most cases. - -.. code-block:: python - - import click - from flask.cli import with_appcontext - - @click.command() - @with_appcontext - def do_work(): - ... - - app.cli.add_command(do_work) - - -Plugins -------- - -Flask will automatically load commands specified in the ``flask.commands`` -`entry point`_. This is useful for extensions that want to add commands when -they are installed. Entry points are specified in :file:`pyproject.toml`: - -.. code-block:: toml - - [project.entry-points."flask.commands"] - my-command = "my_extension.commands:cli" - -.. _entry point: https://packaging.python.org/tutorials/packaging-projects/#entry-points - -Inside :file:`my_extension/commands.py` you can then export a Click -object:: - - import click - - @click.command() - def cli(): - ... - -Once that package is installed in the same virtualenv as your Flask project, -you can run ``flask my-command`` to invoke the command. - - -.. _custom-scripts: - -Custom Scripts --------------- - -When you are using the app factory pattern, it may be more convenient to define -your own Click script. Instead of using ``--app`` and letting Flask load -your application, you can create your own Click object and export it as a -`console script`_ entry point. - -Create an instance of :class:`~cli.FlaskGroup` and pass it the factory:: - - import click - from flask import Flask - from flask.cli import FlaskGroup - - def create_app(): - app = Flask('wiki') - # other setup - return app - - @click.group(cls=FlaskGroup, create_app=create_app) - def cli(): - """Management script for the Wiki application.""" - -Define the entry point in :file:`pyproject.toml`: - -.. code-block:: toml - - [project.scripts] - wiki = "wiki:cli" - -Install the application in the virtualenv in editable mode and the custom -script is available. Note that you don't need to set ``--app``. :: - - $ pip install -e . - $ wiki run - -.. admonition:: Errors in Custom Scripts - - When using a custom script, if you introduce an error in your - module-level code, the reloader will fail because it can no longer - load the entry point. - - The ``flask`` command, being separate from your code, does not have - this issue and is recommended in most cases. - -.. _console script: https://packaging.python.org/tutorials/packaging-projects/#console-scripts - - -PyCharm Integration -------------------- - -PyCharm Professional provides a special Flask run configuration to run the development -server. For the Community Edition, and for other commands besides ``run``, you need to -create a custom run configuration. These instructions should be similar for any other -IDE you use. - -In PyCharm, with your project open, click on *Run* from the menu bar and go to *Edit -Configurations*. You'll see a screen similar to this: - -.. image:: _static/pycharm-run-config.png - :align: center - :class: screenshot - :alt: Screenshot of PyCharm run configuration. - -Once you create a configuration for the ``flask run``, you can copy and change it to -call any other command. - -Click the *+ (Add New Configuration)* button and select *Python*. Give the configuration -a name such as "flask run". - -Click the *Script path* dropdown and change it to *Module name*, then input ``flask``. - -The *Parameters* field is set to the CLI command to execute along with any arguments. -This example uses ``--app hello run --debug``, which will run the development server in -debug mode. ``--app hello`` should be the import or file with your Flask app. - -If you installed your project as a package in your virtualenv, you may uncheck the -*PYTHONPATH* options. This will more accurately match how you deploy later. - -Click *OK* to save and close the configuration. Select the configuration in the main -PyCharm window and click the play button next to it to run the server. - -Now that you have a configuration for ``flask run``, you can copy that configuration and -change the *Parameters* argument to run a different CLI command. diff --git a/docs/config.rst b/docs/config.rst deleted file mode 100644 index 5695bbd0..00000000 --- a/docs/config.rst +++ /dev/null @@ -1,836 +0,0 @@ -Configuration Handling -====================== - -Applications need some kind of configuration. There are different settings -you might want to change depending on the application environment like -toggling the debug mode, setting the secret key, and other such -environment-specific things. - -The way Flask is designed usually requires the configuration to be -available when the application starts up. You can hard code the -configuration in the code, which for many small applications is not -actually that bad, but there are better ways. - -Independent of how you load your config, there is a config object -available which holds the loaded configuration values: -The :attr:`~flask.Flask.config` attribute of the :class:`~flask.Flask` -object. This is the place where Flask itself puts certain configuration -values and also where extensions can put their configuration values. But -this is also where you can have your own configuration. - - -Configuration Basics --------------------- - -The :attr:`~flask.Flask.config` is actually a subclass of a dictionary and -can be modified just like any dictionary:: - - app = Flask(__name__) - app.config['TESTING'] = True - -Certain configuration values are also forwarded to the -:attr:`~flask.Flask` object so you can read and write them from there:: - - app.testing = True - -To update multiple keys at once you can use the :meth:`dict.update` -method:: - - app.config.update( - TESTING=True, - SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' - ) - - -Debug Mode ----------- - -The :data:`DEBUG` config value is special because it may behave inconsistently if -changed after the app has begun setting up. In order to set debug mode reliably, use the -``--debug`` option on the ``flask`` or ``flask run`` command. ``flask run`` will use the -interactive debugger and reloader by default in debug mode. - -.. code-block:: text - - $ flask --app hello run --debug - -Using the option is recommended. While it is possible to set :data:`DEBUG` in your -config or code, this is strongly discouraged. It can't be read early by the -``flask run`` command, and some systems or extensions may have already configured -themselves based on a previous value. - - -Builtin Configuration Values ----------------------------- - -The following configuration values are used internally by Flask: - -.. py:data:: DEBUG - - Whether debug mode is enabled. When using ``flask run`` to start the development - server, an interactive debugger will be shown for unhandled exceptions, and the - server will be reloaded when code changes. The :attr:`~flask.Flask.debug` attribute - maps to this config key. This is set with the ``FLASK_DEBUG`` environment variable. - It may not behave as expected if set in code. - - **Do not enable debug mode when deploying in production.** - - Default: ``False`` - -.. py:data:: TESTING - - Enable testing mode. Exceptions are propagated rather than handled by the - the app's error handlers. Extensions may also change their behavior to - facilitate easier testing. You should enable this in your own tests. - - Default: ``False`` - -.. py:data:: PROPAGATE_EXCEPTIONS - - Exceptions are re-raised rather than being handled by the app's error - handlers. If not set, this is implicitly true if ``TESTING`` or ``DEBUG`` - is enabled. - - Default: ``None`` - -.. py:data:: TRAP_HTTP_EXCEPTIONS - - If there is no handler for an ``HTTPException``-type exception, re-raise it - to be handled by the interactive debugger instead of returning it as a - simple error response. - - Default: ``False`` - -.. py:data:: TRAP_BAD_REQUEST_ERRORS - - Trying to access a key that doesn't exist from request dicts like ``args`` - and ``form`` will return a 400 Bad Request error page. Enable this to treat - the error as an unhandled exception instead so that you get the interactive - debugger. This is a more specific version of ``TRAP_HTTP_EXCEPTIONS``. If - unset, it is enabled in debug mode. - - Default: ``None`` - -.. py:data:: SECRET_KEY - - A secret key that will be used for securely signing the session cookie - and can be used for any other security related needs by extensions or your - application. It should be a long random ``bytes`` or ``str``. For - example, copy the output of this to your config:: - - $ python -c 'import secrets; print(secrets.token_hex())' - '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' - - **Do not reveal the secret key when posting questions or committing code.** - - Default: ``None`` - -.. py:data:: SECRET_KEY_FALLBACKS - - A list of old secret keys that can still be used for unsigning, most recent - first. This allows a project to implement key rotation without invalidating - active sessions or other recently-signed secrets. - - Keys should be removed after an appropriate period of time, as checking each - additional key adds some overhead. - - Flask's built-in secure cookie session supports this. Extensions that use - :data:`SECRET_KEY` may not support this yet. - - Default: ``None`` - - .. versionadded:: 3.1 - -.. py:data:: SESSION_COOKIE_NAME - - The name of the session cookie. Can be changed in case you already have a - cookie with the same name. - - Default: ``'session'`` - -.. py:data:: SESSION_COOKIE_DOMAIN - - The value of the ``Domain`` parameter on the session cookie. If not set, browsers - will only send the cookie to the exact domain it was set from. Otherwise, they - will send it to any subdomain of the given value as well. - - Not setting this value is more restricted and secure than setting it. - - Default: ``None`` - - .. warning:: - If this is changed after the browser created a cookie is created with - one setting, it may result in another being created. Browsers may send - send both in an undefined order. In that case, you may want to change - :data:`SESSION_COOKIE_NAME` as well or otherwise invalidate old sessions. - - .. versionchanged:: 2.3 - Not set by default, does not fall back to ``SERVER_NAME``. - -.. py:data:: SESSION_COOKIE_PATH - - The path that the session cookie will be valid for. If not set, the cookie - will be valid underneath ``APPLICATION_ROOT`` or ``/`` if that is not set. - - Default: ``None`` - -.. py:data:: SESSION_COOKIE_HTTPONLY - - Browsers will not allow JavaScript access to cookies marked as "HTTP only" - for security. - - Default: ``True`` - -.. py:data:: SESSION_COOKIE_SECURE - - Browsers will only send cookies with requests over HTTPS if the cookie is - marked "secure". The application must be served over HTTPS for this to make - sense. - - Default: ``False`` - -.. py:data:: SESSION_COOKIE_PARTITIONED - - Browsers will send cookies based on the top-level document's domain, rather - than only the domain of the document setting the cookie. This prevents third - party cookies set in iframes from "leaking" between separate sites. - - Browsers are beginning to disallow non-partitioned third party cookies, so - you need to mark your cookies partitioned if you expect them to work in such - embedded situations. - - Enabling this implicitly enables :data:`SESSION_COOKIE_SECURE` as well, as - it is only valid when served over HTTPS. - - Default: ``False`` - - .. versionadded:: 3.1 - -.. py:data:: SESSION_COOKIE_SAMESITE - - Restrict how cookies are sent with requests from external sites. Can - be set to ``'Lax'`` (recommended) or ``'Strict'``. - See :ref:`security-cookie`. - - Default: ``None`` - - .. versionadded:: 1.0 - -.. py:data:: PERMANENT_SESSION_LIFETIME - - If ``session.permanent`` is true, the cookie's expiration will be set this - number of seconds in the future. Can either be a - :class:`datetime.timedelta` or an ``int``. - - Flask's default cookie implementation validates that the cryptographic - signature is not older than this value. - - Default: ``timedelta(days=31)`` (``2678400`` seconds) - -.. py:data:: SESSION_REFRESH_EACH_REQUEST - - Control whether the cookie is sent with every response when - ``session.permanent`` is true. Sending the cookie every time (the default) - can more reliably keep the session from expiring, but uses more bandwidth. - Non-permanent sessions are not affected. - - Default: ``True`` - -.. py:data:: USE_X_SENDFILE - - When serving files, set the ``X-Sendfile`` header instead of serving the - data with Flask. Some web servers, such as Apache, recognize this and serve - the data more efficiently. This only makes sense when using such a server. - - Default: ``False`` - -.. py:data:: SEND_FILE_MAX_AGE_DEFAULT - - When serving files, set the cache control max age to this number of - seconds. Can be a :class:`datetime.timedelta` or an ``int``. - Override this value on a per-file basis using - :meth:`~flask.Flask.get_send_file_max_age` on the application or - blueprint. - - If ``None``, ``send_file`` tells the browser to use conditional - requests will be used instead of a timed cache, which is usually - preferable. - - Default: ``None`` - -.. py:data:: TRUSTED_HOSTS - - Validate :attr:`.Request.host` and other attributes that use it against - these trusted values. Raise a :exc:`~werkzeug.exceptions.SecurityError` if - the host is invalid, which results in a 400 error. If it is ``None``, all - hosts are valid. Each value is either an exact match, or can start with - a dot ``.`` to match any subdomain. - - Validation is done during routing against this value. ``before_request`` and - ``after_request`` callbacks will still be called. - - Default: ``None`` - - .. versionadded:: 3.1 - -.. py:data:: SERVER_NAME - - Inform the application what host and port it is bound to. - - Must be set if ``subdomain_matching`` is enabled, to be able to extract the - subdomain from the request. - - Must be set for ``url_for`` to generate external URLs outside of a - request context. - - Default: ``None`` - - .. versionchanged:: 3.1 - Does not restrict requests to only this domain, for both - ``subdomain_matching`` and ``host_matching``. - - .. versionchanged:: 1.0 - Does not implicitly enable ``subdomain_matching``. - - .. versionchanged:: 2.3 - Does not affect ``SESSION_COOKIE_DOMAIN``. - -.. py:data:: APPLICATION_ROOT - - Inform the application what path it is mounted under by the application / - web server. This is used for generating URLs outside the context of a - request (inside a request, the dispatcher is responsible for setting - ``SCRIPT_NAME`` instead; see :doc:`/patterns/appdispatch` - for examples of dispatch configuration). - - Will be used for the session cookie path if ``SESSION_COOKIE_PATH`` is not - set. - - Default: ``'/'`` - -.. py:data:: PREFERRED_URL_SCHEME - - Use this scheme for generating external URLs when not in a request context. - - Default: ``'http'`` - -.. py:data:: MAX_CONTENT_LENGTH - - The maximum number of bytes that will be read during this request. If - this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge` - error is raised. If it is set to ``None``, no limit is enforced at the - Flask application level. However, if it is ``None`` and the request has no - ``Content-Length`` header and the WSGI server does not indicate that it - terminates the stream, then no data is read to avoid an infinite stream. - - Each request defaults to this config. It can be set on a specific - :attr:`.Request.max_content_length` to apply the limit to that specific - view. This should be set appropriately based on an application's or view's - specific needs. - - Default: ``None`` - - .. versionadded:: 0.6 - -.. py:data:: MAX_FORM_MEMORY_SIZE - - The maximum size in bytes any non-file form field may be in a - ``multipart/form-data`` body. If this limit is exceeded, a 413 - :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it is - set to ``None``, no limit is enforced at the Flask application level. - - Each request defaults to this config. It can be set on a specific - :attr:`.Request.max_form_memory_parts` to apply the limit to that specific - view. This should be set appropriately based on an application's or view's - specific needs. - - Default: ``500_000`` - - .. versionadded:: 3.1 - -.. py:data:: MAX_FORM_PARTS - - The maximum number of fields that may be present in a - ``multipart/form-data`` body. If this limit is exceeded, a 413 - :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it - is set to ``None``, no limit is enforced at the Flask application level. - - Each request defaults to this config. It can be set on a specific - :attr:`.Request.max_form_parts` to apply the limit to that specific view. - This should be set appropriately based on an application's or view's - specific needs. - - Default: ``1_000`` - - .. versionadded:: 3.1 - -.. py:data:: TEMPLATES_AUTO_RELOAD - - Reload templates when they are changed. If not set, it will be enabled in - debug mode. - - Default: ``None`` - -.. py:data:: EXPLAIN_TEMPLATE_LOADING - - Log debugging information tracing how a template file was loaded. This can - be useful to figure out why a template was not loaded or the wrong file - appears to be loaded. - - Default: ``False`` - -.. py:data:: MAX_COOKIE_SIZE - - Warn if cookie headers are larger than this many bytes. Defaults to - ``4093``. Larger cookies may be silently ignored by browsers. Set to - ``0`` to disable the warning. - -.. py:data:: PROVIDE_AUTOMATIC_OPTIONS - - Set to ``False`` to disable the automatic addition of OPTIONS - responses. This can be overridden per route by altering the - ``provide_automatic_options`` attribute. - -.. versionadded:: 0.4 - ``LOGGER_NAME`` - -.. versionadded:: 0.5 - ``SERVER_NAME`` - -.. versionadded:: 0.6 - ``MAX_CONTENT_LENGTH`` - -.. versionadded:: 0.7 - ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` - -.. versionadded:: 0.8 - ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``, - ``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``, - ``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``, - ``SESSION_COOKIE_SECURE`` - -.. versionadded:: 0.9 - ``PREFERRED_URL_SCHEME`` - -.. versionadded:: 0.10 - ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR`` - -.. versionadded:: 0.11 - ``SESSION_REFRESH_EACH_REQUEST``, ``TEMPLATES_AUTO_RELOAD``, - ``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING`` - -.. versionchanged:: 1.0 - ``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See - :doc:`/logging` for information about configuration. - - Added :data:`ENV` to reflect the :envvar:`FLASK_ENV` environment - variable. - - Added :data:`SESSION_COOKIE_SAMESITE` to control the session - cookie's ``SameSite`` option. - - Added :data:`MAX_COOKIE_SIZE` to control a warning from Werkzeug. - -.. versionchanged:: 2.2 - Removed ``PRESERVE_CONTEXT_ON_EXCEPTION``. - -.. versionchanged:: 2.3 - ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and - ``JSONIFY_PRETTYPRINT_REGULAR`` were removed. The default ``app.json`` provider has - equivalent attributes instead. - -.. versionchanged:: 2.3 - ``ENV`` was removed. - -.. versionadded:: 3.10 - Added :data:`PROVIDE_AUTOMATIC_OPTIONS` to control the default - addition of autogenerated OPTIONS responses. - - -Configuring from Python Files ------------------------------ - -Configuration becomes more useful if you can store it in a separate file, ideally -located outside the actual application package. You can deploy your application, then -separately configure it for the specific deployment. - -A common pattern is this:: - - app = Flask(__name__) - app.config.from_object('yourapplication.default_settings') - app.config.from_envvar('YOURAPPLICATION_SETTINGS') - -This first loads the configuration from the -`yourapplication.default_settings` module and then overrides the values -with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS` -environment variable points to. This environment variable can be set -in the shell before starting the server: - -.. tabs:: - - .. group-tab:: Bash - - .. code-block:: text - - $ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg - $ flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x YOURAPPLICATION_SETTINGS /path/to/settings.cfg - $ flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: CMD - - .. code-block:: text - - > set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg - > flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:YOURAPPLICATION_SETTINGS = "\path\to\settings.cfg" - > flask run - * Running on http://127.0.0.1:5000/ - -The configuration files themselves are actual Python files. Only values -in uppercase are actually stored in the config object later on. So make -sure to use uppercase letters for your config keys. - -Here is an example of a configuration file:: - - # Example configuration - SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' - -Make sure to load the configuration very early on, so that extensions have -the ability to access the configuration when starting up. There are other -methods on the config object as well to load from individual files. For a -complete reference, read the :class:`~flask.Config` object's -documentation. - - -Configuring from Data Files ---------------------------- - -It is also possible to load configuration from a file in a format of -your choice using :meth:`~flask.Config.from_file`. For example to load -from a TOML file: - -.. code-block:: python - - import tomllib - app.config.from_file("config.toml", load=tomllib.load, text=False) - -Or from a JSON file: - -.. code-block:: python - - import json - app.config.from_file("config.json", load=json.load) - - -Configuring from Environment Variables --------------------------------------- - -In addition to pointing to configuration files using environment -variables, you may find it useful (or necessary) to control your -configuration values directly from the environment. Flask can be -instructed to load all environment variables starting with a specific -prefix into the config using :meth:`~flask.Config.from_prefixed_env`. - -Environment variables can be set in the shell before starting the -server: - -.. tabs:: - - .. group-tab:: Bash - - .. code-block:: text - - $ export FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f" - $ export FLASK_MAIL_ENABLED=false - $ flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: Fish - - .. code-block:: text - - $ set -x FLASK_SECRET_KEY "5f352379324c22463451387a0aec5d2f" - $ set -x FLASK_MAIL_ENABLED false - $ flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: CMD - - .. code-block:: text - - > set FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f" - > set FLASK_MAIL_ENABLED=false - > flask run - * Running on http://127.0.0.1:5000/ - - .. group-tab:: Powershell - - .. code-block:: text - - > $env:FLASK_SECRET_KEY = "5f352379324c22463451387a0aec5d2f" - > $env:FLASK_MAIL_ENABLED = "false" - > flask run - * Running on http://127.0.0.1:5000/ - -The variables can then be loaded and accessed via the config with a key -equal to the environment variable name without the prefix i.e. - -.. code-block:: python - - app.config.from_prefixed_env() - app.config["SECRET_KEY"] # Is "5f352379324c22463451387a0aec5d2f" - -The prefix is ``FLASK_`` by default. This is configurable via the -``prefix`` argument of :meth:`~flask.Config.from_prefixed_env`. - -Values will be parsed to attempt to convert them to a more specific type -than strings. By default :func:`json.loads` is used, so any valid JSON -value is possible, including lists and dicts. This is configurable via -the ``loads`` argument of :meth:`~flask.Config.from_prefixed_env`. - -When adding a boolean value with the default JSON parsing, only "true" -and "false", lowercase, are valid values. Keep in mind that any -non-empty string is considered ``True`` by Python. - -It is possible to set keys in nested dictionaries by separating the -keys with double underscore (``__``). Any intermediate keys that don't -exist on the parent dict will be initialized to an empty dict. - -.. code-block:: text - - $ export FLASK_MYAPI__credentials__username=user123 - -.. code-block:: python - - app.config["MYAPI"]["credentials"]["username"] # Is "user123" - -On Windows, environment variable keys are always uppercase, therefore -the above example would end up as ``MYAPI__CREDENTIALS__USERNAME``. - -For even more config loading features, including merging and -case-insensitive Windows support, try a dedicated library such as -Dynaconf_, which includes integration with Flask. - -.. _Dynaconf: https://www.dynaconf.com/ - - -Configuration Best Practices ----------------------------- - -The downside with the approach mentioned earlier is that it makes testing -a little harder. There is no single 100% solution for this problem in -general, but there are a couple of things you can keep in mind to improve -that experience: - -1. Create your application in a function and register blueprints on it. - That way you can create multiple instances of your application with - different configurations attached which makes unit testing a lot - easier. You can use this to pass in configuration as needed. - -2. Do not write code that needs the configuration at import time. If you - limit yourself to request-only accesses to the configuration you can - reconfigure the object later on as needed. - -3. Make sure to load the configuration very early on, so that - extensions can access the configuration when calling ``init_app``. - - -.. _config-dev-prod: - -Development / Production ------------------------- - -Most applications need more than one configuration. There should be at -least separate configurations for the production server and the one used -during development. The easiest way to handle this is to use a default -configuration that is always loaded and part of the version control, and a -separate configuration that overrides the values as necessary as mentioned -in the example above:: - - app = Flask(__name__) - app.config.from_object('yourapplication.default_settings') - app.config.from_envvar('YOURAPPLICATION_SETTINGS') - -Then you just have to add a separate :file:`config.py` file and export -``YOURAPPLICATION_SETTINGS=/path/to/config.py`` and you are done. However -there are alternative ways as well. For example you could use imports or -subclassing. - -What is very popular in the Django world is to make the import explicit in -the config file by adding ``from yourapplication.default_settings -import *`` to the top of the file and then overriding the changes by hand. -You could also inspect an environment variable like -``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc -and import different hard-coded files based on that. - -An interesting pattern is also to use classes and inheritance for -configuration:: - - class Config(object): - TESTING = False - - class ProductionConfig(Config): - DATABASE_URI = 'mysql://user@localhost/foo' - - class DevelopmentConfig(Config): - DATABASE_URI = "sqlite:////tmp/foo.db" - - class TestingConfig(Config): - DATABASE_URI = 'sqlite:///:memory:' - TESTING = True - -To enable such a config you just have to call into -:meth:`~flask.Config.from_object`:: - - app.config.from_object('configmodule.ProductionConfig') - -Note that :meth:`~flask.Config.from_object` does not instantiate the class -object. If you need to instantiate the class, such as to access a property, -then you must do so before calling :meth:`~flask.Config.from_object`:: - - from configmodule import ProductionConfig - app.config.from_object(ProductionConfig()) - - # Alternatively, import via string: - from werkzeug.utils import import_string - cfg = import_string('configmodule.ProductionConfig')() - app.config.from_object(cfg) - -Instantiating the configuration object allows you to use ``@property`` in -your configuration classes:: - - class Config(object): - """Base config, uses staging database server.""" - TESTING = False - DB_SERVER = '192.168.1.56' - - @property - def DATABASE_URI(self): # Note: all caps - return f"mysql://user@{self.DB_SERVER}/foo" - - class ProductionConfig(Config): - """Uses production database server.""" - DB_SERVER = '192.168.19.32' - - class DevelopmentConfig(Config): - DB_SERVER = 'localhost' - - class TestingConfig(Config): - DB_SERVER = 'localhost' - DATABASE_URI = 'sqlite:///:memory:' - -There are many different ways and it's up to you how you want to manage -your configuration files. However here a list of good recommendations: - -- Keep a default configuration in version control. Either populate the - config with this default configuration or import it in your own - configuration files before overriding values. -- Use an environment variable to switch between the configurations. - This can be done from outside the Python interpreter and makes - development and deployment much easier because you can quickly and - easily switch between different configs without having to touch the - code at all. If you are working often on different projects you can - even create your own script for sourcing that activates a virtualenv - and exports the development configuration for you. -- Use a tool like `fabric`_ to push code and configuration separately - to the production server(s). - -.. _fabric: https://www.fabfile.org/ - - -.. _instance-folders: - -Instance Folders ----------------- - -.. versionadded:: 0.8 - -Flask 0.8 introduces instance folders. Flask for a long time made it -possible to refer to paths relative to the application's folder directly -(via :attr:`Flask.root_path`). This was also how many developers loaded -configurations stored next to the application. Unfortunately however this -only works well if applications are not packages in which case the root -path refers to the contents of the package. - -With Flask 0.8 a new attribute was introduced: -:attr:`Flask.instance_path`. It refers to a new concept called the -“instance folder”. The instance folder is designed to not be under -version control and be deployment specific. It's the perfect place to -drop things that either change at runtime or configuration files. - -You can either explicitly provide the path of the instance folder when -creating the Flask application or you can let Flask autodetect the -instance folder. For explicit configuration use the `instance_path` -parameter:: - - app = Flask(__name__, instance_path='/path/to/instance/folder') - -Please keep in mind that this path *must* be absolute when provided. - -If the `instance_path` parameter is not provided the following default -locations are used: - -- Uninstalled module:: - - /myapp.py - /instance - -- Uninstalled package:: - - /myapp - /__init__.py - /instance - -- Installed module or package:: - - $PREFIX/lib/pythonX.Y/site-packages/myapp - $PREFIX/var/myapp-instance - - ``$PREFIX`` is the prefix of your Python installation. This can be - ``/usr`` or the path to your virtualenv. You can print the value of - ``sys.prefix`` to see what the prefix is set to. - -Since the config object provided loading of configuration files from -relative filenames we made it possible to change the loading via filenames -to be relative to the instance path if wanted. The behavior of relative -paths in config files can be flipped between “relative to the application -root” (the default) to “relative to instance folder” via the -`instance_relative_config` switch to the application constructor:: - - app = Flask(__name__, instance_relative_config=True) - -Here is a full example of how to configure Flask to preload the config -from a module and then override the config from a file in the instance -folder if it exists:: - - app = Flask(__name__, instance_relative_config=True) - app.config.from_object('yourapplication.default_settings') - app.config.from_pyfile('application.cfg', silent=True) - -The path to the instance folder can be found via the -:attr:`Flask.instance_path`. Flask also provides a shortcut to open a -file from the instance folder with :meth:`Flask.open_instance_resource`. - -Example usage for both:: - - filename = os.path.join(app.instance_path, 'application.cfg') - with open(filename) as f: - config = f.read() - - # or via open_instance_resource: - with app.open_instance_resource('application.cfg') as f: - config = f.read() diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index d44f865f..00000000 --- a/docs/contributing.rst +++ /dev/null @@ -1,8 +0,0 @@ -Contributing -============ - -See the Pallets `detailed contributing documentation <_contrib>`_ for many ways -to contribute, including reporting issues, requesting features, asking or -answering questions, and making PRs. - -.. _contrib: https://palletsprojects.com/contributing/ diff --git a/docs/debugging.rst b/docs/debugging.rst deleted file mode 100644 index f6b56cab..00000000 --- a/docs/debugging.rst +++ /dev/null @@ -1,99 +0,0 @@ -Debugging Application Errors -============================ - - -In Production -------------- - -**Do not run the development server, or enable the built-in debugger, in -a production environment.** The debugger allows executing arbitrary -Python code from the browser. It's protected by a pin, but that should -not be relied on for security. - -Use an error logging tool, such as Sentry, as described in -:ref:`error-logging-tools`, or enable logging and notifications as -described in :doc:`/logging`. - -If you have access to the server, you could add some code to start an -external debugger if ``request.remote_addr`` matches your IP. Some IDE -debuggers also have a remote mode so breakpoints on the server can be -interacted with locally. Only enable a debugger temporarily. - - -The Built-In Debugger ---------------------- - -The built-in Werkzeug development server provides a debugger which shows -an interactive traceback in the browser when an unhandled error occurs -during a request. This debugger should only be used during development. - -.. image:: _static/debugger.png - :align: center - :class: screenshot - :alt: screenshot of debugger in action - -.. warning:: - - The debugger allows executing arbitrary Python code from the - browser. It is protected by a pin, but still represents a major - security risk. Do not run the development server or debugger in a - production environment. - -The debugger is enabled by default when the development server is run in debug mode. - -.. code-block:: text - - $ flask --app hello run --debug - -When running from Python code, passing ``debug=True`` enables debug mode, which is -mostly equivalent. - -.. code-block:: python - - app.run(debug=True) - -:doc:`/server` and :doc:`/cli` have more information about running the debugger and -debug mode. More information about the debugger can be found in the `Werkzeug -documentation `__. - - -External Debuggers ------------------- - -External debuggers, such as those provided by IDEs, can offer a more -powerful debugging experience than the built-in debugger. They can also -be used to step through code during a request before an error is raised, -or if no error is raised. Some even have a remote mode so you can debug -code running on another machine. - -When using an external debugger, the app should still be in debug mode, otherwise Flask -turns unhandled errors into generic 500 error pages. However, the built-in debugger and -reloader should be disabled so they don't interfere with the external debugger. - -.. code-block:: text - - $ flask --app hello run --debug --no-debugger --no-reload - -When running from Python: - -.. code-block:: python - - app.run(debug=True, use_debugger=False, use_reloader=False) - -Disabling these isn't required, an external debugger will continue to work with the -following caveats. - -- If the built-in debugger is not disabled, it will catch unhandled exceptions before - the external debugger can. -- If the reloader is not disabled, it could cause an unexpected reload if code changes - during a breakpoint. -- The development server will still catch unhandled exceptions if the built-in - debugger is disabled, otherwise it would crash on any error. If you want that (and - usually you don't) pass ``passthrough_errors=True`` to ``app.run``. - - .. code-block:: python - - app.run( - debug=True, passthrough_errors=True, - use_debugger=False, use_reloader=False - ) diff --git a/docs/deploying/apache-httpd.rst b/docs/deploying/apache-httpd.rst deleted file mode 100644 index bdeaf626..00000000 --- a/docs/deploying/apache-httpd.rst +++ /dev/null @@ -1,66 +0,0 @@ -Apache httpd -============ - -`Apache httpd`_ is a fast, production level HTTP server. When serving -your application with one of the WSGI servers listed in :doc:`index`, it -is often good or necessary to put a dedicated HTTP server in front of -it. This "reverse proxy" can handle incoming requests, TLS, and other -security and performance concerns better than the WSGI server. - -httpd can be installed using your system package manager, or a pre-built -executable for Windows. Installing and running httpd itself is outside -the scope of this doc. This page outlines the basics of configuring -httpd to proxy your application. Be sure to read its documentation to -understand what features are available. - -.. _Apache httpd: https://httpd.apache.org/ - - -Domain Name ------------ - -Acquiring and configuring a domain name is outside the scope of this -doc. In general, you will buy a domain name from a registrar, pay for -server space with a hosting provider, and then point your registrar -at the hosting provider's name servers. - -To simulate this, you can also edit your ``hosts`` file, located at -``/etc/hosts`` on Linux. Add a line that associates a name with the -local IP. - -Modern Linux systems may be configured to treat any domain name that -ends with ``.localhost`` like this without adding it to the ``hosts`` -file. - -.. code-block:: python - :caption: ``/etc/hosts`` - - 127.0.0.1 hello.localhost - - -Configuration -------------- - -The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on -Linux. It may be different depending on your operating system. Check the -docs and look for ``httpd.conf``. - -Remove or comment out any existing ``DocumentRoot`` directive. Add the -config lines below. We'll assume the WSGI server is listening locally at -``http://127.0.0.1:8000``. - -.. code-block:: apache - :caption: ``/etc/httpd/conf/httpd.conf`` - - LoadModule proxy_module modules/mod_proxy.so - LoadModule proxy_http_module modules/mod_proxy_http.so - ProxyPass / http://127.0.0.1:8000/ - RequestHeader set X-Forwarded-Proto http - RequestHeader set X-Forwarded-Prefix / - -The ``LoadModule`` lines might already exist. If so, make sure they are -uncommented instead of adding them manually. - -Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded`` -headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically -set by ``ProxyPass``. diff --git a/docs/deploying/asgi.rst b/docs/deploying/asgi.rst deleted file mode 100644 index 1dc0aa24..00000000 --- a/docs/deploying/asgi.rst +++ /dev/null @@ -1,27 +0,0 @@ -ASGI -==== - -If you'd like to use an ASGI server you will need to utilise WSGI to -ASGI middleware. The asgiref -`WsgiToAsgi `_ -adapter is recommended as it integrates with the event loop used for -Flask's :ref:`async_await` support. You can use the adapter by -wrapping the Flask app, - -.. code-block:: python - - from asgiref.wsgi import WsgiToAsgi - from flask import Flask - - app = Flask(__name__) - - ... - - asgi_app = WsgiToAsgi(app) - -and then serving the ``asgi_app`` with the ASGI server, e.g. using -`Hypercorn `_, - -.. sourcecode:: text - - $ hypercorn module:asgi_app diff --git a/docs/deploying/eventlet.rst b/docs/deploying/eventlet.rst deleted file mode 100644 index 8a718b22..00000000 --- a/docs/deploying/eventlet.rst +++ /dev/null @@ -1,80 +0,0 @@ -eventlet -======== - -Prefer using :doc:`gunicorn` with eventlet workers rather than using -`eventlet`_ directly. Gunicorn provides a much more configurable and -production-tested server. - -`eventlet`_ allows writing asynchronous, coroutine-based code that looks -like standard synchronous Python. It uses `greenlet`_ to enable task -switching without writing ``async/await`` or using ``asyncio``. - -:doc:`gevent` is another library that does the same thing. Certain -dependencies you have, or other considerations, may affect which of the -two you choose to use. - -eventlet provides a WSGI server that can handle many connections at once -instead of one per worker process. You must actually use eventlet in -your own code to see any benefit to using the server. - -.. _eventlet: https://eventlet.net/ -.. _greenlet: https://greenlet.readthedocs.io/en/latest/ - - -Installing ----------- - -When using eventlet, greenlet>=1.0 is required, otherwise context locals -such as ``request`` will not work as expected. When using PyPy, -PyPy>=7.3.7 is required. - -Create a virtualenv, install your application, then install -``eventlet``. - -.. code-block:: text - - $ cd hello-app - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install . # install your application - $ pip install eventlet - - -Running -------- - -To use eventlet to serve your application, write a script that imports -its ``wsgi.server``, as well as your app or app factory. - -.. code-block:: python - :caption: ``wsgi.py`` - - import eventlet - from eventlet import wsgi - from hello import create_app - - app = create_app() - wsgi.server(eventlet.listen(("127.0.0.1", 8000)), app) - -.. code-block:: text - - $ python wsgi.py - (x) wsgi starting up on http://127.0.0.1:8000 - - -Binding Externally ------------------- - -eventlet should not be run as root because it would cause your -application code to run as root, which is not secure. However, this -means it will not be possible to bind to port 80 or 443. Instead, a -reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used -in front of eventlet. - -You can bind to all external IPs on a non-privileged port by using -``0.0.0.0`` in the server arguments shown in the previous section. -Don't do this when using a reverse proxy setup, otherwise it will be -possible to bypass the proxy. - -``0.0.0.0`` is not a valid address to navigate to, you'd use a specific -IP address in your browser. diff --git a/docs/deploying/gevent.rst b/docs/deploying/gevent.rst deleted file mode 100644 index 448b93e7..00000000 --- a/docs/deploying/gevent.rst +++ /dev/null @@ -1,80 +0,0 @@ -gevent -====== - -Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather -than using `gevent`_ directly. Gunicorn and uWSGI provide much more -configurable and production-tested servers. - -`gevent`_ allows writing asynchronous, coroutine-based code that looks -like standard synchronous Python. It uses `greenlet`_ to enable task -switching without writing ``async/await`` or using ``asyncio``. - -:doc:`eventlet` is another library that does the same thing. Certain -dependencies you have, or other considerations, may affect which of the -two you choose to use. - -gevent provides a WSGI server that can handle many connections at once -instead of one per worker process. You must actually use gevent in your -own code to see any benefit to using the server. - -.. _gevent: https://www.gevent.org/ -.. _greenlet: https://greenlet.readthedocs.io/en/latest/ - - -Installing ----------- - -When using gevent, greenlet>=1.0 is required, otherwise context locals -such as ``request`` will not work as expected. When using PyPy, -PyPy>=7.3.7 is required. - -Create a virtualenv, install your application, then install ``gevent``. - -.. code-block:: text - - $ cd hello-app - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install . # install your application - $ pip install gevent - - -Running -------- - -To use gevent to serve your application, write a script that imports its -``WSGIServer``, as well as your app or app factory. - -.. code-block:: python - :caption: ``wsgi.py`` - - from gevent.pywsgi import WSGIServer - from hello import create_app - - app = create_app() - http_server = WSGIServer(("127.0.0.1", 8000), app) - http_server.serve_forever() - -.. code-block:: text - - $ python wsgi.py - -No output is shown when the server starts. - - -Binding Externally ------------------- - -gevent should not be run as root because it would cause your -application code to run as root, which is not secure. However, this -means it will not be possible to bind to port 80 or 443. Instead, a -reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used -in front of gevent. - -You can bind to all external IPs on a non-privileged port by using -``0.0.0.0`` in the server arguments shown in the previous section. Don't -do this when using a reverse proxy setup, otherwise it will be possible -to bypass the proxy. - -``0.0.0.0`` is not a valid address to navigate to, you'd use a specific -IP address in your browser. diff --git a/docs/deploying/gunicorn.rst b/docs/deploying/gunicorn.rst deleted file mode 100644 index c50edc23..00000000 --- a/docs/deploying/gunicorn.rst +++ /dev/null @@ -1,130 +0,0 @@ -Gunicorn -======== - -`Gunicorn`_ is a pure Python WSGI server with simple configuration and -multiple worker implementations for performance tuning. - -* It tends to integrate easily with hosting platforms. -* It does not support Windows (but does run on WSL). -* It is easy to install as it does not require additional dependencies - or compilation. -* It has built-in async worker support using gevent or eventlet. - -This page outlines the basics of running Gunicorn. Be sure to read its -`documentation`_ and use ``gunicorn --help`` to understand what features -are available. - -.. _Gunicorn: https://gunicorn.org/ -.. _documentation: https://docs.gunicorn.org/ - - -Installing ----------- - -Gunicorn is easy to install, as it does not require external -dependencies or compilation. It runs on Windows only under WSL. - -Create a virtualenv, install your application, then install -``gunicorn``. - -.. code-block:: text - - $ cd hello-app - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install . # install your application - $ pip install gunicorn - - -Running -------- - -The only required argument to Gunicorn tells it how to load your Flask -application. The syntax is ``{module_import}:{app_variable}``. -``module_import`` is the dotted import name to the module with your -application. ``app_variable`` is the variable with the application. It -can also be a function call (with any arguments) if you're using the -app factory pattern. - -.. code-block:: text - - # equivalent to 'from hello import app' - $ gunicorn -w 4 'hello:app' - - # equivalent to 'from hello import create_app; create_app()' - $ gunicorn -w 4 'hello:create_app()' - - Starting gunicorn 20.1.0 - Listening at: http://127.0.0.1:8000 (x) - Using worker: sync - Booting worker with pid: x - Booting worker with pid: x - Booting worker with pid: x - Booting worker with pid: x - -The ``-w`` option specifies the number of processes to run; a starting -value could be ``CPU * 2``. The default is only 1 worker, which is -probably not what you want for the default worker type. - -Logs for each request aren't shown by default, only worker info and -errors are shown. To show access logs on stdout, use the -``--access-logfile=-`` option. - - -Binding Externally ------------------- - -Gunicorn should not be run as root because it would cause your -application code to run as root, which is not secure. However, this -means it will not be possible to bind to port 80 or 443. Instead, a -reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used -in front of Gunicorn. - -You can bind to all external IPs on a non-privileged port using the -``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup, -otherwise it will be possible to bypass the proxy. - -.. code-block:: text - - $ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()' - Listening at: http://0.0.0.0:8000 (x) - -``0.0.0.0`` is not a valid address to navigate to, you'd use a specific -IP address in your browser. - - -Async with gevent or eventlet ------------------------------ - -The default sync worker is appropriate for many use cases. If you need -asynchronous support, Gunicorn provides workers using either `gevent`_ -or `eventlet`_. This is not the same as Python's ``async/await``, or the -ASGI server spec. You must actually use gevent/eventlet in your own code -to see any benefit to using the workers. - -When using either gevent or eventlet, greenlet>=1.0 is required, -otherwise context locals such as ``request`` will not work as expected. -When using PyPy, PyPy>=7.3.7 is required. - -To use gevent: - -.. code-block:: text - - $ gunicorn -k gevent 'hello:create_app()' - Starting gunicorn 20.1.0 - Listening at: http://127.0.0.1:8000 (x) - Using worker: gevent - Booting worker with pid: x - -To use eventlet: - -.. code-block:: text - - $ gunicorn -k eventlet 'hello:create_app()' - Starting gunicorn 20.1.0 - Listening at: http://127.0.0.1:8000 (x) - Using worker: eventlet - Booting worker with pid: x - -.. _gevent: https://www.gevent.org/ -.. _eventlet: https://eventlet.net/ diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst deleted file mode 100644 index 4135596a..00000000 --- a/docs/deploying/index.rst +++ /dev/null @@ -1,79 +0,0 @@ -Deploying to Production -======================= - -After developing your application, you'll want to make it available -publicly to other users. When you're developing locally, you're probably -using the built-in development server, debugger, and reloader. These -should not be used in production. Instead, you should use a dedicated -WSGI server or hosting platform, some of which will be described here. - -"Production" means "not development", which applies whether you're -serving your application publicly to millions of users or privately / -locally to a single user. **Do not use the development server when -deploying to production. It is intended for use only during local -development. It is not designed to be particularly secure, stable, or -efficient.** - -Self-Hosted Options -------------------- - -Flask is a WSGI *application*. A WSGI *server* is used to run the -application, converting incoming HTTP requests to the standard WSGI -environ, and converting outgoing WSGI responses to HTTP responses. - -The primary goal of these docs is to familiarize you with the concepts -involved in running a WSGI application using a production WSGI server -and HTTP server. There are many WSGI servers and HTTP servers, with many -configuration possibilities. The pages below discuss the most common -servers, and show the basics of running each one. The next section -discusses platforms that can manage this for you. - -.. toctree:: - :maxdepth: 1 - - gunicorn - waitress - mod_wsgi - uwsgi - gevent - eventlet - asgi - -WSGI servers have HTTP servers built-in. However, a dedicated HTTP -server may be safer, more efficient, or more capable. Putting an HTTP -server in front of the WSGI server is called a "reverse proxy." - -.. toctree:: - :maxdepth: 1 - - proxy_fix - nginx - apache-httpd - -This list is not exhaustive, and you should evaluate these and other -servers based on your application's needs. Different servers will have -different capabilities, configuration, and support. - - -Hosting Platforms ------------------ - -There are many services available for hosting web applications without -needing to maintain your own server, networking, domain, etc. Some -services may have a free tier up to a certain time or bandwidth. Many of -these services use one of the WSGI servers described above, or a similar -interface. The links below are for some of the most common platforms, -which have instructions for Flask, WSGI, or Python. - -- `PythonAnywhere `_ -- `Google App Engine `_ -- `Google Cloud Run `_ -- `AWS Elastic Beanstalk `_ -- `Microsoft Azure `_ - -This list is not exhaustive, and you should evaluate these and other -services based on your application's needs. Different services will have -different capabilities, configuration, pricing, and support. - -You'll probably need to :doc:`proxy_fix` when using most hosting -platforms. diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst deleted file mode 100644 index 23e82279..00000000 --- a/docs/deploying/mod_wsgi.rst +++ /dev/null @@ -1,94 +0,0 @@ -mod_wsgi -======== - -`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server. -The modern `mod_wsgi-express`_ command makes it easy to configure and -start the server without needing to write Apache httpd configuration. - -* Tightly integrated with Apache httpd. -* Supports Windows directly. -* Requires a compiler and the Apache development headers to install. -* Does not require a reverse proxy setup. - -This page outlines the basics of running mod_wsgi-express, not the more -complex installation and configuration with httpd. Be sure to read the -`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to -understand what features are available. - -.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/ -.. _mod_wsgi: https://modwsgi.readthedocs.io/ -.. _Apache httpd: https://httpd.apache.org/ - - -Installing ----------- - -Installing mod_wsgi requires a compiler and the Apache server and -development headers installed. You will get an error if they are not. -How to install them depends on the OS and package manager that you use. - -Create a virtualenv, install your application, then install -``mod_wsgi``. - -.. code-block:: text - - $ cd hello-app - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install . # install your application - $ pip install mod_wsgi - - -Running -------- - -The only argument to ``mod_wsgi-express`` specifies a script containing -your Flask application, which must be called ``application``. You can -write a small script to import your app with this name, or to create it -if using the app factory pattern. - -.. code-block:: python - :caption: ``wsgi.py`` - - from hello import app - - application = app - -.. code-block:: python - :caption: ``wsgi.py`` - - from hello import create_app - - application = create_app() - -Now run the ``mod_wsgi-express start-server`` command. - -.. code-block:: text - - $ mod_wsgi-express start-server wsgi.py --processes 4 - -The ``--processes`` option specifies the number of worker processes to -run; a starting value could be ``CPU * 2``. - -Logs for each request aren't show in the terminal. If an error occurs, -its information is written to the error log file shown when starting the -server. - - -Binding Externally ------------------- - -Unlike the other WSGI servers in these docs, mod_wsgi can be run as -root to bind to privileged ports like 80 and 443. However, it must be -configured to drop permissions to a different user and group for the -worker processes. - -For example, if you created a ``hello`` user and group, you should -install your virtualenv and application as that user, then tell -mod_wsgi to drop to that user after starting. - -.. code-block:: text - - $ sudo /home/hello/.venv/bin/mod_wsgi-express start-server \ - /home/hello/wsgi.py \ - --user hello --group hello --port 80 --processes 4 diff --git a/docs/deploying/nginx.rst b/docs/deploying/nginx.rst deleted file mode 100644 index 6b25c073..00000000 --- a/docs/deploying/nginx.rst +++ /dev/null @@ -1,69 +0,0 @@ -nginx -===== - -`nginx`_ is a fast, production level HTTP server. When serving your -application with one of the WSGI servers listed in :doc:`index`, it is -often good or necessary to put a dedicated HTTP server in front of it. -This "reverse proxy" can handle incoming requests, TLS, and other -security and performance concerns better than the WSGI server. - -Nginx can be installed using your system package manager, or a pre-built -executable for Windows. Installing and running Nginx itself is outside -the scope of this doc. This page outlines the basics of configuring -Nginx to proxy your application. Be sure to read its documentation to -understand what features are available. - -.. _nginx: https://nginx.org/ - - -Domain Name ------------ - -Acquiring and configuring a domain name is outside the scope of this -doc. In general, you will buy a domain name from a registrar, pay for -server space with a hosting provider, and then point your registrar -at the hosting provider's name servers. - -To simulate this, you can also edit your ``hosts`` file, located at -``/etc/hosts`` on Linux. Add a line that associates a name with the -local IP. - -Modern Linux systems may be configured to treat any domain name that -ends with ``.localhost`` like this without adding it to the ``hosts`` -file. - -.. code-block:: python - :caption: ``/etc/hosts`` - - 127.0.0.1 hello.localhost - - -Configuration -------------- - -The nginx configuration is located at ``/etc/nginx/nginx.conf`` on -Linux. It may be different depending on your operating system. Check the -docs and look for ``nginx.conf``. - -Remove or comment out any existing ``server`` section. Add a ``server`` -section and use the ``proxy_pass`` directive to point to the address the -WSGI server is listening on. We'll assume the WSGI server is listening -locally at ``http://127.0.0.1:8000``. - -.. code-block:: nginx - :caption: ``/etc/nginx.conf`` - - server { - listen 80; - server_name _; - - location / { - proxy_pass http://127.0.0.1:8000/; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Prefix /; - } - } - -Then :doc:`proxy_fix` so that your application uses these headers. diff --git a/docs/deploying/proxy_fix.rst b/docs/deploying/proxy_fix.rst deleted file mode 100644 index e2c42e82..00000000 --- a/docs/deploying/proxy_fix.rst +++ /dev/null @@ -1,33 +0,0 @@ -Tell Flask it is Behind a Proxy -=============================== - -When using a reverse proxy, or many Python hosting platforms, the proxy -will intercept and forward all external requests to the local WSGI -server. - -From the WSGI server and Flask application's perspectives, requests are -now coming from the HTTP server to the local address, rather than from -the remote address to the external server address. - -HTTP servers should set ``X-Forwarded-`` headers to pass on the real -values to the application. The application can then be told to trust and -use those values by wrapping it with the -:doc:`werkzeug:middleware/proxy_fix` middleware provided by Werkzeug. - -This middleware should only be used if the application is actually -behind a proxy, and should be configured with the number of proxies that -are chained in front of it. Not all proxies set all the headers. Since -incoming headers can be faked, you must set how many proxies are setting -each header so the middleware knows what to trust. - -.. code-block:: python - - from werkzeug.middleware.proxy_fix import ProxyFix - - app.wsgi_app = ProxyFix( - app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 - ) - -Remember, only apply this middleware if you are behind a proxy, and set -the correct number of proxies that set each header. It can be a security -issue if you get this configuration wrong. diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst deleted file mode 100644 index 1f9d5eca..00000000 --- a/docs/deploying/uwsgi.rst +++ /dev/null @@ -1,145 +0,0 @@ -uWSGI -===== - -`uWSGI`_ is a fast, compiled server suite with extensive configuration -and capabilities beyond a basic server. - -* It can be very performant due to being a compiled program. -* It is complex to configure beyond the basic application, and has so - many options that it can be difficult for beginners to understand. -* It does not support Windows (but does run on WSL). -* It requires a compiler to install in some cases. - -This page outlines the basics of running uWSGI. Be sure to read its -documentation to understand what features are available. - -.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/ - - -Installing ----------- - -uWSGI has multiple ways to install it. The most straightforward is to -install the ``pyuwsgi`` package, which provides precompiled wheels for -common platforms. However, it does not provide SSL support, which can be -provided with a reverse proxy instead. - -Create a virtualenv, install your application, then install ``pyuwsgi``. - -.. code-block:: text - - $ cd hello-app - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install . # install your application - $ pip install pyuwsgi - -If you have a compiler available, you can install the ``uwsgi`` package -instead. Or install the ``pyuwsgi`` package from sdist instead of wheel. -Either method will include SSL support. - -.. code-block:: text - - $ pip install uwsgi - - # or - $ pip install --no-binary pyuwsgi pyuwsgi - - -Running -------- - -The most basic way to run uWSGI is to tell it to start an HTTP server -and import your application. - -.. code-block:: text - - $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app - - *** Starting uWSGI 2.0.20 (64bit) on [x] *** - *** Operational MODE: preforking *** - mounting hello:app on / - spawned uWSGI master process (pid: x) - spawned uWSGI worker 1 (pid: x, cores: 1) - spawned uWSGI worker 2 (pid: x, cores: 1) - spawned uWSGI worker 3 (pid: x, cores: 1) - spawned uWSGI worker 4 (pid: x, cores: 1) - spawned uWSGI http 1 (pid: x) - -If you're using the app factory pattern, you'll need to create a small -Python file to create the app, then point uWSGI at that. - -.. code-block:: python - :caption: ``wsgi.py`` - - from hello import create_app - - app = create_app() - -.. code-block:: text - - $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app - -The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The -``--master`` option specifies the standard worker manager. The ``-p`` -option starts 4 worker processes; a starting value could be ``CPU * 2``. -The ``-w`` option tells uWSGI how to import your application - - -Binding Externally ------------------- - -uWSGI should not be run as root with the configuration shown in this doc -because it would cause your application code to run as root, which is -not secure. However, this means it will not be possible to bind to port -80 or 443. Instead, a reverse proxy such as :doc:`nginx` or -:doc:`apache-httpd` should be used in front of uWSGI. It is possible to -run uWSGI as root securely, but that is beyond the scope of this doc. - -uWSGI has optimized integration with `Nginx uWSGI`_ and -`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using -a standard HTTP proxy. That configuration is beyond the scope of this -doc, see the links for more information. - -.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html -.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi - -You can bind to all external IPs on a non-privileged port using the -``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy -setup, otherwise it will be possible to bypass the proxy. - -.. code-block:: text - - $ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app - -``0.0.0.0`` is not a valid address to navigate to, you'd use a specific -IP address in your browser. - - -Async with gevent ------------------ - -The default sync worker is appropriate for many use cases. If you need -asynchronous support, uWSGI provides a `gevent`_ worker. This is not the -same as Python's ``async/await``, or the ASGI server spec. You must -actually use gevent in your own code to see any benefit to using the -worker. - -When using gevent, greenlet>=1.0 is required, otherwise context locals -such as ``request`` will not work as expected. When using PyPy, -PyPy>=7.3.7 is required. - -.. code-block:: text - - $ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app - - *** Starting uWSGI 2.0.20 (64bit) on [x] *** - *** Operational MODE: async *** - mounting hello:app on / - spawned uWSGI master process (pid: x) - spawned uWSGI worker 1 (pid: x, cores: 100) - spawned uWSGI http 1 (pid: x) - *** running gevent loop engine [addr:x] *** - - -.. _gevent: https://www.gevent.org/ diff --git a/docs/deploying/waitress.rst b/docs/deploying/waitress.rst deleted file mode 100644 index 7bdd695b..00000000 --- a/docs/deploying/waitress.rst +++ /dev/null @@ -1,75 +0,0 @@ -Waitress -======== - -`Waitress`_ is a pure Python WSGI server. - -* It is easy to configure. -* It supports Windows directly. -* It is easy to install as it does not require additional dependencies - or compilation. -* It does not support streaming requests, full request data is always - buffered. -* It uses a single process with multiple thread workers. - -This page outlines the basics of running Waitress. Be sure to read its -documentation and ``waitress-serve --help`` to understand what features -are available. - -.. _Waitress: https://docs.pylonsproject.org/projects/waitress/ - - -Installing ----------- - -Create a virtualenv, install your application, then install -``waitress``. - -.. code-block:: text - - $ cd hello-app - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install . # install your application - $ pip install waitress - - -Running -------- - -The only required argument to ``waitress-serve`` tells it how to load -your Flask application. The syntax is ``{module}:{app}``. ``module`` is -the dotted import name to the module with your application. ``app`` is -the variable with the application. If you're using the app factory -pattern, use ``--call {module}:{factory}`` instead. - -.. code-block:: text - - # equivalent to 'from hello import app' - $ waitress-serve --host 127.0.0.1 hello:app - - # equivalent to 'from hello import create_app; create_app()' - $ waitress-serve --host 127.0.0.1 --call hello:create_app - - Serving on http://127.0.0.1:8080 - -The ``--host`` option binds the server to local ``127.0.0.1`` only. - -Logs for each request aren't shown, only errors are shown. Logging can -be configured through the Python interface instead of the command line. - - -Binding Externally ------------------- - -Waitress should not be run as root because it would cause your -application code to run as root, which is not secure. However, this -means it will not be possible to bind to port 80 or 443. Instead, a -reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used -in front of Waitress. - -You can bind to all external IPs on a non-privileged port by not -specifying the ``--host`` option. Don't do this when using a reverse -proxy setup, otherwise it will be possible to bypass the proxy. - -``0.0.0.0`` is not a valid address to navigate to, you'd use a specific -IP address in your browser. diff --git a/docs/design.rst b/docs/design.rst deleted file mode 100644 index 066cf107..00000000 --- a/docs/design.rst +++ /dev/null @@ -1,228 +0,0 @@ -Design Decisions in Flask -========================= - -If you are curious why Flask does certain things the way it does and not -differently, this section is for you. This should give you an idea about -some of the design decisions that may appear arbitrary and surprising at -first, especially in direct comparison with other frameworks. - - -The Explicit Application Object -------------------------------- - -A Python web application based on WSGI has to have one central callable -object that implements the actual application. In Flask this is an -instance of the :class:`~flask.Flask` class. Each Flask application has -to create an instance of this class itself and pass it the name of the -module, but why can't Flask do that itself? - -Without such an explicit application object the following code:: - - from flask import Flask - app = Flask(__name__) - - @app.route('/') - def index(): - return 'Hello World!' - -Would look like this instead:: - - from hypothetical_flask import route - - @route('/') - def index(): - return 'Hello World!' - -There are three major reasons for this. The most important one is that -implicit application objects require that there may only be one instance at -the time. There are ways to fake multiple applications with a single -application object, like maintaining a stack of applications, but this -causes some problems I won't outline here in detail. Now the question is: -when does a microframework need more than one application at the same -time? A good example for this is unit testing. When you want to test -something it can be very helpful to create a minimal application to test -specific behavior. When the application object is deleted everything it -allocated will be freed again. - -Another thing that becomes possible when you have an explicit object lying -around in your code is that you can subclass the base class -(:class:`~flask.Flask`) to alter specific behavior. This would not be -possible without hacks if the object were created ahead of time for you -based on a class that is not exposed to you. - -But there is another very important reason why Flask depends on an -explicit instantiation of that class: the package name. Whenever you -create a Flask instance you usually pass it `__name__` as package name. -Flask depends on that information to properly load resources relative -to your module. With Python's outstanding support for reflection it can -then access the package to figure out where the templates and static files -are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there -are frameworks around that do not need any configuration and will still be -able to load templates relative to your application module. But they have -to use the current working directory for that, which is a very unreliable -way to determine where the application is. The current working directory -is process-wide and if you are running multiple applications in one -process (which could happen in a webserver without you knowing) the paths -will be off. Worse: many webservers do not set the working directory to -the directory of your application but to the document root which does not -have to be the same folder. - -The third reason is "explicit is better than implicit". That object is -your WSGI application, you don't have to remember anything else. If you -want to apply a WSGI middleware, just wrap it and you're done (though -there are better ways to do that so that you do not lose the reference -to the application object :meth:`~flask.Flask.wsgi_app`). - -Furthermore this design makes it possible to use a factory function to -create the application which is very helpful for unit testing and similar -things (:doc:`/patterns/appfactories`). - -The Routing System ------------------- - -Flask uses the Werkzeug routing system which was designed to -automatically order routes by complexity. This means that you can declare -routes in arbitrary order and they will still work as expected. This is a -requirement if you want to properly implement decorator based routing -since decorators could be fired in undefined order when the application is -split into multiple modules. - -Another design decision with the Werkzeug routing system is that routes -in Werkzeug try to ensure that URLs are unique. Werkzeug will go quite far -with that in that it will automatically redirect to a canonical URL if a route -is ambiguous. - - -One Template Engine -------------------- - -Flask decides on one template engine: Jinja2. Why doesn't Flask have a -pluggable template engine interface? You can obviously use a different -template engine, but Flask will still configure Jinja2 for you. While -that limitation that Jinja2 is *always* configured will probably go away, -the decision to bundle one template engine and use that will not. - -Template engines are like programming languages and each of those engines -has a certain understanding about how things work. On the surface they -all work the same: you tell the engine to evaluate a template with a set -of variables and take the return value as string. - -But that's about where similarities end. Jinja2 for example has an -extensive filter system, a certain way to do template inheritance, -support for reusable blocks (macros) that can be used from inside -templates and also from Python code, supports iterative template -rendering, configurable syntax and more. On the other hand an engine -like Genshi is based on XML stream evaluation, template inheritance by -taking the availability of XPath into account and more. Mako on the -other hand treats templates similar to Python modules. - -When it comes to connecting a template engine with an application or -framework there is more than just rendering templates. For instance, -Flask uses Jinja2's extensive autoescaping support. Also it provides -ways to access macros from Jinja2 templates. - -A template abstraction layer that would not take the unique features of -the template engines away is a science on its own and a too large -undertaking for a microframework like Flask. - -Furthermore extensions can then easily depend on one template language -being present. You can easily use your own templating language, but an -extension could still depend on Jinja itself. - - -What does "micro" mean? ------------------------ - -“Micro” does not mean that your whole web application has to fit into a single -Python file (although it certainly can), nor does it mean that Flask is lacking -in functionality. The "micro" in microframework means Flask aims to keep the -core simple but extensible. Flask won't make many decisions for you, such as -what database to use. Those decisions that it does make, such as what -templating engine to use, are easy to change. Everything else is up to you, so -that Flask can be everything you need and nothing you don't. - -By default, Flask does not include a database abstraction layer, form -validation or anything else where different libraries already exist that can -handle that. Instead, Flask supports extensions to add such functionality to -your application as if it was implemented in Flask itself. Numerous extensions -provide database integration, form validation, upload handling, various open -authentication technologies, and more. Flask may be "micro", but it's ready for -production use on a variety of needs. - -Why does Flask call itself a microframework and yet it depends on two -libraries (namely Werkzeug and Jinja2). Why shouldn't it? If we look -over to the Ruby side of web development there we have a protocol very -similar to WSGI. Just that it's called Rack there, but besides that it -looks very much like a WSGI rendition for Ruby. But nearly all -applications in Ruby land do not work with Rack directly, but on top of a -library with the same name. This Rack library has two equivalents in -Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but -from my understanding it's sort of deprecated in favour of WebOb. The -development of WebOb and Werkzeug started side by side with similar ideas -in mind: be a good implementation of WSGI for other applications to take -advantage. - -Flask is a framework that takes advantage of the work already done by -Werkzeug to properly interface WSGI (which can be a complex task at -times). Thanks to recent developments in the Python package -infrastructure, packages with dependencies are no longer an issue and -there are very few reasons against having libraries that depend on others. - - -Thread Locals -------------- - -Flask uses thread local objects (context local objects in fact, they -support greenlet contexts as well) for request, session and an extra -object you can put your own things on (:data:`~flask.g`). Why is that and -isn't that a bad idea? - -Yes it is usually not such a bright idea to use thread locals. They cause -troubles for servers that are not based on the concept of threads and make -large applications harder to maintain. However Flask is just not designed -for large applications or asynchronous servers. Flask wants to make it -quick and easy to write a traditional web application. - - -Async/await and ASGI support ----------------------------- - -Flask supports ``async`` coroutines for view functions by executing the -coroutine on a separate thread instead of using an event loop on the -main thread as an async-first (ASGI) framework would. This is necessary -for Flask to remain backwards compatible with extensions and code built -before ``async`` was introduced into Python. This compromise introduces -a performance cost compared with the ASGI frameworks, due to the -overhead of the threads. - -Due to how tied to WSGI Flask's code is, it's not clear if it's possible -to make the ``Flask`` class support ASGI and WSGI at the same time. Work -is currently being done in Werkzeug to work with ASGI, which may -eventually enable support in Flask as well. - -See :doc:`/async-await` for more discussion. - - -What Flask is, What Flask is Not --------------------------------- - -Flask will never have a database layer. It will not have a form library -or anything else in that direction. Flask itself just bridges to Werkzeug -to implement a proper WSGI application and to Jinja2 to handle templating. -It also binds to a few common standard library packages such as logging. -Everything else is up for extensions. - -Why is this the case? Because people have different preferences and -requirements and Flask could not meet those if it would force any of this -into the core. The majority of web applications will need a template -engine in some sort. However not every application needs a SQL database. - -As your codebase grows, you are free to make the design decisions appropriate -for your project. Flask will continue to provide a very simple glue layer to -the best that Python has to offer. You can implement advanced patterns in -SQLAlchemy or another database tool, introduce non-relational data persistence -as appropriate, and take advantage of framework-agnostic tools built for WSGI, -the Python web interface. - -The idea of Flask is to build a good foundation for all applications. -Everything else is up to you or extensions. diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst deleted file mode 100644 index faca58c2..00000000 --- a/docs/errorhandling.rst +++ /dev/null @@ -1,523 +0,0 @@ -Handling Application Errors -=========================== - -Applications fail, servers fail. Sooner or later you will see an exception -in production. Even if your code is 100% correct, you will still see -exceptions from time to time. Why? Because everything else involved will -fail. Here are some situations where perfectly fine code can lead to server -errors: - -- the client terminated the request early and the application was still - reading from the incoming data -- the database server was overloaded and could not handle the query -- a filesystem is full -- a harddrive crashed -- a backend server overloaded -- a programming error in a library you are using -- network connection of the server to another system failed - -And that's just a small sample of issues you could be facing. So how do we -deal with that sort of problem? By default if your application runs in -production mode, and an exception is raised Flask will display a very simple -page for you and log the exception to the :attr:`~flask.Flask.logger`. - -But there is more you can do, and we will cover some better setups to deal -with errors including custom exceptions and 3rd party tools. - - -.. _error-logging-tools: - -Error Logging Tools -------------------- - -Sending error mails, even if just for critical ones, can become -overwhelming if enough users are hitting the error and log files are -typically never looked at. This is why we recommend using `Sentry -`_ for dealing with application errors. It's -available as a source-available project `on GitHub -`_ and is also available as a `hosted version -`_ which you can try for free. Sentry -aggregates duplicate errors, captures the full stack trace and local -variables for debugging, and sends you mails based on new errors or -frequency thresholds. - -To use Sentry you need to install the ``sentry-sdk`` client with extra -``flask`` dependencies. - -.. code-block:: text - - $ pip install sentry-sdk[flask] - -And then add this to your Flask app: - -.. code-block:: python - - import sentry_sdk - from sentry_sdk.integrations.flask import FlaskIntegration - - sentry_sdk.init('YOUR_DSN_HERE', integrations=[FlaskIntegration()]) - -The ``YOUR_DSN_HERE`` value needs to be replaced with the DSN value you -get from your Sentry installation. - -After installation, failures leading to an Internal Server Error -are automatically reported to Sentry and from there you can -receive error notifications. - -See also: - -- Sentry also supports catching errors from a worker queue - (RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs - `__ for more information. -- `Flask-specific documentation `__ - - -Error Handlers --------------- - -When an error occurs in Flask, an appropriate `HTTP status code -`__ will be -returned. 400-499 indicate errors with the client's request data, or -about the data requested. 500-599 indicate errors with the server or -application itself. - -You might want to show custom error pages to the user when an error occurs. -This can be done by registering error handlers. - -An error handler is a function that returns a response when a type of error is -raised, similar to how a view is a function that returns a response when a -request URL is matched. It is passed the instance of the error being handled, -which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. - -The status code of the response will not be set to the handler's code. Make -sure to provide the appropriate HTTP status code when returning a response from -a handler. - - -Registering -``````````` - -Register handlers by decorating a function with -:meth:`~flask.Flask.errorhandler`. Or use -:meth:`~flask.Flask.register_error_handler` to register the function later. -Remember to set the error code when returning the response. - -.. code-block:: python - - @app.errorhandler(werkzeug.exceptions.BadRequest) - def handle_bad_request(e): - return 'bad request!', 400 - - # or, without the decorator - app.register_error_handler(400, handle_bad_request) - -:exc:`werkzeug.exceptions.HTTPException` subclasses like -:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable -when registering handlers. (``BadRequest.code == 400``) - -Non-standard HTTP codes cannot be registered by code because they are not known -by Werkzeug. Instead, define a subclass of -:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and -register and raise that exception class. - -.. code-block:: python - - class InsufficientStorage(werkzeug.exceptions.HTTPException): - code = 507 - description = 'Not enough storage space.' - - app.register_error_handler(InsufficientStorage, handle_507) - - raise InsufficientStorage() - -Handlers can be registered for any exception class, not just -:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status -codes. Handlers can be registered for a specific class, or for all subclasses -of a parent class. - - -Handling -```````` - -When building a Flask application you *will* run into exceptions. If some part -of your code breaks while handling a request (and you have no error handlers -registered), a "500 Internal Server Error" -(:exc:`~werkzeug.exceptions.InternalServerError`) will be returned by default. -Similarly, "404 Not Found" -(:exc:`~werkzeug.exceptions.NotFound`) error will occur if a request is sent to an unregistered route. -If a route receives an unallowed request method, a "405 Method Not Allowed" -(:exc:`~werkzeug.exceptions.MethodNotAllowed`) will be raised. These are all -subclasses of :class:`~werkzeug.exceptions.HTTPException` and are provided by -default in Flask. - -Flask gives you the ability to raise any HTTP exception registered by -Werkzeug. However, the default HTTP exceptions return simple exception -pages. You might want to show custom error pages to the user when an error occurs. -This can be done by registering error handlers. - -When Flask catches an exception while handling a request, it is first looked up by code. -If no handler is registered for the code, Flask looks up the error by its class hierarchy; the most specific handler is chosen. -If no handler is registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a -generic message about their code, while other exceptions are converted to a -generic "500 Internal Server Error". - -For example, if an instance of :exc:`ConnectionRefusedError` is raised, -and a handler is registered for :exc:`ConnectionError` and -:exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError` -handler is called with the exception instance to generate the response. - -Handlers registered on the blueprint take precedence over those registered -globally on the application, assuming a blueprint is handling the request that -raises the exception. However, the blueprint cannot handle 404 routing errors -because the 404 occurs at the routing level before the blueprint can be -determined. - - -Generic Exception Handlers -`````````````````````````` - -It is possible to register error handlers for very generic base classes -such as ``HTTPException`` or even ``Exception``. However, be aware that -these will catch more than you might expect. - -For example, an error handler for ``HTTPException`` might be useful for turning -the default HTML errors pages into JSON. However, this -handler will trigger for things you don't cause directly, such as 404 -and 405 errors during routing. Be sure to craft your handler carefully -so you don't lose information about the HTTP error. - -.. code-block:: python - - from flask import json - from werkzeug.exceptions import HTTPException - - @app.errorhandler(HTTPException) - def handle_exception(e): - """Return JSON instead of HTML for HTTP errors.""" - # start with the correct headers and status code from the error - response = e.get_response() - # replace the body with JSON - response.data = json.dumps({ - "code": e.code, - "name": e.name, - "description": e.description, - }) - response.content_type = "application/json" - return response - -An error handler for ``Exception`` might seem useful for changing how -all errors, even unhandled ones, are presented to the user. However, -this is similar to doing ``except Exception:`` in Python, it will -capture *all* otherwise unhandled errors, including all HTTP status -codes. - -In most cases it will be safer to register handlers for more -specific exceptions. Since ``HTTPException`` instances are valid WSGI -responses, you could also pass them through directly. - -.. code-block:: python - - from werkzeug.exceptions import HTTPException - - @app.errorhandler(Exception) - def handle_exception(e): - # pass through HTTP errors - if isinstance(e, HTTPException): - return e - - # now you're handling non-HTTP exceptions only - return render_template("500_generic.html", e=e), 500 - -Error handlers still respect the exception class hierarchy. If you -register handlers for both ``HTTPException`` and ``Exception``, the -``Exception`` handler will not handle ``HTTPException`` subclasses -because the ``HTTPException`` handler is more specific. - - -Unhandled Exceptions -```````````````````` - -When there is no error handler registered for an exception, a 500 -Internal Server Error will be returned instead. See -:meth:`flask.Flask.handle_exception` for information about this -behavior. - -If there is an error handler registered for ``InternalServerError``, -this will be invoked. As of Flask 1.1.0, this error handler will always -be passed an instance of ``InternalServerError``, not the original -unhandled error. - -The original error is available as ``e.original_exception``. - -An error handler for "500 Internal Server Error" will be passed uncaught -exceptions in addition to explicit 500 errors. In debug mode, a handler -for "500 Internal Server Error" will not be used. Instead, the -interactive debugger will be shown. - - -Custom Error Pages ------------------- - -Sometimes when building a Flask application, you might want to raise a -:exc:`~werkzeug.exceptions.HTTPException` to signal to the user that -something is wrong with the request. Fortunately, Flask comes with a handy -:func:`~flask.abort` function that aborts a request with a HTTP error from -werkzeug as desired. It will also provide a plain black and white error page -for you with a basic description, but nothing fancy. - -Depending on the error code it is less or more likely for the user to -actually see such an error. - -Consider the code below, we might have a user profile route, and if the user -fails to pass a username we can raise a "400 Bad Request". If the user passes a -username and we can't find it, we raise a "404 Not Found". - -.. code-block:: python - - from flask import abort, render_template, request - - # a username needs to be supplied in the query args - # a successful request would be like /profile?username=jack - @app.route("/profile") - def user_profile(): - username = request.arg.get("username") - # if a username isn't supplied in the request, return a 400 bad request - if username is None: - abort(400) - - user = get_user(username=username) - # if a user can't be found by their username, return 404 not found - if user is None: - abort(404) - - return render_template("profile.html", user=user) - -Here is another example implementation for a "404 Page Not Found" exception: - -.. code-block:: python - - from flask import render_template - - @app.errorhandler(404) - def page_not_found(e): - # note that we set the 404 status explicitly - return render_template('404.html'), 404 - -When using :doc:`/patterns/appfactories`: - -.. code-block:: python - - from flask import Flask, render_template - - def page_not_found(e): - return render_template('404.html'), 404 - - def create_app(config_filename): - app = Flask(__name__) - app.register_error_handler(404, page_not_found) - return app - -An example template might be this: - -.. code-block:: html+jinja - - {% extends "layout.html" %} - {% block title %}Page Not Found{% endblock %} - {% block body %} -

Page Not Found

-

What you were looking for is just not there. -

go somewhere nice - {% endblock %} - - -Further Examples -```````````````` - -The above examples wouldn't actually be an improvement on the default -exception pages. We can create a custom 500.html template like this: - -.. code-block:: html+jinja - - {% extends "layout.html" %} - {% block title %}Internal Server Error{% endblock %} - {% block body %} -

Internal Server Error

-

Oops... we seem to have made a mistake, sorry!

-

Go somewhere nice instead - {% endblock %} - -It can be implemented by rendering the template on "500 Internal Server Error": - -.. code-block:: python - - from flask import render_template - - @app.errorhandler(500) - def internal_server_error(e): - # note that we set the 500 status explicitly - return render_template('500.html'), 500 - -When using :doc:`/patterns/appfactories`: - -.. code-block:: python - - from flask import Flask, render_template - - def internal_server_error(e): - return render_template('500.html'), 500 - - def create_app(): - app = Flask(__name__) - app.register_error_handler(500, internal_server_error) - return app - -When using :doc:`/blueprints`: - -.. code-block:: python - - from flask import Blueprint - - blog = Blueprint('blog', __name__) - - # as a decorator - @blog.errorhandler(500) - def internal_server_error(e): - return render_template('500.html'), 500 - - # or with register_error_handler - blog.register_error_handler(500, internal_server_error) - - -Blueprint Error Handlers ------------------------- - -In :doc:`/blueprints`, most error handlers will work as expected. -However, there is a caveat concerning handlers for 404 and 405 -exceptions. These error handlers are only invoked from an appropriate -``raise`` statement or a call to ``abort`` in another of the blueprint's -view functions; they are not invoked by, e.g., an invalid URL access. - -This is because the blueprint does not "own" a certain URL space, so -the application instance has no way of knowing which blueprint error -handler it should run if given an invalid URL. If you would like to -execute different handling strategies for these errors based on URL -prefixes, they may be defined at the application level using the -``request`` proxy object. - -.. code-block:: python - - from flask import jsonify, render_template - - # at the application level - # not the blueprint level - @app.errorhandler(404) - def page_not_found(e): - # if a request is in our blog URL space - if request.path.startswith('/blog/'): - # we return a custom blog 404 page - return render_template("blog/404.html"), 404 - else: - # otherwise we return our generic site-wide 404 page - return render_template("404.html"), 404 - - @app.errorhandler(405) - def method_not_allowed(e): - # if a request has the wrong method to our API - if request.path.startswith('/api/'): - # we return a json saying so - return jsonify(message="Method Not Allowed"), 405 - else: - # otherwise we return a generic site-wide 405 page - return render_template("405.html"), 405 - - -Returning API Errors as JSON ----------------------------- - -When building APIs in Flask, some developers realise that the built-in -exceptions are not expressive enough for APIs and that the content type of -:mimetype:`text/html` they are emitting is not very useful for API consumers. - -Using the same techniques as above and :func:`~flask.json.jsonify` we can return JSON -responses to API errors. :func:`~flask.abort` is called -with a ``description`` parameter. The error handler will -use that as the JSON error message, and set the status code to 404. - -.. code-block:: python - - from flask import abort, jsonify - - @app.errorhandler(404) - def resource_not_found(e): - return jsonify(error=str(e)), 404 - - @app.route("/cheese") - def get_one_cheese(): - resource = get_resource() - - if resource is None: - abort(404, description="Resource not found") - - return jsonify(resource) - -We can also create custom exception classes. For instance, we can -introduce a new custom exception for an API that can take a proper human readable message, -a status code for the error and some optional payload to give more context -for the error. - -This is a simple example: - -.. code-block:: python - - from flask import jsonify, request - - class InvalidAPIUsage(Exception): - status_code = 400 - - def __init__(self, message, status_code=None, payload=None): - super().__init__() - self.message = message - if status_code is not None: - self.status_code = status_code - self.payload = payload - - def to_dict(self): - rv = dict(self.payload or ()) - rv['message'] = self.message - return rv - - @app.errorhandler(InvalidAPIUsage) - def invalid_api_usage(e): - return jsonify(e.to_dict()), e.status_code - - # an API app route for getting user information - # a correct request might be /api/user?user_id=420 - @app.route("/api/user") - def user_api(user_id): - user_id = request.arg.get("user_id") - if not user_id: - raise InvalidAPIUsage("No user id provided!") - - user = get_user(user_id=user_id) - if not user: - raise InvalidAPIUsage("No such user!", status_code=404) - - return jsonify(user.to_dict()) - -A view can now raise that exception with an error message. Additionally -some extra payload can be provided as a dictionary through the `payload` -parameter. - - -Logging -------- - -See :doc:`/logging` for information about how to log exceptions, such as -by emailing them to admins. - - -Debugging ---------- - -See :doc:`/debugging` for information about how to debug errors in -development and production. diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst deleted file mode 100644 index 0c74ad92..00000000 --- a/docs/extensiondev.rst +++ /dev/null @@ -1,304 +0,0 @@ -Flask Extension Development -=========================== - -.. currentmodule:: flask - -Extensions are extra packages that add functionality to a Flask -application. While `PyPI`_ contains many Flask extensions, you may not -find one that fits your need. If this is the case, you can create your -own, and publish it for others to use as well. - -This guide will show how to create a Flask extension, and some of the -common patterns and requirements involved. Since extensions can do -anything, this guide won't be able to cover every possibility. - -The best ways to learn about extensions are to look at how other -extensions you use are written, and discuss with others. Discuss your -design ideas with others on our `Discord Chat`_ or -`GitHub Discussions`_. - -The best extensions share common patterns, so that anyone familiar with -using one extension won't feel completely lost with another. This can -only work if collaboration happens early. - - -Naming ------- - -A Flask extension typically has ``flask`` in its name as a prefix or -suffix. If it wraps another library, it should include the library name -as well. This makes it easy to search for extensions, and makes their -purpose clearer. - -A general Python packaging recommendation is that the install name from -the package index and the name used in ``import`` statements should be -related. The import name is lowercase, with words separated by -underscores (``_``). The install name is either lower case or title -case, with words separated by dashes (``-``). If it wraps another -library, prefer using the same case as that library's name. - -Here are some example install and import names: - -- ``Flask-Name`` imported as ``flask_name`` -- ``flask-name-lower`` imported as ``flask_name_lower`` -- ``Flask-ComboName`` imported as ``flask_comboname`` -- ``Name-Flask`` imported as ``name_flask`` - - -The Extension Class and Initialization --------------------------------------- - -All extensions will need some entry point that initializes the -extension with the application. The most common pattern is to create a -class that represents the extension's configuration and behavior, with -an ``init_app`` method to apply the extension instance to the given -application instance. - -.. code-block:: python - - class HelloExtension: - def __init__(self, app=None): - if app is not None: - self.init_app(app) - - def init_app(self, app): - app.before_request(...) - -It is important that the app is not stored on the extension, don't do -``self.app = app``. The only time the extension should have direct -access to an app is during ``init_app``, otherwise it should use -:data:`current_app`. - -This allows the extension to support the application factory pattern, -avoids circular import issues when importing the extension instance -elsewhere in a user's code, and makes testing with different -configurations easier. - -.. code-block:: python - - hello = HelloExtension() - - def create_app(): - app = Flask(__name__) - hello.init_app(app) - return app - -Above, the ``hello`` extension instance exists independently of the -application. This means that other modules in a user's project can do -``from project import hello`` and use the extension in blueprints before -the app exists. - -The :attr:`Flask.extensions` dict can be used to store a reference to -the extension on the application, or some other state specific to the -application. Be aware that this is a single namespace, so use a name -unique to your extension, such as the extension's name without the -"flask" prefix. - - -Adding Behavior ---------------- - -There are many ways that an extension can add behavior. Any setup -methods that are available on the :class:`Flask` object can be used -during an extension's ``init_app`` method. - -A common pattern is to use :meth:`~Flask.before_request` to initialize -some data or a connection at the beginning of each request, then -:meth:`~Flask.teardown_request` to clean it up at the end. This can be -stored on :data:`g`, discussed more below. - -A more lazy approach is to provide a method that initializes and caches -the data or connection. For example, a ``ext.get_db`` method could -create a database connection the first time it's called, so that a view -that doesn't use the database doesn't create a connection. - -Besides doing something before and after every view, your extension -might want to add some specific views as well. In this case, you could -define a :class:`Blueprint`, then call :meth:`~Flask.register_blueprint` -during ``init_app`` to add the blueprint to the app. - - -Configuration Techniques ------------------------- - -There can be multiple levels and sources of configuration for an -extension. You should consider what parts of your extension fall into -each one. - -- Configuration per application instance, through ``app.config`` - values. This is configuration that could reasonably change for each - deployment of an application. A common example is a URL to an - external resource, such as a database. Configuration keys should - start with the extension's name so that they don't interfere with - other extensions. -- Configuration per extension instance, through ``__init__`` - arguments. This configuration usually affects how the extension - is used, such that it wouldn't make sense to change it per - deployment. -- Configuration per extension instance, through instance attributes - and decorator methods. It might be more ergonomic to assign to - ``ext.value``, or use a ``@ext.register`` decorator to register a - function, after the extension instance has been created. -- Global configuration through class attributes. Changing a class - attribute like ``Ext.connection_class`` can customize default - behavior without making a subclass. This could be combined - per-extension configuration to override defaults. -- Subclassing and overriding methods and attributes. Making the API of - the extension itself something that can be overridden provides a - very powerful tool for advanced customization. - -The :class:`~flask.Flask` object itself uses all of these techniques. - -It's up to you to decide what configuration is appropriate for your -extension, based on what you need and what you want to support. - -Configuration should not be changed after the application setup phase is -complete and the server begins handling requests. Configuration is -global, any changes to it are not guaranteed to be visible to other -workers. - - -Data During a Request ---------------------- - -When writing a Flask application, the :data:`~flask.g` object is used to -store information during a request. For example the -:doc:`tutorial ` stores a connection to a SQLite -database as ``g.db``. Extensions can also use this, with some care. -Since ``g`` is a single global namespace, extensions must use unique -names that won't collide with user data. For example, use the extension -name as a prefix, or as a namespace. - -.. code-block:: python - - # an internal prefix with the extension name - g._hello_user_id = 2 - - # or an internal prefix as a namespace - from types import SimpleNamespace - g._hello = SimpleNamespace() - g._hello.user_id = 2 - -The data in ``g`` lasts for an application context. An application -context is active when a request context is, or when a CLI command is -run. If you're storing something that should be closed, use -:meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed -when the application context ends. If it should only be valid during a -request, or would not be used in the CLI outside a request, use -:meth:`~flask.Flask.teardown_request`. - - -Views and Models ----------------- - -Your extension views might want to interact with specific models in your -database, or some other extension or data connected to your application. -For example, let's consider a ``Flask-SimpleBlog`` extension that works -with Flask-SQLAlchemy to provide a ``Post`` model and views to write -and read posts. - -The ``Post`` model needs to subclass the Flask-SQLAlchemy ``db.Model`` -object, but that's only available once you've created an instance of -that extension, not when your extension is defining its views. So how -can the view code, defined before the model exists, access the model? - -One method could be to use :doc:`views`. During ``__init__``, create -the model, then create the views by passing the model to the view -class's :meth:`~views.View.as_view` method. - -.. code-block:: python - - class PostAPI(MethodView): - def __init__(self, model): - self.model = model - - def get(self, id): - post = self.model.query.get(id) - return jsonify(post.to_json()) - - class BlogExtension: - def __init__(self, db): - class Post(db.Model): - id = db.Column(primary_key=True) - title = db.Column(db.String, nullable=False) - - self.post_model = Post - - def init_app(self, app): - api_view = PostAPI.as_view(model=self.post_model) - - db = SQLAlchemy() - blog = BlogExtension(db) - db.init_app(app) - blog.init_app(app) - -Another technique could be to use an attribute on the extension, such as -``self.post_model`` from above. Add the extension to ``app.extensions`` -in ``init_app``, then access -``current_app.extensions["simple_blog"].post_model`` from views. - -You may also want to provide base classes so that users can provide -their own ``Post`` model that conforms to the API your extension -expects. So they could implement ``class Post(blog.BasePost)``, then -set it as ``blog.post_model``. - -As you can see, this can get a bit complex. Unfortunately, there's no -perfect solution here, only different strategies and tradeoffs depending -on your needs and how much customization you want to offer. Luckily, -this sort of resource dependency is not a common need for most -extensions. Remember, if you need help with design, ask on our -`Discord Chat`_ or `GitHub Discussions`_. - - -Recommended Extension Guidelines --------------------------------- - -Flask previously had the concept of "approved extensions", where the -Flask maintainers evaluated the quality, support, and compatibility of -the extensions before listing them. While the list became too difficult -to maintain over time, the guidelines are still relevant to all -extensions maintained and developed today, as they help the Flask -ecosystem remain consistent and compatible. - -1. An extension requires a maintainer. In the event an extension author - would like to move beyond the project, the project should find a new - maintainer and transfer access to the repository, documentation, - PyPI, and any other services. The `Pallets-Eco`_ organization on - GitHub allows for community maintenance with oversight from the - Pallets maintainers. -2. The naming scheme is *Flask-ExtensionName* or *ExtensionName-Flask*. - It must provide exactly one package or module named - ``flask_extension_name``. -3. The extension must use an open source license. The Python web - ecosystem tends to prefer BSD or MIT. It must be open source and - publicly available. -4. The extension's API must have the following characteristics: - - - It must support multiple applications running in the same Python - process. Use ``current_app`` instead of ``self.app``, store - configuration and state per application instance. - - It must be possible to use the factory pattern for creating - applications. Use the ``ext.init_app()`` pattern. - -5. From a clone of the repository, an extension with its dependencies - must be installable in editable mode with ``pip install -e .``. -6. It must ship tests that can be invoked with a common tool like - ``tox -e py``, ``nox -s test`` or ``pytest``. If not using ``tox``, - the test dependencies should be specified in a requirements file. - The tests must be part of the sdist distribution. -7. A link to the documentation or project website must be in the PyPI - metadata or the readme. The documentation should use the Flask theme - from the `Official Pallets Themes`_. -8. The extension's dependencies should not use upper bounds or assume - any particular version scheme, but should use lower bounds to - indicate minimum compatibility support. For example, - ``sqlalchemy>=1.4``. -9. Indicate the versions of Python supported using ``python_requires=">=version"``. - Flask itself supports Python >=3.9 as of October 2024, and this will update - over time. - -.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask -.. _Discord Chat: https://discord.gg/pallets -.. _GitHub Discussions: https://github.com/pallets/flask/discussions -.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/ -.. _Pallets-Eco: https://github.com/pallets-eco diff --git a/docs/extensions.rst b/docs/extensions.rst deleted file mode 100644 index 4713ec8e..00000000 --- a/docs/extensions.rst +++ /dev/null @@ -1,48 +0,0 @@ -Extensions -========== - -Extensions are extra packages that add functionality to a Flask -application. For example, an extension might add support for sending -email or connecting to a database. Some extensions add entire new -frameworks to help build certain types of applications, like a REST API. - - -Finding Extensions ------------------- - -Flask extensions are usually named "Flask-Foo" or "Foo-Flask". You can -search PyPI for packages tagged with `Framework :: Flask `_. - - -Using Extensions ----------------- - -Consult each extension's documentation for installation, configuration, -and usage instructions. Generally, extensions pull their own -configuration from :attr:`app.config ` and are -passed an application instance during initialization. For example, -an extension called "Flask-Foo" might be used like this:: - - from flask_foo import Foo - - foo = Foo() - - app = Flask(__name__) - app.config.update( - FOO_BAR='baz', - FOO_SPAM='eggs', - ) - - foo.init_app(app) - - -Building Extensions -------------------- - -While `PyPI `_ contains many Flask extensions, you may not find -an extension that fits your need. If this is the case, you can create -your own, and publish it for others to use as well. Read -:doc:`extensiondev` to develop your own Flask extension. - - -.. _pypi: https://pypi.org/search/?c=Framework+%3A%3A+Flask diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 90a314bf..00000000 --- a/docs/installation.rst +++ /dev/null @@ -1,144 +0,0 @@ -Installation -============ - - -Python Version --------------- - -We recommend using the latest version of Python. Flask supports Python 3.9 and newer. - - -Dependencies ------------- - -These distributions will be installed automatically when installing Flask. - -* `Werkzeug`_ implements WSGI, the standard Python interface between - applications and servers. -* `Jinja`_ is a template language that renders the pages your application - serves. -* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering - templates to avoid injection attacks. -* `ItsDangerous`_ securely signs data to ensure its integrity. This is used - to protect Flask's session cookie. -* `Click`_ is a framework for writing command line applications. It provides - the ``flask`` command and allows adding custom management commands. -* `Blinker`_ provides support for :doc:`signals`. - -.. _Werkzeug: https://palletsprojects.com/p/werkzeug/ -.. _Jinja: https://palletsprojects.com/p/jinja/ -.. _MarkupSafe: https://palletsprojects.com/p/markupsafe/ -.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ -.. _Click: https://palletsprojects.com/p/click/ -.. _Blinker: https://blinker.readthedocs.io/ - - -Optional dependencies -~~~~~~~~~~~~~~~~~~~~~ - -These distributions will not be installed automatically. Flask will detect and -use them if you install them. - -* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask`` - commands. -* `Watchdog`_ provides a faster, more efficient reloader for the development - server. - -.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme -.. _watchdog: https://pythonhosted.org/watchdog/ - - -greenlet -~~~~~~~~ - -You may choose to use gevent or eventlet with your application. In this -case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is -required. - -These are not minimum supported versions, they only indicate the first -versions that added necessary features. You should use the latest -versions of each. - - -Virtual environments --------------------- - -Use a virtual environment to manage the dependencies for your project, both in -development and in production. - -What problem does a virtual environment solve? The more Python projects you -have, the more likely it is that you need to work with different versions of -Python libraries, or even Python itself. Newer versions of libraries for one -project can break compatibility in another project. - -Virtual environments are independent groups of Python libraries, one for each -project. Packages installed for one project will not affect other projects or -the operating system's packages. - -Python comes bundled with the :mod:`venv` module to create virtual -environments. - - -.. _install-create-env: - -Create an environment -~~~~~~~~~~~~~~~~~~~~~ - -Create a project folder and a :file:`.venv` folder within: - -.. tabs:: - - .. group-tab:: macOS/Linux - - .. code-block:: text - - $ mkdir myproject - $ cd myproject - $ python3 -m venv .venv - - .. group-tab:: Windows - - .. code-block:: text - - > mkdir myproject - > cd myproject - > py -3 -m venv .venv - - -.. _install-activate-env: - -Activate the environment -~~~~~~~~~~~~~~~~~~~~~~~~ - -Before you work on your project, activate the corresponding environment: - -.. tabs:: - - .. group-tab:: macOS/Linux - - .. code-block:: text - - $ . .venv/bin/activate - - .. group-tab:: Windows - - .. code-block:: text - - > .venv\Scripts\activate - -Your shell prompt will change to show the name of the activated -environment. - - -Install Flask -------------- - -Within the activated environment, use the following command to install -Flask: - -.. code-block:: sh - - $ pip install Flask - -Flask is now installed. Check out the :doc:`/quickstart` or go to the -:doc:`Documentation Overview `. diff --git a/docs/license.rst b/docs/license.rst deleted file mode 100644 index 2a445f9c..00000000 --- a/docs/license.rst +++ /dev/null @@ -1,5 +0,0 @@ -BSD-3-Clause License -==================== - -.. literalinclude:: ../LICENSE.txt - :language: text diff --git a/docs/lifecycle.rst b/docs/lifecycle.rst deleted file mode 100644 index 2344d98a..00000000 --- a/docs/lifecycle.rst +++ /dev/null @@ -1,168 +0,0 @@ -Application Structure and Lifecycle -=================================== - -Flask makes it pretty easy to write a web application. But there are quite a few -different parts to an application and to each request it handles. Knowing what happens -during application setup, serving, and handling requests will help you know what's -possible in Flask and how to structure your application. - - -Application Setup ------------------ - -The first step in creating a Flask application is creating the application object. Each -Flask application is an instance of the :class:`.Flask` class, which collects all -configuration, extensions, and views. - -.. code-block:: python - - from flask import Flask - - app = Flask(__name__) - app.config.from_mapping( - SECRET_KEY="dev", - ) - app.config.from_prefixed_env() - - @app.route("/") - def index(): - return "Hello, World!" - -This is known as the "application setup phase", it's the code you write that's outside -any view functions or other handlers. It can be split up between different modules and -sub-packages, but all code that you want to be part of your application must be imported -in order for it to be registered. - -All application setup must be completed before you start serving your application and -handling requests. This is because WSGI servers divide work between multiple workers, or -can be distributed across multiple machines. If the configuration changed in one worker, -there's no way for Flask to ensure consistency between other workers. - -Flask tries to help developers catch some of these setup ordering issues by showing an -error if setup-related methods are called after requests are handled. In that case -you'll see this error: - - The setup method 'route' can no longer be called on the application. It has already - handled its first request, any changes will not be applied consistently. - Make sure all imports, decorators, functions, etc. needed to set up the application - are done before running it. - -However, it is not possible for Flask to detect all cases of out-of-order setup. In -general, don't do anything to modify the ``Flask`` app object and ``Blueprint`` objects -from within view functions that run during requests. This includes: - -- Adding routes, view functions, and other request handlers with ``@app.route``, - ``@app.errorhandler``, ``@app.before_request``, etc. -- Registering blueprints. -- Loading configuration with ``app.config``. -- Setting up the Jinja template environment with ``app.jinja_env``. -- Setting a session interface, instead of the default itsdangerous cookie. -- Setting a JSON provider with ``app.json``, instead of the default provider. -- Creating and initializing Flask extensions. - - -Serving the Application ------------------------ - -Flask is a WSGI application framework. The other half of WSGI is the WSGI server. During -development, Flask, through Werkzeug, provides a development WSGI server with the -``flask run`` CLI command. When you are done with development, use a production server -to serve your application, see :doc:`deploying/index`. - -Regardless of what server you're using, it will follow the :pep:`3333` WSGI spec. The -WSGI server will be told how to access your Flask application object, which is the WSGI -application. Then it will start listening for HTTP requests, translate the request data -into a WSGI environ, and call the WSGI application with that data. The WSGI application -will return data that is translated into an HTTP response. - -#. Browser or other client makes HTTP request. -#. WSGI server receives request. -#. WSGI server converts HTTP data to WSGI ``environ`` dict. -#. WSGI server calls WSGI application with the ``environ``. -#. Flask, the WSGI application, does all its internal processing to route the request - to a view function, handle errors, etc. -#. Flask translates View function return into WSGI response data, passes it to WSGI - server. -#. WSGI server creates and send an HTTP response. -#. Client receives the HTTP response. - - -Middleware -~~~~~~~~~~ - -The WSGI application above is a callable that behaves in a certain way. Middleware -is a WSGI application that wraps another WSGI application. It's a similar concept to -Python decorators. The outermost middleware will be called by the server. It can modify -the data passed to it, then call the WSGI application (or further middleware) that it -wraps, and so on. And it can take the return value of that call and modify it further. - -From the WSGI server's perspective, there is one WSGI application, the one it calls -directly. Typically, Flask is the "real" application at the end of the chain of -middleware. But even Flask can call further WSGI applications, although that's an -advanced, uncommon use case. - -A common middleware you'll see used with Flask is Werkzeug's -:class:`~werkzeug.middleware.proxy_fix.ProxyFix`, which modifies the request to look -like it came directly from a client even if it passed through HTTP proxies on the way. -There are other middleware that can handle serving static files, authentication, etc. - - -How a Request is Handled ------------------------- - -For us, the interesting part of the steps above is when Flask gets called by the WSGI -server (or middleware). At that point, it will do quite a lot to handle the request and -generate the response. At the most basic, it will match the URL to a view function, call -the view function, and pass the return value back to the server. But there are many more -parts that you can use to customize its behavior. - -#. WSGI server calls the Flask object, which calls :meth:`.Flask.wsgi_app`. -#. A :class:`.RequestContext` object is created. This converts the WSGI ``environ`` - dict into a :class:`.Request` object. It also creates an :class:`AppContext` object. -#. The :doc:`app context ` is pushed, which makes :data:`.current_app` and - :data:`.g` available. -#. The :data:`.appcontext_pushed` signal is sent. -#. The :doc:`request context ` is pushed, which makes :attr:`.request` and - :class:`.session` available. -#. The session is opened, loading any existing session data using the app's - :attr:`~.Flask.session_interface`, an instance of :class:`.SessionInterface`. -#. The URL is matched against the URL rules registered with the :meth:`~.Flask.route` - decorator during application setup. If there is no match, the error - usually a 404, - 405, or redirect - is stored to be handled later. -#. The :data:`.request_started` signal is sent. -#. Any :meth:`~.Flask.url_value_preprocessor` decorated functions are called. -#. Any :meth:`~.Flask.before_request` decorated functions are called. If any of - these function returns a value it is treated as the response immediately. -#. If the URL didn't match a route a few steps ago, that error is raised now. -#. The :meth:`~.Flask.route` decorated view function associated with the matched URL - is called and returns a value to be used as the response. -#. If any step so far raised an exception, and there is an :meth:`~.Flask.errorhandler` - decorated function that matches the exception class or HTTP error code, it is - called to handle the error and return a response. -#. Whatever returned a response value - a before request function, the view, or an - error handler, that value is converted to a :class:`.Response` object. -#. Any :func:`~.after_this_request` decorated functions are called, then cleared. -#. Any :meth:`~.Flask.after_request` decorated functions are called, which can modify - the response object. -#. The session is saved, persisting any modified session data using the app's - :attr:`~.Flask.session_interface`. -#. The :data:`.request_finished` signal is sent. -#. If any step so far raised an exception, and it was not handled by an error handler - function, it is handled now. HTTP exceptions are treated as responses with their - corresponding status code, other exceptions are converted to a generic 500 response. - The :data:`.got_request_exception` signal is sent. -#. The response object's status, headers, and body are returned to the WSGI server. -#. Any :meth:`~.Flask.teardown_request` decorated functions are called. -#. The :data:`.request_tearing_down` signal is sent. -#. The request context is popped, :attr:`.request` and :class:`.session` are no longer - available. -#. Any :meth:`~.Flask.teardown_appcontext` decorated functions are called. -#. The :data:`.appcontext_tearing_down` signal is sent. -#. The app context is popped, :data:`.current_app` and :data:`.g` are no longer - available. -#. The :data:`.appcontext_popped` signal is sent. - -There are even more decorators and customization points than this, but that aren't part -of every request lifecycle. They're more specific to certain things you might use during -a request, such as templates, building URLs, or handling JSON data. See the rest of this -documentation, as well as the :doc:`api` to explore further. diff --git a/docs/logging.rst b/docs/logging.rst deleted file mode 100644 index 39588242..00000000 --- a/docs/logging.rst +++ /dev/null @@ -1,183 +0,0 @@ -Logging -======= - -Flask uses standard Python :mod:`logging`. Messages about your Flask -application are logged with :meth:`app.logger `, -which takes the same name as :attr:`app.name `. This -logger can also be used to log your own messages. - -.. code-block:: python - - @app.route('/login', methods=['POST']) - def login(): - user = get_user(request.form['username']) - - if user.check_password(request.form['password']): - login_user(user) - app.logger.info('%s logged in successfully', user.username) - return redirect(url_for('index')) - else: - app.logger.info('%s failed to log in', user.username) - abort(401) - -If you don't configure logging, Python's default log level is usually -'warning'. Nothing below the configured level will be visible. - - -Basic Configuration -------------------- - -When you want to configure logging for your project, you should do it as soon -as possible when the program starts. If :meth:`app.logger ` -is accessed before logging is configured, it will add a default handler. If -possible, configure logging before creating the application object. - -This example uses :func:`~logging.config.dictConfig` to create a logging -configuration similar to Flask's default, except for all logs:: - - from logging.config import dictConfig - - dictConfig({ - 'version': 1, - 'formatters': {'default': { - 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', - }}, - 'handlers': {'wsgi': { - 'class': 'logging.StreamHandler', - 'stream': 'ext://flask.logging.wsgi_errors_stream', - 'formatter': 'default' - }}, - 'root': { - 'level': 'INFO', - 'handlers': ['wsgi'] - } - }) - - app = Flask(__name__) - - -Default Configuration -````````````````````` - -If you do not configure logging yourself, Flask will add a -:class:`~logging.StreamHandler` to :meth:`app.logger ` -automatically. During requests, it will write to the stream specified by the -WSGI server in ``environ['wsgi.errors']`` (which is usually -:data:`sys.stderr`). Outside a request, it will log to :data:`sys.stderr`. - - -Removing the Default Handler -```````````````````````````` - -If you configured logging after accessing -:meth:`app.logger `, and need to remove the default -handler, you can import and remove it:: - - from flask.logging import default_handler - - app.logger.removeHandler(default_handler) - - -Email Errors to Admins ----------------------- - -When running the application on a remote server for production, you probably -won't be looking at the log messages very often. The WSGI server will probably -send log messages to a file, and you'll only check that file if a user tells -you something went wrong. - -To be proactive about discovering and fixing bugs, you can configure a -:class:`logging.handlers.SMTPHandler` to send an email when errors and higher -are logged. :: - - import logging - from logging.handlers import SMTPHandler - - mail_handler = SMTPHandler( - mailhost='127.0.0.1', - fromaddr='server-error@example.com', - toaddrs=['admin@example.com'], - subject='Application Error' - ) - mail_handler.setLevel(logging.ERROR) - mail_handler.setFormatter(logging.Formatter( - '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' - )) - - if not app.debug: - app.logger.addHandler(mail_handler) - -This requires that you have an SMTP server set up on the same server. See the -Python docs for more information about configuring the handler. - - -Injecting Request Information ------------------------------ - -Seeing more information about the request, such as the IP address, may help -debugging some errors. You can subclass :class:`logging.Formatter` to inject -your own fields that can be used in messages. You can change the formatter for -Flask's default handler, the mail handler defined above, or any other -handler. :: - - from flask import has_request_context, request - from flask.logging import default_handler - - class RequestFormatter(logging.Formatter): - def format(self, record): - if has_request_context(): - record.url = request.url - record.remote_addr = request.remote_addr - else: - record.url = None - record.remote_addr = None - - return super().format(record) - - formatter = RequestFormatter( - '[%(asctime)s] %(remote_addr)s requested %(url)s\n' - '%(levelname)s in %(module)s: %(message)s' - ) - default_handler.setFormatter(formatter) - mail_handler.setFormatter(formatter) - - -Other Libraries ---------------- - -Other libraries may use logging extensively, and you want to see relevant -messages from those logs too. The simplest way to do this is to add handlers -to the root logger instead of only the app logger. :: - - from flask.logging import default_handler - - root = logging.getLogger() - root.addHandler(default_handler) - root.addHandler(mail_handler) - -Depending on your project, it may be more useful to configure each logger you -care about separately, instead of configuring only the root logger. :: - - for logger in ( - logging.getLogger(app.name), - logging.getLogger('sqlalchemy'), - logging.getLogger('other_package'), - ): - logger.addHandler(default_handler) - logger.addHandler(mail_handler) - - -Werkzeug -```````` - -Werkzeug logs basic request/response information to the ``'werkzeug'`` logger. -If the root logger has no handlers configured, Werkzeug adds a -:class:`~logging.StreamHandler` to its logger. - - -Flask Extensions -```````````````` - -Depending on the situation, an extension may choose to log to -:meth:`app.logger ` or its own named logger. Consult each -extension's documentation for details. diff --git a/docs/make.bat b/docs/make.bat index 922152e9..747ffb7b 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,35 +1,35 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst deleted file mode 100644 index f22c8060..00000000 --- a/docs/patterns/appdispatch.rst +++ /dev/null @@ -1,189 +0,0 @@ -Application Dispatching -======================= - -Application dispatching is the process of combining multiple Flask -applications on the WSGI level. You can combine not only Flask -applications but any WSGI application. This would allow you to run a -Django and a Flask application in the same interpreter side by side if -you want. The usefulness of this depends on how the applications work -internally. - -The fundamental difference from :doc:`packages` is that in this case you -are running the same or different Flask applications that are entirely -isolated from each other. They run different configurations and are -dispatched on the WSGI level. - - -Working with this Document --------------------------- - -Each of the techniques and examples below results in an ``application`` -object that can be run with any WSGI server. For development, use the -``flask run`` command to start a development server. For production, see -:doc:`/deploying/index`. - -.. code-block:: python - - from flask import Flask - - app = Flask(__name__) - - @app.route('/') - def hello_world(): - return 'Hello World!' - - -Combining Applications ----------------------- - -If you have entirely separated applications and you want them to work next -to each other in the same Python interpreter process you can take -advantage of the :class:`werkzeug.wsgi.DispatcherMiddleware`. The idea -here is that each Flask application is a valid WSGI application and they -are combined by the dispatcher middleware into a larger one that is -dispatched based on prefix. - -For example you could have your main application run on ``/`` and your -backend interface on ``/backend``. - -.. code-block:: python - - from werkzeug.middleware.dispatcher import DispatcherMiddleware - from frontend_app import application as frontend - from backend_app import application as backend - - application = DispatcherMiddleware(frontend, { - '/backend': backend - }) - - -Dispatch by Subdomain ---------------------- - -Sometimes you might want to use multiple instances of the same application -with different configurations. Assuming the application is created inside -a function and you can call that function to instantiate it, that is -really easy to implement. In order to develop your application to support -creating new instances in functions have a look at the -:doc:`appfactories` pattern. - -A very common example would be creating applications per subdomain. For -instance you configure your webserver to dispatch all requests for all -subdomains to your application and you then use the subdomain information -to create user-specific instances. Once you have your server set up to -listen on all subdomains you can use a very simple WSGI application to do -the dynamic application creation. - -The perfect level for abstraction in that regard is the WSGI layer. You -write your own WSGI application that looks at the request that comes and -delegates it to your Flask application. If that application does not -exist yet, it is dynamically created and remembered. - -.. code-block:: python - - from threading import Lock - - class SubdomainDispatcher: - - def __init__(self, domain, create_app): - self.domain = domain - self.create_app = create_app - self.lock = Lock() - self.instances = {} - - def get_application(self, host): - host = host.split(':')[0] - assert host.endswith(self.domain), 'Configuration error' - subdomain = host[:-len(self.domain)].rstrip('.') - with self.lock: - app = self.instances.get(subdomain) - if app is None: - app = self.create_app(subdomain) - self.instances[subdomain] = app - return app - - def __call__(self, environ, start_response): - app = self.get_application(environ['HTTP_HOST']) - return app(environ, start_response) - - -This dispatcher can then be used like this: - -.. code-block:: python - - from myapplication import create_app, get_user_for_subdomain - from werkzeug.exceptions import NotFound - - def make_app(subdomain): - user = get_user_for_subdomain(subdomain) - if user is None: - # if there is no user for that subdomain we still have - # to return a WSGI application that handles that request. - # We can then just return the NotFound() exception as - # application which will render a default 404 page. - # You might also redirect the user to the main page then - return NotFound() - - # otherwise create the application for the specific user - return create_app(user) - - application = SubdomainDispatcher('example.com', make_app) - - -Dispatch by Path ----------------- - -Dispatching by a path on the URL is very similar. Instead of looking at -the ``Host`` header to figure out the subdomain one simply looks at the -request path up to the first slash. - -.. code-block:: python - - from threading import Lock - from wsgiref.util import shift_path_info - - class PathDispatcher: - - def __init__(self, default_app, create_app): - self.default_app = default_app - self.create_app = create_app - self.lock = Lock() - self.instances = {} - - def get_application(self, prefix): - with self.lock: - app = self.instances.get(prefix) - if app is None: - app = self.create_app(prefix) - if app is not None: - self.instances[prefix] = app - return app - - def __call__(self, environ, start_response): - app = self.get_application(_peek_path_info(environ)) - if app is not None: - shift_path_info(environ) - else: - app = self.default_app - return app(environ, start_response) - - def _peek_path_info(environ): - segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1) - if segments: - return segments[0] - - return None - -The big difference between this and the subdomain one is that this one -falls back to another application if the creator function returns ``None``. - -.. code-block:: python - - from myapplication import create_app, default_app, get_user_for_prefix - - def make_app(prefix): - user = get_user_for_prefix(prefix) - if user is not None: - return create_app(user) - - application = PathDispatcher(default_app, make_app) diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst deleted file mode 100644 index 0f248783..00000000 --- a/docs/patterns/appfactories.rst +++ /dev/null @@ -1,118 +0,0 @@ -Application Factories -===================== - -If you are already using packages and blueprints for your application -(:doc:`/blueprints`) there are a couple of really nice ways to further improve -the experience. A common pattern is creating the application object when -the blueprint is imported. But if you move the creation of this object -into a function, you can then create multiple instances of this app later. - -So why would you want to do this? - -1. Testing. You can have instances of the application with different - settings to test every case. -2. Multiple instances. Imagine you want to run different versions of the - same application. Of course you could have multiple instances with - different configs set up in your webserver, but if you use factories, - you can have multiple instances of the same application running in the - same application process which can be handy. - -So how would you then actually implement that? - -Basic Factories ---------------- - -The idea is to set up the application in a function. Like this:: - - def create_app(config_filename): - app = Flask(__name__) - app.config.from_pyfile(config_filename) - - from yourapplication.model import db - db.init_app(app) - - from yourapplication.views.admin import admin - from yourapplication.views.frontend import frontend - app.register_blueprint(admin) - app.register_blueprint(frontend) - - return app - -The downside is that you cannot use the application object in the blueprints -at import time. You can however use it from within a request. How do you -get access to the application with the config? Use -:data:`~flask.current_app`:: - - from flask import current_app, Blueprint, render_template - admin = Blueprint('admin', __name__, url_prefix='/admin') - - @admin.route('/') - def index(): - return render_template(current_app.config['INDEX_TEMPLATE']) - -Here we look up the name of a template in the config. - -Factories & Extensions ----------------------- - -It's preferable to create your extensions and app factories so that the -extension object does not initially get bound to the application. - -Using `Flask-SQLAlchemy `_, -as an example, you should not do something along those lines:: - - def create_app(config_filename): - app = Flask(__name__) - app.config.from_pyfile(config_filename) - - db = SQLAlchemy(app) - -But, rather, in model.py (or equivalent):: - - db = SQLAlchemy() - -and in your application.py (or equivalent):: - - def create_app(config_filename): - app = Flask(__name__) - app.config.from_pyfile(config_filename) - - from yourapplication.model import db - db.init_app(app) - -Using this design pattern, no application-specific state is stored on the -extension object, so one extension object can be used for multiple apps. -For more information about the design of extensions refer to :doc:`/extensiondev`. - -Using Applications ------------------- - -To run such an application, you can use the :command:`flask` command: - -.. code-block:: text - - $ flask --app hello run - -Flask will automatically detect the factory if it is named -``create_app`` or ``make_app`` in ``hello``. You can also pass arguments -to the factory like this: - -.. code-block:: text - - $ flask --app 'hello:create_app(local_auth=True)' run - -Then the ``create_app`` factory in ``hello`` is called with the keyword -argument ``local_auth=True``. See :doc:`/cli` for more detail. - -Factory Improvements --------------------- - -The factory function above is not very clever, but you can improve it. -The following changes are straightforward to implement: - -1. Make it possible to pass in configuration values for unit tests so that - you don't have to create config files on the filesystem. -2. Call a function from a blueprint when the application is setting up so - that you have a place to modify attributes of the application (like - hooking in before/after request handlers etc.) -3. Add in WSGI middlewares when the application is being created if necessary. diff --git a/docs/patterns/caching.rst b/docs/patterns/caching.rst deleted file mode 100644 index 9bf7b72a..00000000 --- a/docs/patterns/caching.rst +++ /dev/null @@ -1,16 +0,0 @@ -Caching -======= - -When your application runs slow, throw some caches in. Well, at least -it's the easiest way to speed up things. What does a cache do? Say you -have a function that takes some time to complete but the results would -still be good enough if they were 5 minutes old. So then the idea is that -you actually put the result of that calculation into a cache for some -time. - -Flask itself does not provide caching for you, but `Flask-Caching`_, an -extension for Flask does. Flask-Caching supports various backends, and it is -even possible to develop your own caching backend. - - -.. _Flask-Caching: https://flask-caching.readthedocs.io/en/latest/ diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst deleted file mode 100644 index 2e9a43a7..00000000 --- a/docs/patterns/celery.rst +++ /dev/null @@ -1,242 +0,0 @@ -Background Tasks with Celery -============================ - -If your application has a long running task, such as processing some uploaded data or -sending email, you don't want to wait for it to finish during a request. Instead, use a -task queue to send the necessary data to another process that will run the task in the -background while the request returns immediately. - -`Celery`_ is a powerful task queue that can be used for simple background tasks as well -as complex multi-stage programs and schedules. This guide will show you how to configure -Celery using Flask. Read Celery's `First Steps with Celery`_ guide to learn how to use -Celery itself. - -.. _Celery: https://celery.readthedocs.io -.. _First Steps with Celery: https://celery.readthedocs.io/en/latest/getting-started/first-steps-with-celery.html - -The Flask repository contains `an example `_ -based on the information on this page, which also shows how to use JavaScript to submit -tasks and poll for progress and results. - - -Install -------- - -Install Celery from PyPI, for example using pip: - -.. code-block:: text - - $ pip install celery - - -Integrate Celery with Flask ---------------------------- - -You can use Celery without any integration with Flask, but it's convenient to configure -it through Flask's config, and to let tasks access the Flask application. - -Celery uses similar ideas to Flask, with a ``Celery`` app object that has configuration -and registers tasks. While creating a Flask app, use the following code to create and -configure a Celery app as well. - -.. code-block:: python - - from celery import Celery, Task - - def celery_init_app(app: Flask) -> Celery: - class FlaskTask(Task): - def __call__(self, *args: object, **kwargs: object) -> object: - with app.app_context(): - return self.run(*args, **kwargs) - - celery_app = Celery(app.name, task_cls=FlaskTask) - celery_app.config_from_object(app.config["CELERY"]) - celery_app.set_default() - app.extensions["celery"] = celery_app - return celery_app - -This creates and returns a ``Celery`` app object. Celery `configuration`_ is taken from -the ``CELERY`` key in the Flask configuration. The Celery app is set as the default, so -that it is seen during each request. The ``Task`` subclass automatically runs task -functions with a Flask app context active, so that services like your database -connections are available. - -.. _configuration: https://celery.readthedocs.io/en/stable/userguide/configuration.html - -Here's a basic ``example.py`` that configures Celery to use Redis for communication. We -enable a result backend, but ignore results by default. This allows us to store results -only for tasks where we care about the result. - -.. code-block:: python - - from flask import Flask - - app = Flask(__name__) - app.config.from_mapping( - CELERY=dict( - broker_url="redis://localhost", - result_backend="redis://localhost", - task_ignore_result=True, - ), - ) - celery_app = celery_init_app(app) - -Point the ``celery worker`` command at this and it will find the ``celery_app`` object. - -.. code-block:: text - - $ celery -A example worker --loglevel INFO - -You can also run the ``celery beat`` command to run tasks on a schedule. See Celery's -docs for more information about defining schedules. - -.. code-block:: text - - $ celery -A example beat --loglevel INFO - - -Application Factory -------------------- - -When using the Flask application factory pattern, call the ``celery_init_app`` function -inside the factory. It sets ``app.extensions["celery"]`` to the Celery app object, which -can be used to get the Celery app from the Flask app returned by the factory. - -.. code-block:: python - - def create_app() -> Flask: - app = Flask(__name__) - app.config.from_mapping( - CELERY=dict( - broker_url="redis://localhost", - result_backend="redis://localhost", - task_ignore_result=True, - ), - ) - app.config.from_prefixed_env() - celery_init_app(app) - return app - -To use ``celery`` commands, Celery needs an app object, but that's no longer directly -available. Create a ``make_celery.py`` file that calls the Flask app factory and gets -the Celery app from the returned Flask app. - -.. code-block:: python - - from example import create_app - - flask_app = create_app() - celery_app = flask_app.extensions["celery"] - -Point the ``celery`` command to this file. - -.. code-block:: text - - $ celery -A make_celery worker --loglevel INFO - $ celery -A make_celery beat --loglevel INFO - - -Defining Tasks --------------- - -Using ``@celery_app.task`` to decorate task functions requires access to the -``celery_app`` object, which won't be available when using the factory pattern. It also -means that the decorated tasks are tied to the specific Flask and Celery app instances, -which could be an issue during testing if you change configuration for a test. - -Instead, use Celery's ``@shared_task`` decorator. This creates task objects that will -access whatever the "current app" is, which is a similar concept to Flask's blueprints -and app context. This is why we called ``celery_app.set_default()`` above. - -Here's an example task that adds two numbers together and returns the result. - -.. code-block:: python - - from celery import shared_task - - @shared_task(ignore_result=False) - def add_together(a: int, b: int) -> int: - return a + b - -Earlier, we configured Celery to ignore task results by default. Since we want to know -the return value of this task, we set ``ignore_result=False``. On the other hand, a task -that didn't need a result, such as sending an email, wouldn't set this. - - -Calling Tasks -------------- - -The decorated function becomes a task object with methods to call it in the background. -The simplest way is to use the ``delay(*args, **kwargs)`` method. See Celery's docs for -more methods. - -A Celery worker must be running to run the task. Starting a worker is shown in the -previous sections. - -.. code-block:: python - - from flask import request - - @app.post("/add") - def start_add() -> dict[str, object]: - a = request.form.get("a", type=int) - b = request.form.get("b", type=int) - result = add_together.delay(a, b) - return {"result_id": result.id} - -The route doesn't get the task's result immediately. That would defeat the purpose by -blocking the response. Instead, we return the running task's result id, which we can use -later to get the result. - - -Getting Results ---------------- - -To fetch the result of the task we started above, we'll add another route that takes the -result id we returned before. We return whether the task is finished (ready), whether it -finished successfully, and what the return value (or error) was if it is finished. - -.. code-block:: python - - from celery.result import AsyncResult - - @app.get("/result/") - def task_result(id: str) -> dict[str, object]: - result = AsyncResult(id) - return { - "ready": result.ready(), - "successful": result.successful(), - "value": result.result if result.ready() else None, - } - -Now you can start the task using the first route, then poll for the result using the -second route. This keeps the Flask request workers from being blocked waiting for tasks -to finish. - -The Flask repository contains `an example `_ -using JavaScript to submit tasks and poll for progress and results. - - -Passing Data to Tasks ---------------------- - -The "add" task above took two integers as arguments. To pass arguments to tasks, Celery -has to serialize them to a format that it can pass to other processes. Therefore, -passing complex objects is not recommended. For example, it would be impossible to pass -a SQLAlchemy model object, since that object is probably not serializable and is tied to -the session that queried it. - -Pass the minimal amount of data necessary to fetch or recreate any complex data within -the task. Consider a task that will run when the logged in user asks for an archive of -their data. The Flask request knows the logged in user, and has the user object queried -from the database. It got that by querying the database for a given id, so the task can -do the same thing. Pass the user's id rather than the user object. - -.. code-block:: python - - @shared_task - def generate_user_archive(user_id: str) -> None: - user = db.session.get(User, user_id) - ... - - generate_user_archive.delay(current_user.id) diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst deleted file mode 100644 index 4ff8814b..00000000 --- a/docs/patterns/deferredcallbacks.rst +++ /dev/null @@ -1,44 +0,0 @@ -Deferred Request Callbacks -========================== - -One of the design principles of Flask is that response objects are created and -passed down a chain of potential callbacks that can modify them or replace -them. When the request handling starts, there is no response object yet. It is -created as necessary either by a view function or by some other component in -the system. - -What happens if you want to modify the response at a point where the response -does not exist yet? A common example for that would be a -:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the -response object. - -One way is to avoid the situation. Very often that is possible. For instance -you can try to move that logic into a :meth:`~flask.Flask.after_request` -callback instead. However, sometimes moving code there makes it -more complicated or awkward to reason about. - -As an alternative, you can use :func:`~flask.after_this_request` to register -callbacks that will execute after only the current request. This way you can -defer code execution from anywhere in the application, based on the current -request. - -At any time during a request, we can register a function to be called at the -end of the request. For example you can remember the current language of the -user in a cookie in a :meth:`~flask.Flask.before_request` callback:: - - from flask import request, after_this_request - - @app.before_request - def detect_user_language(): - language = request.cookies.get('user_lang') - - if language is None: - language = guess_language_from_request() - - # when the response exists, set a cookie with the language - @after_this_request - def remember_language(response): - response.set_cookie('user_lang', language) - return response - - g.language = language diff --git a/docs/patterns/favicon.rst b/docs/patterns/favicon.rst deleted file mode 100644 index b867854f..00000000 --- a/docs/patterns/favicon.rst +++ /dev/null @@ -1,56 +0,0 @@ -Adding a favicon -================ - -A "favicon" is an icon used by browsers for tabs and bookmarks. This helps -to distinguish your website and to give it a unique brand. - -A common question is how to add a favicon to a Flask application. First, of -course, you need an icon. It should be 16 × 16 pixels and in the ICO file -format. This is not a requirement but a de-facto standard supported by all -relevant browsers. Put the icon in your static directory as -:file:`favicon.ico`. - -Now, to get browsers to find your icon, the correct way is to add a link -tag in your HTML. So, for example: - -.. sourcecode:: html+jinja - - - -That's all you need for most browsers, however some really old ones do not -support this standard. The old de-facto standard is to serve this file, -with this name, at the website root. If your application is not mounted at -the root path of the domain you either need to configure the web server to -serve the icon at the root or if you can't do that you're out of luck. If -however your application is the root you can simply route a redirect:: - - app.add_url_rule( - "/favicon.ico", - endpoint="favicon", - redirect_to=url_for("static", filename="favicon.ico"), - ) - -If you want to save the extra redirect request you can also write a view -using :func:`~flask.send_from_directory`:: - - import os - from flask import send_from_directory - - @app.route('/favicon.ico') - def favicon(): - return send_from_directory(os.path.join(app.root_path, 'static'), - 'favicon.ico', mimetype='image/vnd.microsoft.icon') - -We can leave out the explicit mimetype and it will be guessed, but we may -as well specify it to avoid the extra guessing, as it will always be the -same. - -The above will serve the icon via your application and if possible it's -better to configure your dedicated web server to serve it; refer to the -web server's documentation. - -See also --------- - -* The `Favicon `_ article on - Wikipedia diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst deleted file mode 100644 index 304f57dc..00000000 --- a/docs/patterns/fileuploads.rst +++ /dev/null @@ -1,182 +0,0 @@ -Uploading Files -=============== - -Ah yes, the good old problem of file uploads. The basic idea of file -uploads is actually quite simple. It basically works like this: - -1. A ``

`` tag is marked with ``enctype=multipart/form-data`` - and an ```` is placed in that form. -2. The application accesses the file from the :attr:`~flask.request.files` - dictionary on the request object. -3. use the :meth:`~werkzeug.datastructures.FileStorage.save` method of the file to save - the file permanently somewhere on the filesystem. - -A Gentle Introduction ---------------------- - -Let's start with a very basic application that uploads a file to a -specific upload folder and displays a file to the user. Let's look at the -bootstrapping code for our application:: - - import os - from flask import Flask, flash, request, redirect, url_for - from werkzeug.utils import secure_filename - - UPLOAD_FOLDER = '/path/to/the/uploads' - ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'} - - app = Flask(__name__) - app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER - -So first we need a couple of imports. Most should be straightforward, the -:func:`werkzeug.secure_filename` is explained a little bit later. The -``UPLOAD_FOLDER`` is where we will store the uploaded files and the -``ALLOWED_EXTENSIONS`` is the set of allowed file extensions. - -Why do we limit the extensions that are allowed? You probably don't want -your users to be able to upload everything there if the server is directly -sending out the data to the client. That way you can make sure that users -are not able to upload HTML files that would cause XSS problems (see -:ref:`security-xss`). Also make sure to disallow ``.php`` files if the server -executes them, but who has PHP installed on their server, right? :) - -Next the functions that check if an extension is valid and that uploads -the file and redirects the user to the URL for the uploaded file:: - - def allowed_file(filename): - return '.' in filename and \ - filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS - - @app.route('/', methods=['GET', 'POST']) - def upload_file(): - if request.method == 'POST': - # check if the post request has the file part - if 'file' not in request.files: - flash('No file part') - return redirect(request.url) - file = request.files['file'] - # If the user does not select a file, the browser submits an - # empty file without a filename. - if file.filename == '': - flash('No selected file') - return redirect(request.url) - if file and allowed_file(file.filename): - filename = secure_filename(file.filename) - file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) - return redirect(url_for('download_file', name=filename)) - return ''' - - Upload new File -

Upload new File

- - - -
- ''' - -So what does that :func:`~werkzeug.utils.secure_filename` function actually do? -Now the problem is that there is that principle called "never trust user -input". This is also true for the filename of an uploaded file. All -submitted form data can be forged, and filenames can be dangerous. For -the moment just remember: always use that function to secure a filename -before storing it directly on the filesystem. - -.. admonition:: Information for the Pros - - So you're interested in what that :func:`~werkzeug.utils.secure_filename` - function does and what the problem is if you're not using it? So just - imagine someone would send the following information as `filename` to - your application:: - - filename = "../../../../home/username/.bashrc" - - Assuming the number of ``../`` is correct and you would join this with - the ``UPLOAD_FOLDER`` the user might have the ability to modify a file on - the server's filesystem he or she should not modify. This does require some - knowledge about how the application looks like, but trust me, hackers - are patient :) - - Now let's look how that function works: - - >>> secure_filename('../../../../home/username/.bashrc') - 'home_username_.bashrc' - -We want to be able to serve the uploaded files so they can be downloaded -by users. We'll define a ``download_file`` view to serve files in the -upload folder by name. ``url_for("download_file", name=name)`` generates -download URLs. - -.. code-block:: python - - from flask import send_from_directory - - @app.route('/uploads/') - def download_file(name): - return send_from_directory(app.config["UPLOAD_FOLDER"], name) - -If you're using middleware or the HTTP server to serve files, you can -register the ``download_file`` endpoint as ``build_only`` so ``url_for`` -will work without a view function. - -.. code-block:: python - - app.add_url_rule( - "/uploads/", endpoint="download_file", build_only=True - ) - - -Improving Uploads ------------------ - -.. versionadded:: 0.6 - -So how exactly does Flask handle uploads? Well it will store them in the -webserver's memory if the files are reasonably small, otherwise in a -temporary location (as returned by :func:`tempfile.gettempdir`). But how -do you specify the maximum file size after which an upload is aborted? By -default Flask will happily accept file uploads with an unlimited amount of -memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH`` -config key:: - - from flask import Flask, Request - - app = Flask(__name__) - app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000 - -The code above will limit the maximum allowed payload to 16 megabytes. -If a larger file is transmitted, Flask will raise a -:exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception. - -.. admonition:: Connection Reset Issue - - When using the local development server, you may get a connection - reset error instead of a 413 response. You will get the correct - status response when running the app with a production WSGI server. - -This feature was added in Flask 0.6 but can be achieved in older versions -as well by subclassing the request object. For more information on that -consult the Werkzeug documentation on file handling. - - -Upload Progress Bars --------------------- - -A while ago many developers had the idea to read the incoming file in -small chunks and store the upload progress in the database to be able to -poll the progress with JavaScript from the client. The client asks the -server every 5 seconds how much it has transmitted, but this is -something it should already know. - -An Easier Solution ------------------- - -Now there are better solutions that work faster and are more reliable. There -are JavaScript libraries like jQuery_ that have form plugins to ease the -construction of progress bar. - -Because the common pattern for file uploads exists almost unchanged in all -applications dealing with uploads, there are also some Flask extensions that -implement a full fledged upload mechanism that allows controlling which -file extensions are allowed to be uploaded. - -.. _jQuery: https://jquery.com/ diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst deleted file mode 100644 index 8eb6b3ac..00000000 --- a/docs/patterns/flashing.rst +++ /dev/null @@ -1,148 +0,0 @@ -Message Flashing -================ - -Good applications and user interfaces are all about feedback. If the user -does not get enough feedback they will probably end up hating the -application. Flask provides a really simple way to give feedback to a -user with the flashing system. The flashing system basically makes it -possible to record a message at the end of a request and access it next -request and only next request. This is usually combined with a layout -template that does this. Note that browsers and sometimes web servers enforce -a limit on cookie sizes. This means that flashing messages that are too -large for session cookies causes message flashing to fail silently. - -Simple Flashing ---------------- - -So here is a full example:: - - from flask import Flask, flash, redirect, render_template, \ - request, url_for - - app = Flask(__name__) - app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' - - @app.route('/') - def index(): - return render_template('index.html') - - @app.route('/login', methods=['GET', 'POST']) - def login(): - error = None - if request.method == 'POST': - if request.form['username'] != 'admin' or \ - request.form['password'] != 'secret': - error = 'Invalid credentials' - else: - flash('You were successfully logged in') - return redirect(url_for('index')) - return render_template('login.html', error=error) - -And here is the :file:`layout.html` template which does the magic: - -.. sourcecode:: html+jinja - - - My Application - {% with messages = get_flashed_messages() %} - {% if messages %} -
    - {% for message in messages %} -
  • {{ message }}
  • - {% endfor %} -
- {% endif %} - {% endwith %} - {% block body %}{% endblock %} - -Here is the :file:`index.html` template which inherits from :file:`layout.html`: - -.. sourcecode:: html+jinja - - {% extends "layout.html" %} - {% block body %} -

Overview

-

Do you want to log in? - {% endblock %} - -And here is the :file:`login.html` template which also inherits from -:file:`layout.html`: - -.. sourcecode:: html+jinja - - {% extends "layout.html" %} - {% block body %} -

Login

- {% if error %} -

Error: {{ error }} - {% endif %} -

-
-
Username: -
-
Password: -
-
-

-

- {% endblock %} - -Flashing With Categories ------------------------- - -.. versionadded:: 0.3 - -It is also possible to provide categories when flashing a message. The -default category if nothing is provided is ``'message'``. Alternative -categories can be used to give the user better feedback. For example -error messages could be displayed with a red background. - -To flash a message with a different category, just use the second argument -to the :func:`~flask.flash` function:: - - flash('Invalid password provided', 'error') - -Inside the template you then have to tell the -:func:`~flask.get_flashed_messages` function to also return the -categories. The loop looks slightly different in that situation then: - -.. sourcecode:: html+jinja - - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
    - {% for category, message in messages %} -
  • {{ message }}
  • - {% endfor %} -
- {% endif %} - {% endwith %} - -This is just one example of how to render these flashed messages. One -might also use the category to add a prefix such as -``Error:`` to the message. - -Filtering Flash Messages ------------------------- - -.. versionadded:: 0.9 - -Optionally you can pass a list of categories which filters the results of -:func:`~flask.get_flashed_messages`. This is useful if you wish to -render each category in a separate block. - -.. sourcecode:: html+jinja - - {% with errors = get_flashed_messages(category_filter=["error"]) %} - {% if errors %} -
- × -
    - {%- for msg in errors %} -
  • {{ msg }}
  • - {% endfor -%} -
-
- {% endif %} - {% endwith %} diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst deleted file mode 100644 index 1f2c07dd..00000000 --- a/docs/patterns/index.rst +++ /dev/null @@ -1,40 +0,0 @@ -Patterns for Flask -================== - -Certain features and interactions are common enough that you will find -them in most web applications. For example, many applications use a -relational database and user authentication. They will open a database -connection at the beginning of the request and get the information for -the logged in user. At the end of the request, the database connection -is closed. - -These types of patterns may be a bit outside the scope of Flask itself, -but Flask makes it easy to implement them. Some common patterns are -collected in the following pages. - -.. toctree:: - :maxdepth: 2 - - packages - appfactories - appdispatch - urlprocessors - sqlite3 - sqlalchemy - fileuploads - caching - viewdecorators - wtforms - templateinheritance - flashing - javascript - lazyloading - mongoengine - favicon - streaming - deferredcallbacks - methodoverrides - requestchecksum - celery - subclassing - singlepageapplications diff --git a/docs/patterns/javascript.rst b/docs/patterns/javascript.rst deleted file mode 100644 index d58a3eb6..00000000 --- a/docs/patterns/javascript.rst +++ /dev/null @@ -1,259 +0,0 @@ -JavaScript, ``fetch``, and JSON -=============================== - -You may want to make your HTML page dynamic, by changing data without -reloading the entire page. Instead of submitting an HTML ``
`` and -performing a redirect to re-render the template, you can add -`JavaScript`_ that calls |fetch|_ and replaces content on the page. - -|fetch|_ is the modern, built-in JavaScript solution to making -requests from a page. You may have heard of other "AJAX" methods and -libraries, such as |XHR|_ or `jQuery`_. These are no longer needed in -modern browsers, although you may choose to use them or another library -depending on your application's requirements. These docs will only focus -on built-in JavaScript features. - -.. _JavaScript: https://developer.mozilla.org/Web/JavaScript -.. |fetch| replace:: ``fetch()`` -.. _fetch: https://developer.mozilla.org/Web/API/Fetch_API -.. |XHR| replace:: ``XMLHttpRequest()`` -.. _XHR: https://developer.mozilla.org/Web/API/XMLHttpRequest -.. _jQuery: https://jquery.com/ - - -Rendering Templates -------------------- - -It is important to understand the difference between templates and -JavaScript. Templates are rendered on the server, before the response is -sent to the user's browser. JavaScript runs in the user's browser, after -the template is rendered and sent. Therefore, it is impossible to use -JavaScript to affect how the Jinja template is rendered, but it is -possible to render data into the JavaScript that will run. - -To provide data to JavaScript when rendering the template, use the -:func:`~jinja-filters.tojson` filter in a `` - -A less common pattern is to add the data to a ``data-`` attribute on an -HTML tag. In this case, you must use single quotes around the value, not -double quotes, otherwise you will produce invalid or unsafe HTML. - -.. code-block:: jinja - -
- - -Generating URLs ---------------- - -The other way to get data from the server to JavaScript is to make a -request for it. First, you need to know the URL to request. - -The simplest way to generate URLs is to continue to use -:func:`~flask.url_for` when rendering the template. For example: - -.. code-block:: javascript - - const user_url = {{ url_for("user", id=current_user.id)|tojson }} - fetch(user_url).then(...) - -However, you might need to generate a URL based on information you only -know in JavaScript. As discussed above, JavaScript runs in the user's -browser, not as part of the template rendering, so you can't use -``url_for`` at that point. - -In this case, you need to know the "root URL" under which your -application is served. In simple setups, this is ``/``, but it might -also be something else, like ``https://example.com/myapp/``. - -A simple way to tell your JavaScript code about this root is to set it -as a global variable when rendering the template. Then you can use it -when generating URLs from JavaScript. - -.. code-block:: javascript - - const SCRIPT_ROOT = {{ request.script_root|tojson }} - let user_id = ... // do something to get a user id from the page - let user_url = `${SCRIPT_ROOT}/user/${user_id}` - fetch(user_url).then(...) - - -Making a Request with ``fetch`` -------------------------------- - -|fetch|_ takes two arguments, a URL and an object with other options, -and returns a |Promise|_. We won't cover all the available options, and -will only use ``then()`` on the promise, not other callbacks or -``await`` syntax. Read the linked MDN docs for more information about -those features. - -By default, the GET method is used. If the response contains JSON, it -can be used with a ``then()`` callback chain. - -.. code-block:: javascript - - const room_url = {{ url_for("room_detail", id=room.id)|tojson }} - fetch(room_url) - .then(response => response.json()) - .then(data => { - // data is a parsed JSON object - }) - -To send data, use a data method such as POST, and pass the ``body`` -option. The most common types for data are form data or JSON data. - -To send form data, pass a populated |FormData|_ object. This uses the -same format as an HTML form, and would be accessed with ``request.form`` -in a Flask view. - -.. code-block:: javascript - - let data = new FormData() - data.append("name", "Flask Room") - data.append("description", "Talk about Flask here.") - fetch(room_url, { - "method": "POST", - "body": data, - }).then(...) - -In general, prefer sending request data as form data, as would be used -when submitting an HTML form. JSON can represent more complex data, but -unless you need that it's better to stick with the simpler format. When -sending JSON data, the ``Content-Type: application/json`` header must be -sent as well, otherwise Flask will return a 400 error. - -.. code-block:: javascript - - let data = { - "name": "Flask Room", - "description": "Talk about Flask here.", - } - fetch(room_url, { - "method": "POST", - "headers": {"Content-Type": "application/json"}, - "body": JSON.stringify(data), - }).then(...) - -.. |Promise| replace:: ``Promise`` -.. _Promise: https://developer.mozilla.org/Web/JavaScript/Reference/Global_Objects/Promise -.. |FormData| replace:: ``FormData`` -.. _FormData: https://developer.mozilla.org/en-US/docs/Web/API/FormData - - -Following Redirects -------------------- - -A response might be a redirect, for example if you logged in with -JavaScript instead of a traditional HTML form, and your view returned -a redirect instead of JSON. JavaScript requests do follow redirects, but -they don't change the page. If you want to make the page change you can -inspect the response and apply the redirect manually. - -.. code-block:: javascript - - fetch("/login", {"body": ...}).then( - response => { - if (response.redirected) { - window.location = response.url - } else { - showLoginError() - } - } - ) - - -Replacing Content ------------------ - -A response might be new HTML, either a new section of the page to add or -replace, or an entirely new page. In general, if you're returning the -entire page, it would be better to handle that with a redirect as shown -in the previous section. The following example shows how to replace a -``
`` with the HTML returned by a request. - -.. code-block:: html - -
- {{ include "geology_fact.html" }} -
- - - -Return JSON from Views ----------------------- - -To return a JSON object from your API view, you can directly return a -dict from the view. It will be serialized to JSON automatically. - -.. code-block:: python - - @app.route("/user/") - def user_detail(id): - user = User.query.get_or_404(id) - return { - "username": User.username, - "email": User.email, - "picture": url_for("static", filename=f"users/{id}/profile.png"), - } - -If you want to return another JSON type, use the -:func:`~flask.json.jsonify` function, which creates a response object -with the given data serialized to JSON. - -.. code-block:: python - - from flask import jsonify - - @app.route("/users") - def user_list(): - users = User.query.order_by(User.name).all() - return jsonify([u.to_json() for u in users]) - -It is usually not a good idea to return file data in a JSON response. -JSON cannot represent binary data directly, so it must be base64 -encoded, which can be slow, takes more bandwidth to send, and is not as -easy to cache. Instead, serve files using one view, and generate a URL -to the desired file to include in the JSON. Then the client can make a -separate request to get the linked resource after getting the JSON. - - -Receiving JSON in Views ------------------------ - -Use the :attr:`~flask.Request.json` property of the -:data:`~flask.request` object to decode the request's body as JSON. If -the body is not valid JSON, or the ``Content-Type`` header is not set to -``application/json``, a 400 Bad Request error will be raised. - -.. code-block:: python - - from flask import request - - @app.post("/user/") - def user_update(id): - user = User.query.get_or_404(id) - user.update_from_json(request.json) - db.session.commit() - return user.to_json() diff --git a/docs/patterns/jquery.rst b/docs/patterns/jquery.rst deleted file mode 100644 index 7ac6856e..00000000 --- a/docs/patterns/jquery.rst +++ /dev/null @@ -1,6 +0,0 @@ -:orphan: - -AJAX with jQuery -================ - -Obsolete, see :doc:`/patterns/javascript` instead. diff --git a/docs/patterns/lazyloading.rst b/docs/patterns/lazyloading.rst deleted file mode 100644 index 658a1cd4..00000000 --- a/docs/patterns/lazyloading.rst +++ /dev/null @@ -1,109 +0,0 @@ -Lazily Loading Views -==================== - -Flask is usually used with the decorators. Decorators are simple and you -have the URL right next to the function that is called for that specific -URL. However there is a downside to this approach: it means all your code -that uses decorators has to be imported upfront or Flask will never -actually find your function. - -This can be a problem if your application has to import quick. It might -have to do that on systems like Google's App Engine or other systems. So -if you suddenly notice that your application outgrows this approach you -can fall back to a centralized URL mapping. - -The system that enables having a central URL map is the -:meth:`~flask.Flask.add_url_rule` function. Instead of using decorators, -you have a file that sets up the application with all URLs. - -Converting to Centralized URL Map ---------------------------------- - -Imagine the current application looks somewhat like this:: - - from flask import Flask - app = Flask(__name__) - - @app.route('/') - def index(): - pass - - @app.route('/user/') - def user(username): - pass - -Then, with the centralized approach you would have one file with the views -(:file:`views.py`) but without any decorator:: - - def index(): - pass - - def user(username): - pass - -And then a file that sets up an application which maps the functions to -URLs:: - - from flask import Flask - from yourapplication import views - app = Flask(__name__) - app.add_url_rule('/', view_func=views.index) - app.add_url_rule('/user/', view_func=views.user) - -Loading Late ------------- - -So far we only split up the views and the routing, but the module is still -loaded upfront. The trick is to actually load the view function as needed. -This can be accomplished with a helper class that behaves just like a -function but internally imports the real function on first use:: - - from werkzeug.utils import import_string, cached_property - - class LazyView(object): - - def __init__(self, import_name): - self.__module__, self.__name__ = import_name.rsplit('.', 1) - self.import_name = import_name - - @cached_property - def view(self): - return import_string(self.import_name) - - def __call__(self, *args, **kwargs): - return self.view(*args, **kwargs) - -What's important here is is that `__module__` and `__name__` are properly -set. This is used by Flask internally to figure out how to name the -URL rules in case you don't provide a name for the rule yourself. - -Then you can define your central place to combine the views like this:: - - from flask import Flask - from yourapplication.helpers import LazyView - app = Flask(__name__) - app.add_url_rule('/', - view_func=LazyView('yourapplication.views.index')) - app.add_url_rule('/user/', - view_func=LazyView('yourapplication.views.user')) - -You can further optimize this in terms of amount of keystrokes needed to -write this by having a function that calls into -:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project -name and a dot, and by wrapping `view_func` in a `LazyView` as needed. :: - - def url(import_name, url_rules=[], **options): - view = LazyView(f"yourapplication.{import_name}") - for url_rule in url_rules: - app.add_url_rule(url_rule, view_func=view, **options) - - # add a single route to the index view - url('views.index', ['/']) - - # add two routes to a single function endpoint - url_rules = ['/user/','/user/'] - url('views.user', url_rules) - -One thing to keep in mind is that before and after request handlers have -to be in a file that is imported upfront to work properly on the first -request. The same goes for any kind of remaining decorator. diff --git a/docs/patterns/methodoverrides.rst b/docs/patterns/methodoverrides.rst deleted file mode 100644 index 45dbb87e..00000000 --- a/docs/patterns/methodoverrides.rst +++ /dev/null @@ -1,42 +0,0 @@ -Adding HTTP Method Overrides -============================ - -Some HTTP proxies do not support arbitrary HTTP methods or newer HTTP -methods (such as PATCH). In that case it's possible to "proxy" HTTP -methods through another HTTP method in total violation of the protocol. - -The way this works is by letting the client do an HTTP POST request and -set the ``X-HTTP-Method-Override`` header. Then the method is replaced -with the header value before being passed to Flask. - -This can be accomplished with an HTTP middleware:: - - class HTTPMethodOverrideMiddleware(object): - allowed_methods = frozenset([ - 'GET', - 'HEAD', - 'POST', - 'DELETE', - 'PUT', - 'PATCH', - 'OPTIONS' - ]) - bodyless_methods = frozenset(['GET', 'HEAD', 'OPTIONS', 'DELETE']) - - def __init__(self, app): - self.app = app - - def __call__(self, environ, start_response): - method = environ.get('HTTP_X_HTTP_METHOD_OVERRIDE', '').upper() - if method in self.allowed_methods: - environ['REQUEST_METHOD'] = method - if method in self.bodyless_methods: - environ['CONTENT_LENGTH'] = '0' - return self.app(environ, start_response) - -To use this with Flask, wrap the app object with the middleware:: - - from flask import Flask - - app = Flask(__name__) - app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app) diff --git a/docs/patterns/mongoengine.rst b/docs/patterns/mongoengine.rst deleted file mode 100644 index 624988e4..00000000 --- a/docs/patterns/mongoengine.rst +++ /dev/null @@ -1,103 +0,0 @@ -MongoDB with MongoEngine -======================== - -Using a document database like MongoDB is a common alternative to -relational SQL databases. This pattern shows how to use -`MongoEngine`_, a document mapper library, to integrate with MongoDB. - -A running MongoDB server and `Flask-MongoEngine`_ are required. :: - - pip install flask-mongoengine - -.. _MongoEngine: http://mongoengine.org -.. _Flask-MongoEngine: https://flask-mongoengine.readthedocs.io - - -Configuration -------------- - -Basic setup can be done by defining ``MONGODB_SETTINGS`` on -``app.config`` and creating a ``MongoEngine`` instance. :: - - from flask import Flask - from flask_mongoengine import MongoEngine - - app = Flask(__name__) - app.config['MONGODB_SETTINGS'] = { - "db": "myapp", - } - db = MongoEngine(app) - - -Mapping Documents ------------------ - -To declare a model that represents a Mongo document, create a class that -inherits from ``Document`` and declare each of the fields. :: - - import mongoengine as me - - class Movie(me.Document): - title = me.StringField(required=True) - year = me.IntField() - rated = me.StringField() - director = me.StringField() - actors = me.ListField() - -If the document has nested fields, use ``EmbeddedDocument`` to -defined the fields of the embedded document and -``EmbeddedDocumentField`` to declare it on the parent document. :: - - class Imdb(me.EmbeddedDocument): - imdb_id = me.StringField() - rating = me.DecimalField() - votes = me.IntField() - - class Movie(me.Document): - ... - imdb = me.EmbeddedDocumentField(Imdb) - - -Creating Data -------------- - -Instantiate your document class with keyword arguments for the fields. -You can also assign values to the field attributes after instantiation. -Then call ``doc.save()``. :: - - bttf = Movie(title="Back To The Future", year=1985) - bttf.actors = [ - "Michael J. Fox", - "Christopher Lloyd" - ] - bttf.imdb = Imdb(imdb_id="tt0088763", rating=8.5) - bttf.save() - - -Queries -------- - -Use the class ``objects`` attribute to make queries. A keyword argument -looks for an equal value on the field. :: - - bttf = Movie.objects(title="Back To The Future").get_or_404() - -Query operators may be used by concatenating them with the field name -using a double-underscore. ``objects``, and queries returned by -calling it, are iterable. :: - - some_theron_movie = Movie.objects(actors__in=["Charlize Theron"]).first() - - for recents in Movie.objects(year__gte=2017): - print(recents.title) - - -Documentation -------------- - -There are many more ways to define and query documents with MongoEngine. -For more information, check out the `official documentation -`_. - -Flask-MongoEngine adds helpful utilities on top of MongoEngine. Check -out their `documentation `_ as well. diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst deleted file mode 100644 index 90fa8a8f..00000000 --- a/docs/patterns/packages.rst +++ /dev/null @@ -1,133 +0,0 @@ -Large Applications as Packages -============================== - -Imagine a simple flask application structure that looks like this:: - - /yourapplication - yourapplication.py - /static - style.css - /templates - layout.html - index.html - login.html - ... - -While this is fine for small applications, for larger applications -it's a good idea to use a package instead of a module. -The :doc:`/tutorial/index` is structured to use the package pattern, -see the :gh:`example code `. - -Simple Packages ---------------- - -To convert that into a larger one, just create a new folder -:file:`yourapplication` inside the existing one and move everything below it. -Then rename :file:`yourapplication.py` to :file:`__init__.py`. (Make sure to delete -all ``.pyc`` files first, otherwise things would most likely break) - -You should then end up with something like that:: - - /yourapplication - /yourapplication - __init__.py - /static - style.css - /templates - layout.html - index.html - login.html - ... - -But how do you run your application now? The naive ``python -yourapplication/__init__.py`` will not work. Let's just say that Python -does not want modules in packages to be the startup file. But that is not -a big problem, just add a new file called :file:`pyproject.toml` next to the inner -:file:`yourapplication` folder with the following contents: - -.. code-block:: toml - - [project] - name = "yourapplication" - dependencies = [ - "flask", - ] - - [build-system] - requires = ["flit_core<4"] - build-backend = "flit_core.buildapi" - -Install your application so it is importable: - -.. code-block:: text - - $ pip install -e . - -To use the ``flask`` command and run your application you need to set -the ``--app`` option that tells Flask where to find the application -instance: - -.. code-block:: text - - $ flask --app yourapplication run - -What did we gain from this? Now we can restructure the application a bit -into multiple modules. The only thing you have to remember is the -following quick checklist: - -1. the `Flask` application object creation has to be in the - :file:`__init__.py` file. That way each module can import it safely and the - `__name__` variable will resolve to the correct package. -2. all the view functions (the ones with a :meth:`~flask.Flask.route` - decorator on top) have to be imported in the :file:`__init__.py` file. - Not the object itself, but the module it is in. Import the view module - **after the application object is created**. - -Here's an example :file:`__init__.py`:: - - from flask import Flask - app = Flask(__name__) - - import yourapplication.views - -And this is what :file:`views.py` would look like:: - - from yourapplication import app - - @app.route('/') - def index(): - return 'Hello World!' - -You should then end up with something like that:: - - /yourapplication - pyproject.toml - /yourapplication - __init__.py - views.py - /static - style.css - /templates - layout.html - index.html - login.html - ... - -.. admonition:: Circular Imports - - Every Python programmer hates them, and yet we just added some: - circular imports (That's when two modules depend on each other. In this - case :file:`views.py` depends on :file:`__init__.py`). Be advised that this is a - bad idea in general but here it is actually fine. The reason for this is - that we are not actually using the views in :file:`__init__.py` and just - ensuring the module is imported and we are doing that at the bottom of - the file. - - -Working with Blueprints ------------------------ - -If you have larger applications it's recommended to divide them into -smaller groups where each group is implemented with the help of a -blueprint. For a gentle introduction into this topic refer to the -:doc:`/blueprints` chapter of the documentation. diff --git a/docs/patterns/requestchecksum.rst b/docs/patterns/requestchecksum.rst deleted file mode 100644 index 25bc38b2..00000000 --- a/docs/patterns/requestchecksum.rst +++ /dev/null @@ -1,55 +0,0 @@ -Request Content Checksums -========================= - -Various pieces of code can consume the request data and preprocess it. -For instance JSON data ends up on the request object already read and -processed, form data ends up there as well but goes through a different -code path. This seems inconvenient when you want to calculate the -checksum of the incoming request data. This is necessary sometimes for -some APIs. - -Fortunately this is however very simple to change by wrapping the input -stream. - -The following example calculates the SHA1 checksum of the incoming data as -it gets read and stores it in the WSGI environment:: - - import hashlib - - class ChecksumCalcStream(object): - - def __init__(self, stream): - self._stream = stream - self._hash = hashlib.sha1() - - def read(self, bytes): - rv = self._stream.read(bytes) - self._hash.update(rv) - return rv - - def readline(self, size_hint): - rv = self._stream.readline(size_hint) - self._hash.update(rv) - return rv - - def generate_checksum(request): - env = request.environ - stream = ChecksumCalcStream(env['wsgi.input']) - env['wsgi.input'] = stream - return stream._hash - -To use this, all you need to do is to hook the calculating stream in -before the request starts consuming data. (Eg: be careful accessing -``request.form`` or anything of that nature. ``before_request_handlers`` -for instance should be careful not to access it). - -Example usage:: - - @app.route('/special-api', methods=['POST']) - def special_api(): - hash = generate_checksum(request) - # Accessing this parses the input stream - files = request.files - # At this point the hash is fully constructed. - checksum = hash.hexdigest() - return f"Hash was: {checksum}" diff --git a/docs/patterns/singlepageapplications.rst b/docs/patterns/singlepageapplications.rst deleted file mode 100644 index 1cb779b3..00000000 --- a/docs/patterns/singlepageapplications.rst +++ /dev/null @@ -1,24 +0,0 @@ -Single-Page Applications -======================== - -Flask can be used to serve Single-Page Applications (SPA) by placing static -files produced by your frontend framework in a subfolder inside of your -project. You will also need to create a catch-all endpoint that routes all -requests to your SPA. - -The following example demonstrates how to serve an SPA along with an API:: - - from flask import Flask, jsonify - - app = Flask(__name__, static_folder='app', static_url_path="/app") - - - @app.route("/heartbeat") - def heartbeat(): - return jsonify({"status": "healthy"}) - - - @app.route('/', defaults={'path': ''}) - @app.route('/') - def catch_all(path): - return app.send_static_file("index.html") diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst deleted file mode 100644 index 7e4108d0..00000000 --- a/docs/patterns/sqlalchemy.rst +++ /dev/null @@ -1,214 +0,0 @@ -SQLAlchemy in Flask -=================== - -Many people prefer `SQLAlchemy`_ for database access. In this case it's -encouraged to use a package instead of a module for your flask application -and drop the models into a separate module (:doc:`packages`). While that -is not necessary, it makes a lot of sense. - -There are four very common ways to use SQLAlchemy. I will outline each -of them here: - -Flask-SQLAlchemy Extension --------------------------- - -Because SQLAlchemy is a common database abstraction layer and object -relational mapper that requires a little bit of configuration effort, -there is a Flask extension that handles that for you. This is recommended -if you want to get started quickly. - -You can download `Flask-SQLAlchemy`_ from `PyPI -`_. - -.. _Flask-SQLAlchemy: https://flask-sqlalchemy.palletsprojects.com/ - - -Declarative ------------ - -The declarative extension in SQLAlchemy is the most recent method of using -SQLAlchemy. It allows you to define tables and models in one go, similar -to how Django works. In addition to the following text I recommend the -official documentation on the `declarative`_ extension. - -Here's the example :file:`database.py` module for your application:: - - from sqlalchemy import create_engine - from sqlalchemy.orm import scoped_session, sessionmaker, declarative_base - - engine = create_engine('sqlite:////tmp/test.db') - db_session = scoped_session(sessionmaker(autocommit=False, - autoflush=False, - bind=engine)) - Base = declarative_base() - Base.query = db_session.query_property() - - def init_db(): - # import all modules here that might define models so that - # they will be registered properly on the metadata. Otherwise - # you will have to import them first before calling init_db() - import yourapplication.models - Base.metadata.create_all(bind=engine) - -To define your models, just subclass the `Base` class that was created by -the code above. If you are wondering why we don't have to care about -threads here (like we did in the SQLite3 example above with the -:data:`~flask.g` object): that's because SQLAlchemy does that for us -already with the :class:`~sqlalchemy.orm.scoped_session`. - -To use SQLAlchemy in a declarative way with your application, you just -have to put the following code into your application module. Flask will -automatically remove database sessions at the end of the request or -when the application shuts down:: - - from yourapplication.database import db_session - - @app.teardown_appcontext - def shutdown_session(exception=None): - db_session.remove() - -Here is an example model (put this into :file:`models.py`, e.g.):: - - from sqlalchemy import Column, Integer, String - from yourapplication.database import Base - - class User(Base): - __tablename__ = 'users' - id = Column(Integer, primary_key=True) - name = Column(String(50), unique=True) - email = Column(String(120), unique=True) - - def __init__(self, name=None, email=None): - self.name = name - self.email = email - - def __repr__(self): - return f'' - -To create the database you can use the `init_db` function: - ->>> from yourapplication.database import init_db ->>> init_db() - -You can insert entries into the database like this: - ->>> from yourapplication.database import db_session ->>> from yourapplication.models import User ->>> u = User('admin', 'admin@localhost') ->>> db_session.add(u) ->>> db_session.commit() - -Querying is simple as well: - ->>> User.query.all() -[] ->>> User.query.filter(User.name == 'admin').first() - - -.. _SQLAlchemy: https://www.sqlalchemy.org/ -.. _declarative: https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/ - -Manual Object Relational Mapping --------------------------------- - -Manual object relational mapping has a few upsides and a few downsides -versus the declarative approach from above. The main difference is that -you define tables and classes separately and map them together. It's more -flexible but a little more to type. In general it works like the -declarative approach, so make sure to also split up your application into -multiple modules in a package. - -Here is an example :file:`database.py` module for your application:: - - from sqlalchemy import create_engine, MetaData - from sqlalchemy.orm import scoped_session, sessionmaker - - engine = create_engine('sqlite:////tmp/test.db') - metadata = MetaData() - db_session = scoped_session(sessionmaker(autocommit=False, - autoflush=False, - bind=engine)) - def init_db(): - metadata.create_all(bind=engine) - -As in the declarative approach, you need to close the session after -each request or application context shutdown. Put this into your -application module:: - - from yourapplication.database import db_session - - @app.teardown_appcontext - def shutdown_session(exception=None): - db_session.remove() - -Here is an example table and model (put this into :file:`models.py`):: - - from sqlalchemy import Table, Column, Integer, String - from sqlalchemy.orm import mapper - from yourapplication.database import metadata, db_session - - class User(object): - query = db_session.query_property() - - def __init__(self, name=None, email=None): - self.name = name - self.email = email - - def __repr__(self): - return f'' - - users = Table('users', metadata, - Column('id', Integer, primary_key=True), - Column('name', String(50), unique=True), - Column('email', String(120), unique=True) - ) - mapper(User, users) - -Querying and inserting works exactly the same as in the example above. - - -SQL Abstraction Layer ---------------------- - -If you just want to use the database system (and SQL) abstraction layer -you basically only need the engine:: - - from sqlalchemy import create_engine, MetaData, Table - - engine = create_engine('sqlite:////tmp/test.db') - metadata = MetaData(bind=engine) - -Then you can either declare the tables in your code like in the examples -above, or automatically load them:: - - from sqlalchemy import Table - - users = Table('users', metadata, autoload=True) - -To insert data you can use the `insert` method. We have to get a -connection first so that we can use a transaction: - ->>> con = engine.connect() ->>> con.execute(users.insert(), name='admin', email='admin@localhost') - -SQLAlchemy will automatically commit for us. - -To query your database, you use the engine directly or use a connection: - ->>> users.select(users.c.id == 1).execute().first() -(1, 'admin', 'admin@localhost') - -These results are also dict-like tuples: - ->>> r = users.select(users.c.id == 1).execute().first() ->>> r['name'] -'admin' - -You can also pass strings of SQL statements to the -:meth:`~sqlalchemy.engine.base.Connection.execute` method: - ->>> engine.execute('select * from users where id = :1', [1]).first() -(1, 'admin', 'admin@localhost') - -For more information about SQLAlchemy, head over to the -`website `_. diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst deleted file mode 100644 index 5932589f..00000000 --- a/docs/patterns/sqlite3.rst +++ /dev/null @@ -1,147 +0,0 @@ -Using SQLite 3 with Flask -========================= - -In Flask you can easily implement the opening of database connections on -demand and closing them when the context dies (usually at the end of the -request). - -Here is a simple example of how you can use SQLite 3 with Flask:: - - import sqlite3 - from flask import g - - DATABASE = '/path/to/database.db' - - def get_db(): - db = getattr(g, '_database', None) - if db is None: - db = g._database = sqlite3.connect(DATABASE) - return db - - @app.teardown_appcontext - def close_connection(exception): - db = getattr(g, '_database', None) - if db is not None: - db.close() - -Now, to use the database, the application must either have an active -application context (which is always true if there is a request in flight) -or create an application context itself. At that point the ``get_db`` -function can be used to get the current database connection. Whenever the -context is destroyed the database connection will be terminated. - -Example:: - - @app.route('/') - def index(): - cur = get_db().cursor() - ... - - -.. note:: - - Please keep in mind that the teardown request and appcontext functions - are always executed, even if a before-request handler failed or was - never executed. Because of this we have to make sure here that the - database is there before we close it. - -Connect on Demand ------------------ - -The upside of this approach (connecting on first use) is that this will -only open the connection if truly necessary. If you want to use this -code outside a request context you can use it in a Python shell by opening -the application context by hand:: - - with app.app_context(): - # now you can use get_db() - - -Easy Querying -------------- - -Now in each request handling function you can access `get_db()` to get the -current open database connection. To simplify working with SQLite, a -row factory function is useful. It is executed for every result returned -from the database to convert the result. For instance, in order to get -dictionaries instead of tuples, this could be inserted into the ``get_db`` -function we created above:: - - def make_dicts(cursor, row): - return dict((cursor.description[idx][0], value) - for idx, value in enumerate(row)) - - db.row_factory = make_dicts - -This will make the sqlite3 module return dicts for this database connection, which are much nicer to deal with. Even more simply, we could place this in ``get_db`` instead:: - - db.row_factory = sqlite3.Row - -This would use Row objects rather than dicts to return the results of queries. These are ``namedtuple`` s, so we can access them either by index or by key. For example, assuming we have a ``sqlite3.Row`` called ``r`` for the rows ``id``, ``FirstName``, ``LastName``, and ``MiddleInitial``:: - - >>> # You can get values based on the row's name - >>> r['FirstName'] - John - >>> # Or, you can get them based on index - >>> r[1] - John - # Row objects are also iterable: - >>> for value in r: - ... print(value) - 1 - John - Doe - M - -Additionally, it is a good idea to provide a query function that combines -getting the cursor, executing and fetching the results:: - - def query_db(query, args=(), one=False): - cur = get_db().execute(query, args) - rv = cur.fetchall() - cur.close() - return (rv[0] if rv else None) if one else rv - -This handy little function, in combination with a row factory, makes -working with the database much more pleasant than it is by just using the -raw cursor and connection objects. - -Here is how you can use it:: - - for user in query_db('select * from users'): - print(user['username'], 'has the id', user['user_id']) - -Or if you just want a single result:: - - user = query_db('select * from users where username = ?', - [the_username], one=True) - if user is None: - print('No such user') - else: - print(the_username, 'has the id', user['user_id']) - -To pass variable parts to the SQL statement, use a question mark in the -statement and pass in the arguments as a list. Never directly add them to -the SQL statement with string formatting because this makes it possible -to attack the application using `SQL Injections -`_. - -Initial Schemas ---------------- - -Relational databases need schemas, so applications often ship a -`schema.sql` file that creates the database. It's a good idea to provide -a function that creates the database based on that schema. This function -can do that for you:: - - def init_db(): - with app.app_context(): - db = get_db() - with app.open_resource('schema.sql', mode='r') as f: - db.cursor().executescript(f.read()) - db.commit() - -You can then create such a database from the Python shell: - ->>> from yourapplication import init_db ->>> init_db() diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst deleted file mode 100644 index c9e6ef22..00000000 --- a/docs/patterns/streaming.rst +++ /dev/null @@ -1,85 +0,0 @@ -Streaming Contents -================== - -Sometimes you want to send an enormous amount of data to the client, much -more than you want to keep in memory. When you are generating the data on -the fly though, how do you send that back to the client without the -roundtrip to the filesystem? - -The answer is by using generators and direct responses. - -Basic Usage ------------ - -This is a basic view function that generates a lot of CSV data on the fly. -The trick is to have an inner function that uses a generator to generate -data and to then invoke that function and pass it to a response object:: - - @app.route('/large.csv') - def generate_large_csv(): - def generate(): - for row in iter_all_rows(): - yield f"{','.join(row)}\n" - return generate(), {"Content-Type": "text/csv"} - -Each ``yield`` expression is directly sent to the browser. Note though -that some WSGI middlewares might break streaming, so be careful there in -debug environments with profilers and other things you might have enabled. - -Streaming from Templates ------------------------- - -The Jinja2 template engine supports rendering a template piece by -piece, returning an iterator of strings. Flask provides the -:func:`~flask.stream_template` and :func:`~flask.stream_template_string` -functions to make this easier to use. - -.. code-block:: python - - from flask import stream_template - - @app.get("/timeline") - def timeline(): - return stream_template("timeline.html") - -The parts yielded by the render stream tend to match statement blocks in -the template. - - -Streaming with Context ----------------------- - -The :data:`~flask.request` will not be active while the generator is -running, because the view has already returned at that point. If you try -to access ``request``, you'll get a ``RuntimeError``. - -If your generator function relies on data in ``request``, use the -:func:`~flask.stream_with_context` wrapper. This will keep the request -context active during the generator. - -.. code-block:: python - - from flask import stream_with_context, request - from markupsafe import escape - - @app.route('/stream') - def streamed_response(): - def generate(): - yield '

Hello ' - yield escape(request.args['name']) - yield '!

' - return stream_with_context(generate()) - -It can also be used as a decorator. - -.. code-block:: python - - @stream_with_context - def generate(): - ... - - return generate() - -The :func:`~flask.stream_template` and -:func:`~flask.stream_template_string` functions automatically -use :func:`~flask.stream_with_context` if a request is active. diff --git a/docs/patterns/subclassing.rst b/docs/patterns/subclassing.rst deleted file mode 100644 index d8de2335..00000000 --- a/docs/patterns/subclassing.rst +++ /dev/null @@ -1,17 +0,0 @@ -Subclassing Flask -================= - -The :class:`~flask.Flask` class is designed for subclassing. - -For example, you may want to override how request parameters are handled to preserve their order:: - - from flask import Flask, Request - from werkzeug.datastructures import ImmutableOrderedMultiDict - class MyRequest(Request): - """Request subclass to override request parameter storage""" - parameter_storage_class = ImmutableOrderedMultiDict - class MyFlask(Flask): - """Flask subclass using the custom request class""" - request_class = MyRequest - -This is the recommended approach for overriding or augmenting Flask's internal functionality. diff --git a/docs/patterns/templateinheritance.rst b/docs/patterns/templateinheritance.rst deleted file mode 100644 index bb5cba27..00000000 --- a/docs/patterns/templateinheritance.rst +++ /dev/null @@ -1,68 +0,0 @@ -Template Inheritance -==================== - -The most powerful part of Jinja is template inheritance. Template inheritance -allows you to build a base "skeleton" template that contains all the common -elements of your site and defines **blocks** that child templates can override. - -Sounds complicated but is very basic. It's easiest to understand it by starting -with an example. - - -Base Template -------------- - -This template, which we'll call :file:`layout.html`, defines a simple HTML skeleton -document that you might use for a simple two-column page. It's the job of -"child" templates to fill the empty blocks with content: - -.. sourcecode:: html+jinja - - - - - {% block head %} - - {% block title %}{% endblock %} - My Webpage - {% endblock %} - - -
{% block content %}{% endblock %}
- - - - -In this example, the ``{% block %}`` tags define four blocks that child templates -can fill in. All the `block` tag does is tell the template engine that a -child template may override those portions of the template. - -Child Template --------------- - -A child template might look like this: - -.. sourcecode:: html+jinja - - {% extends "layout.html" %} - {% block title %}Index{% endblock %} - {% block head %} - {{ super() }} - - {% endblock %} - {% block content %} -

Index

-

- Welcome on my awesome homepage. - {% endblock %} - -The ``{% extends %}`` tag is the key here. It tells the template engine that -this template "extends" another template. When the template system evaluates -this template, first it locates the parent. The extends tag must be the -first tag in the template. To render the contents of a block defined in -the parent template, use ``{{ super() }}``. diff --git a/docs/patterns/urlprocessors.rst b/docs/patterns/urlprocessors.rst deleted file mode 100644 index 0d743205..00000000 --- a/docs/patterns/urlprocessors.rst +++ /dev/null @@ -1,126 +0,0 @@ -Using URL Processors -==================== - -.. versionadded:: 0.7 - -Flask 0.7 introduces the concept of URL processors. The idea is that you -might have a bunch of resources with common parts in the URL that you -don't always explicitly want to provide. For instance you might have a -bunch of URLs that have the language code in it but you don't want to have -to handle it in every single function yourself. - -URL processors are especially helpful when combined with blueprints. We -will handle both application specific URL processors here as well as -blueprint specifics. - -Internationalized Application URLs ----------------------------------- - -Consider an application like this:: - - from flask import Flask, g - - app = Flask(__name__) - - @app.route('//') - def index(lang_code): - g.lang_code = lang_code - ... - - @app.route('//about') - def about(lang_code): - g.lang_code = lang_code - ... - -This is an awful lot of repetition as you have to handle the language code -setting on the :data:`~flask.g` object yourself in every single function. -Sure, a decorator could be used to simplify this, but if you want to -generate URLs from one function to another you would have to still provide -the language code explicitly which can be annoying. - -For the latter, this is where :func:`~flask.Flask.url_defaults` functions -come in. They can automatically inject values into a call to -:func:`~flask.url_for`. The code below checks if the -language code is not yet in the dictionary of URL values and if the -endpoint wants a value named ``'lang_code'``:: - - @app.url_defaults - def add_language_code(endpoint, values): - if 'lang_code' in values or not g.lang_code: - return - if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): - values['lang_code'] = g.lang_code - -The method :meth:`~werkzeug.routing.Map.is_endpoint_expecting` of the URL -map can be used to figure out if it would make sense to provide a language -code for the given endpoint. - -The reverse of that function are -:meth:`~flask.Flask.url_value_preprocessor`\s. They are executed right -after the request was matched and can execute code based on the URL -values. The idea is that they pull information out of the values -dictionary and put it somewhere else:: - - @app.url_value_preprocessor - def pull_lang_code(endpoint, values): - g.lang_code = values.pop('lang_code', None) - -That way you no longer have to do the `lang_code` assignment to -:data:`~flask.g` in every function. You can further improve that by -writing your own decorator that prefixes URLs with the language code, but -the more beautiful solution is using a blueprint. Once the -``'lang_code'`` is popped from the values dictionary and it will no longer -be forwarded to the view function reducing the code to this:: - - from flask import Flask, g - - app = Flask(__name__) - - @app.url_defaults - def add_language_code(endpoint, values): - if 'lang_code' in values or not g.lang_code: - return - if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): - values['lang_code'] = g.lang_code - - @app.url_value_preprocessor - def pull_lang_code(endpoint, values): - g.lang_code = values.pop('lang_code', None) - - @app.route('//') - def index(): - ... - - @app.route('//about') - def about(): - ... - -Internationalized Blueprint URLs --------------------------------- - -Because blueprints can automatically prefix all URLs with a common string -it's easy to automatically do that for every function. Furthermore -blueprints can have per-blueprint URL processors which removes a whole lot -of logic from the :meth:`~flask.Flask.url_defaults` function because it no -longer has to check if the URL is really interested in a ``'lang_code'`` -parameter:: - - from flask import Blueprint, g - - bp = Blueprint('frontend', __name__, url_prefix='/') - - @bp.url_defaults - def add_language_code(endpoint, values): - values.setdefault('lang_code', g.lang_code) - - @bp.url_value_preprocessor - def pull_lang_code(endpoint, values): - g.lang_code = values.pop('lang_code') - - @bp.route('/') - def index(): - ... - - @bp.route('/about') - def about(): - ... diff --git a/docs/patterns/viewdecorators.rst b/docs/patterns/viewdecorators.rst deleted file mode 100644 index 0b0479ef..00000000 --- a/docs/patterns/viewdecorators.rst +++ /dev/null @@ -1,171 +0,0 @@ -View Decorators -=============== - -Python has a really interesting feature called function decorators. This -allows some really neat things for web applications. Because each view in -Flask is a function, decorators can be used to inject additional -functionality to one or more functions. The :meth:`~flask.Flask.route` -decorator is the one you probably used already. But there are use cases -for implementing your own decorator. For instance, imagine you have a -view that should only be used by people that are logged in. If a user -goes to the site and is not logged in, they should be redirected to the -login page. This is a good example of a use case where a decorator is an -excellent solution. - -Login Required Decorator ------------------------- - -So let's implement such a decorator. A decorator is a function that -wraps and replaces another function. Since the original function is -replaced, you need to remember to copy the original function's information -to the new function. Use :func:`functools.wraps` to handle this for you. - -This example assumes that the login page is called ``'login'`` and that -the current user is stored in ``g.user`` and is ``None`` if there is no-one -logged in. :: - - from functools import wraps - from flask import g, request, redirect, url_for - - def login_required(f): - @wraps(f) - def decorated_function(*args, **kwargs): - if g.user is None: - return redirect(url_for('login', next=request.url)) - return f(*args, **kwargs) - return decorated_function - -To use the decorator, apply it as innermost decorator to a view function. -When applying further decorators, always remember -that the :meth:`~flask.Flask.route` decorator is the outermost. :: - - @app.route('/secret_page') - @login_required - def secret_page(): - pass - -.. note:: - The ``next`` value will exist in ``request.args`` after a ``GET`` request for - the login page. You'll have to pass it along when sending the ``POST`` request - from the login form. You can do this with a hidden input tag, then retrieve it - from ``request.form`` when logging the user in. :: - - - - -Caching Decorator ------------------ - -Imagine you have a view function that does an expensive calculation and -because of that you would like to cache the generated results for a -certain amount of time. A decorator would be nice for that. We're -assuming you have set up a cache like mentioned in :doc:`caching`. - -Here is an example cache function. It generates the cache key from a -specific prefix (actually a format string) and the current path of the -request. Notice that we are using a function that first creates the -decorator that then decorates the function. Sounds awful? Unfortunately -it is a little bit more complex, but the code should still be -straightforward to read. - -The decorated function will then work as follows - -1. get the unique cache key for the current request based on the current - path. -2. get the value for that key from the cache. If the cache returned - something we will return that value. -3. otherwise the original function is called and the return value is - stored in the cache for the timeout provided (by default 5 minutes). - -Here the code:: - - from functools import wraps - from flask import request - - def cached(timeout=5 * 60, key='view/{}'): - def decorator(f): - @wraps(f) - def decorated_function(*args, **kwargs): - cache_key = key.format(request.path) - rv = cache.get(cache_key) - if rv is not None: - return rv - rv = f(*args, **kwargs) - cache.set(cache_key, rv, timeout=timeout) - return rv - return decorated_function - return decorator - -Notice that this assumes an instantiated ``cache`` object is available, see -:doc:`caching`. - - -Templating Decorator --------------------- - -A common pattern invented by the TurboGears guys a while back is a -templating decorator. The idea of that decorator is that you return a -dictionary with the values passed to the template from the view function -and the template is automatically rendered. With that, the following -three examples do exactly the same:: - - @app.route('/') - def index(): - return render_template('index.html', value=42) - - @app.route('/') - @templated('index.html') - def index(): - return dict(value=42) - - @app.route('/') - @templated() - def index(): - return dict(value=42) - -As you can see, if no template name is provided it will use the endpoint -of the URL map with dots converted to slashes + ``'.html'``. Otherwise -the provided template name is used. When the decorated function returns, -the dictionary returned is passed to the template rendering function. If -``None`` is returned, an empty dictionary is assumed, if something else than -a dictionary is returned we return it from the function unchanged. That -way you can still use the redirect function or return simple strings. - -Here is the code for that decorator:: - - from functools import wraps - from flask import request, render_template - - def templated(template=None): - def decorator(f): - @wraps(f) - def decorated_function(*args, **kwargs): - template_name = template - if template_name is None: - template_name = f"{request.endpoint.replace('.', '/')}.html" - ctx = f(*args, **kwargs) - if ctx is None: - ctx = {} - elif not isinstance(ctx, dict): - return ctx - return render_template(template_name, **ctx) - return decorated_function - return decorator - - -Endpoint Decorator ------------------- - -When you want to use the werkzeug routing system for more flexibility you -need to map the endpoint as defined in the :class:`~werkzeug.routing.Rule` -to a view function. This is possible with this decorator. For example:: - - from flask import Flask - from werkzeug.routing import Rule - - app = Flask(__name__) - app.url_map.add(Rule('/', endpoint='index')) - - @app.endpoint('index') - def my_index(): - return "Hello world" diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst deleted file mode 100644 index 3d626f50..00000000 --- a/docs/patterns/wtforms.rst +++ /dev/null @@ -1,126 +0,0 @@ -Form Validation with WTForms -============================ - -When you have to work with form data submitted by a browser view, code -quickly becomes very hard to read. There are libraries out there designed -to make this process easier to manage. One of them is `WTForms`_ which we -will handle here. If you find yourself in the situation of having many -forms, you might want to give it a try. - -When you are working with WTForms you have to define your forms as classes -first. I recommend breaking up the application into multiple modules -(:doc:`packages`) for that and adding a separate module for the -forms. - -.. admonition:: Getting the most out of WTForms with an Extension - - The `Flask-WTF`_ extension expands on this pattern and adds a - few little helpers that make working with forms and Flask more - fun. You can get it from `PyPI - `_. - -.. _Flask-WTF: https://flask-wtf.readthedocs.io/ - -The Forms ---------- - -This is an example form for a typical registration page:: - - from wtforms import Form, BooleanField, StringField, PasswordField, validators - - class RegistrationForm(Form): - username = StringField('Username', [validators.Length(min=4, max=25)]) - email = StringField('Email Address', [validators.Length(min=6, max=35)]) - password = PasswordField('New Password', [ - validators.DataRequired(), - validators.EqualTo('confirm', message='Passwords must match') - ]) - confirm = PasswordField('Repeat Password') - accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()]) - -In the View ------------ - -In the view function, the usage of this form looks like this:: - - @app.route('/register', methods=['GET', 'POST']) - def register(): - form = RegistrationForm(request.form) - if request.method == 'POST' and form.validate(): - user = User(form.username.data, form.email.data, - form.password.data) - db_session.add(user) - flash('Thanks for registering') - return redirect(url_for('login')) - return render_template('register.html', form=form) - -Notice we're implying that the view is using SQLAlchemy here -(:doc:`sqlalchemy`), but that's not a requirement, of course. Adapt -the code as necessary. - -Things to remember: - -1. create the form from the request :attr:`~flask.request.form` value if - the data is submitted via the HTTP ``POST`` method and - :attr:`~flask.request.args` if the data is submitted as ``GET``. -2. to validate the data, call the :func:`~wtforms.form.Form.validate` - method, which will return ``True`` if the data validates, ``False`` - otherwise. -3. to access individual values from the form, access `form..data`. - -Forms in Templates ------------------- - -Now to the template side. When you pass the form to the templates, you can -easily render them there. Look at the following example template to see -how easy this is. WTForms does half the form generation for us already. -To make it even nicer, we can write a macro that renders a field with -label and a list of errors if there are any. - -Here's an example :file:`_formhelpers.html` template with such a macro: - -.. sourcecode:: html+jinja - - {% macro render_field(field) %} -

{{ field.label }} -
{{ field(**kwargs)|safe }} - {% if field.errors %} -
    - {% for error in field.errors %} -
  • {{ error }}
  • - {% endfor %} -
- {% endif %} -
- {% endmacro %} - -This macro accepts a couple of keyword arguments that are forwarded to -WTForm's field function, which renders the field for us. The keyword -arguments will be inserted as HTML attributes. So, for example, you can -call ``render_field(form.username, class='username')`` to add a class to -the input element. Note that WTForms returns standard Python strings, -so we have to tell Jinja2 that this data is already HTML-escaped with -the ``|safe`` filter. - -Here is the :file:`register.html` template for the function we used above, which -takes advantage of the :file:`_formhelpers.html` template: - -.. sourcecode:: html+jinja - - {% from "_formhelpers.html" import render_field %} - -
- {{ render_field(form.username) }} - {{ render_field(form.email) }} - {{ render_field(form.password) }} - {{ render_field(form.confirm) }} - {{ render_field(form.accept_tos) }} -
-

-

- -For more information about WTForms, head over to the `WTForms -website`_. - -.. _WTForms: https://wtforms.readthedocs.io/ -.. _WTForms website: https://wtforms.readthedocs.io/ diff --git a/docs/quickstart.rst b/docs/quickstart.rst deleted file mode 100644 index f763bb1e..00000000 --- a/docs/quickstart.rst +++ /dev/null @@ -1,907 +0,0 @@ -Quickstart -========== - -Eager to get started? This page gives a good introduction to Flask. -Follow :doc:`installation` to set up a project and install Flask first. - - -A Minimal Application ---------------------- - -A minimal Flask application looks something like this: - -.. code-block:: python - - from flask import Flask - - app = Flask(__name__) - - @app.route("/") - def hello_world(): - return "

Hello, World!

" - -So what did that code do? - -1. First we imported the :class:`~flask.Flask` class. An instance of - this class will be our WSGI application. -2. Next we create an instance of this class. The first argument is the - name of the application's module or package. ``__name__`` is a - convenient shortcut for this that is appropriate for most cases. - This is needed so that Flask knows where to look for resources such - as templates and static files. -3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask - what URL should trigger our function. -4. The function returns the message we want to display in the user's - browser. The default content type is HTML, so HTML in the string - will be rendered by the browser. - -Save it as :file:`hello.py` or something similar. Make sure to not call -your application :file:`flask.py` because this would conflict with Flask -itself. - -To run the application, use the ``flask`` command or -``python -m flask``. You need to tell the Flask where your application -is with the ``--app`` option. - -.. code-block:: text - - $ flask --app hello run - * Serving Flask app 'hello' - * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) - -.. admonition:: Application Discovery Behavior - - As a shortcut, if the file is named ``app.py`` or ``wsgi.py``, you - don't have to use ``--app``. See :doc:`/cli` for more details. - -This launches a very simple builtin server, which is good enough for -testing but probably not what you want to use in production. For -deployment options see :doc:`deploying/index`. - -Now head over to http://127.0.0.1:5000/, and you should see your hello -world greeting. - -If another program is already using port 5000, you'll see -``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the -server tries to start. See :ref:`address-already-in-use` for how to -handle that. - -.. _public-server: - -.. admonition:: Externally Visible Server - - If you run the server you will notice that the server is only accessible - from your own computer, not from any other in the network. This is the - default because in debugging mode a user of the application can execute - arbitrary Python code on your computer. - - If you have the debugger disabled or trust the users on your network, - you can make the server publicly available simply by adding - ``--host=0.0.0.0`` to the command line:: - - $ flask run --host=0.0.0.0 - - This tells your operating system to listen on all public IPs. - - -Debug Mode ----------- - -The ``flask run`` command can do more than just start the development -server. By enabling debug mode, the server will automatically reload if -code changes, and will show an interactive debugger in the browser if an -error occurs during a request. - -.. image:: _static/debugger.png - :align: center - :class: screenshot - :alt: The interactive debugger in action. - -.. warning:: - - The debugger allows executing arbitrary Python code from the - browser. It is protected by a pin, but still represents a major - security risk. Do not run the development server or debugger in a - production environment. - -To enable debug mode, use the ``--debug`` option. - -.. code-block:: text - - $ flask --app hello run --debug - * Serving Flask app 'hello' - * Debug mode: on - * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) - * Restarting with stat - * Debugger is active! - * Debugger PIN: nnn-nnn-nnn - -See also: - -- :doc:`/server` and :doc:`/cli` for information about running in debug mode. -- :doc:`/debugging` for information about using the built-in debugger - and other debuggers. -- :doc:`/logging` and :doc:`/errorhandling` to log errors and display - nice error pages. - - -HTML Escaping -------------- - -When returning HTML (the default response type in Flask), any -user-provided values rendered in the output must be escaped to protect -from injection attacks. HTML templates rendered with Jinja, introduced -later, will do this automatically. - -:func:`~markupsafe.escape`, shown here, can be used manually. It is -omitted in most examples for brevity, but you should always be aware of -how you're using untrusted data. - -.. code-block:: python - - from markupsafe import escape - - @app.route("/") - def hello(name): - return f"Hello, {escape(name)}!" - -If a user managed to submit the name ````, -escaping causes it to be rendered as text, rather than running the -script in the user's browser. - -```` in the route captures a value from the URL and passes it to -the view function. These variable rules are explained below. - - -Routing -------- - -Modern web applications use meaningful URLs to help users. Users are more -likely to like a page and come back if the page uses a meaningful URL they can -remember and use to directly visit a page. - -Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. :: - - @app.route('/') - def index(): - return 'Index Page' - - @app.route('/hello') - def hello(): - return 'Hello, World' - -You can do more! You can make parts of the URL dynamic and attach multiple -rules to a function. - -Variable Rules -`````````````` - -You can add variable sections to a URL by marking sections with -````. Your function then receives the ```` -as a keyword argument. Optionally, you can use a converter to specify the type -of the argument like ````. :: - - from markupsafe import escape - - @app.route('/user/') - def show_user_profile(username): - # show the user profile for that user - return f'User {escape(username)}' - - @app.route('/post/') - def show_post(post_id): - # show the post with the given id, the id is an integer - return f'Post {post_id}' - - @app.route('/path/') - def show_subpath(subpath): - # show the subpath after /path/ - return f'Subpath {escape(subpath)}' - -Converter types: - -========== ========================================== -``string`` (default) accepts any text without a slash -``int`` accepts positive integers -``float`` accepts positive floating point values -``path`` like ``string`` but also accepts slashes -``uuid`` accepts UUID strings -========== ========================================== - - -Unique URLs / Redirection Behavior -`````````````````````````````````` - -The following two rules differ in their use of a trailing slash. :: - - @app.route('/projects/') - def projects(): - return 'The project page' - - @app.route('/about') - def about(): - return 'The about page' - -The canonical URL for the ``projects`` endpoint has a trailing slash. -It's similar to a folder in a file system. If you access the URL without -a trailing slash (``/projects``), Flask redirects you to the canonical URL -with the trailing slash (``/projects/``). - -The canonical URL for the ``about`` endpoint does not have a trailing -slash. It's similar to the pathname of a file. Accessing the URL with a -trailing slash (``/about/``) produces a 404 "Not Found" error. This helps -keep URLs unique for these resources, which helps search engines avoid -indexing the same page twice. - - -.. _url-building: - -URL Building -```````````` - -To build a URL to a specific function, use the :func:`~flask.url_for` function. -It accepts the name of the function as its first argument and any number of -keyword arguments, each corresponding to a variable part of the URL rule. -Unknown variable parts are appended to the URL as query parameters. - -Why would you want to build URLs using the URL reversing function -:func:`~flask.url_for` instead of hard-coding them into your templates? - -1. Reversing is often more descriptive than hard-coding the URLs. -2. You can change your URLs in one go instead of needing to remember to - manually change hard-coded URLs. -3. URL building handles escaping of special characters transparently. -4. The generated paths are always absolute, avoiding unexpected behavior - of relative paths in browsers. -5. If your application is placed outside the URL root, for example, in - ``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly - handles that for you. - -For example, here we use the :meth:`~flask.Flask.test_request_context` method -to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context` -tells Flask to behave as though it's handling a request even while we use a -Python shell. See :ref:`context-locals`. - -.. code-block:: python - - from flask import url_for - - @app.route('/') - def index(): - return 'index' - - @app.route('/login') - def login(): - return 'login' - - @app.route('/user/') - def profile(username): - return f'{username}\'s profile' - - with app.test_request_context(): - print(url_for('index')) - print(url_for('login')) - print(url_for('login', next='/')) - print(url_for('profile', username='John Doe')) - -.. code-block:: text - - / - /login - /login?next=/ - /user/John%20Doe - - -HTTP Methods -```````````` - -Web applications use different HTTP methods when accessing URLs. You should -familiarize yourself with the HTTP methods as you work with Flask. By default, -a route only answers to ``GET`` requests. You can use the ``methods`` argument -of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods. -:: - - from flask import request - - @app.route('/login', methods=['GET', 'POST']) - def login(): - if request.method == 'POST': - return do_the_login() - else: - return show_the_login_form() - -The example above keeps all methods for the route within one function, -which can be useful if each part uses some common data. - -You can also separate views for different methods into different -functions. Flask provides a shortcut for decorating such routes with -:meth:`~flask.Flask.get`, :meth:`~flask.Flask.post`, etc. for each -common HTTP method. - -.. code-block:: python - - @app.get('/login') - def login_get(): - return show_the_login_form() - - @app.post('/login') - def login_post(): - return do_the_login() - -If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method -and handles ``HEAD`` requests according to the `HTTP RFC`_. Likewise, -``OPTIONS`` is automatically implemented for you. - -.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt - -Static Files ------------- - -Dynamic web applications also need static files. That's usually where -the CSS and JavaScript files are coming from. Ideally your web server is -configured to serve them for you, but during development Flask can do that -as well. Just create a folder called :file:`static` in your package or next to -your module and it will be available at ``/static`` on the application. - -To generate URLs for static files, use the special ``'static'`` endpoint name:: - - url_for('static', filename='style.css') - -The file has to be stored on the filesystem as :file:`static/style.css`. - -Rendering Templates -------------------- - -Generating HTML from within Python is not fun, and actually pretty -cumbersome because you have to do the HTML escaping on your own to keep -the application secure. Because of that Flask configures the `Jinja2 -`_ template engine for you automatically. - -Templates can be used to generate any type of text file. For web applications, you'll -primarily be generating HTML pages, but you can also generate markdown, plain text for -emails, and anything else. - -For a reference to HTML, CSS, and other web APIs, use the `MDN Web Docs`_. - -.. _MDN Web Docs: https://developer.mozilla.org/ - -To render a template you can use the :func:`~flask.render_template` -method. All you have to do is provide the name of the template and the -variables you want to pass to the template engine as keyword arguments. -Here's a simple example of how to render a template:: - - from flask import render_template - - @app.route('/hello/') - @app.route('/hello/') - def hello(name=None): - return render_template('hello.html', person=name) - -Flask will look for templates in the :file:`templates` folder. So if your -application is a module, this folder is next to that module, if it's a -package it's actually inside your package: - -**Case 1**: a module:: - - /application.py - /templates - /hello.html - -**Case 2**: a package:: - - /application - /__init__.py - /templates - /hello.html - -For templates you can use the full power of Jinja2 templates. Head over -to the official `Jinja2 Template Documentation -`_ for more information. - -Here is an example template: - -.. sourcecode:: html+jinja - - - Hello from Flask - {% if person %} -

Hello {{ person }}!

- {% else %} -

Hello, World!

- {% endif %} - -Inside templates you also have access to the :data:`~flask.Flask.config`, -:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [#]_ objects -as well as the :func:`~flask.url_for` and :func:`~flask.get_flashed_messages` functions. - -Templates are especially useful if inheritance is used. If you want to -know how that works, see :doc:`patterns/templateinheritance`. Basically -template inheritance makes it possible to keep certain elements on each -page (like header, navigation and footer). - -Automatic escaping is enabled, so if ``person`` contains HTML it will be escaped -automatically. If you can trust a variable and you know that it will be -safe HTML (for example because it came from a module that converts wiki -markup to HTML) you can mark it as safe by using the -:class:`~markupsafe.Markup` class or by using the ``|safe`` filter in the -template. Head over to the Jinja 2 documentation for more examples. - -Here is a basic introduction to how the :class:`~markupsafe.Markup` class works:: - - >>> from markupsafe import Markup - >>> Markup('Hello %s!') % 'hacker' - Markup('Hello <blink>hacker</blink>!') - >>> Markup.escape('hacker') - Markup('<blink>hacker</blink>') - >>> Markup('Marked up » HTML').striptags() - 'Marked up » HTML' - -.. versionchanged:: 0.5 - - Autoescaping is no longer enabled for all templates. The following - extensions for templates trigger autoescaping: ``.html``, ``.htm``, - ``.xml``, ``.xhtml``. Templates loaded from a string will have - autoescaping disabled. - -.. [#] Unsure what that :class:`~flask.g` object is? It's something in which - you can store information for your own needs. See the documentation - for :class:`flask.g` and :doc:`patterns/sqlite3`. - - -Accessing Request Data ----------------------- - -For web applications it's crucial to react to the data a client sends to -the server. In Flask this information is provided by the global -:class:`~flask.request` object. If you have some experience with Python -you might be wondering how that object can be global and how Flask -manages to still be threadsafe. The answer is context locals: - - -.. _context-locals: - -Context Locals -`````````````` - -.. admonition:: Insider Information - - If you want to understand how that works and how you can implement - tests with context locals, read this section, otherwise just skip it. - -Certain objects in Flask are global objects, but not of the usual kind. -These objects are actually proxies to objects that are local to a specific -context. What a mouthful. But that is actually quite easy to understand. - -Imagine the context being the handling thread. A request comes in and the -web server decides to spawn a new thread (or something else, the -underlying object is capable of dealing with concurrency systems other -than threads). When Flask starts its internal request handling it -figures out that the current thread is the active context and binds the -current application and the WSGI environments to that context (thread). -It does that in an intelligent way so that one application can invoke another -application without breaking. - -So what does this mean to you? Basically you can completely ignore that -this is the case unless you are doing something like unit testing. You -will notice that code which depends on a request object will suddenly break -because there is no request object. The solution is creating a request -object yourself and binding it to the context. The easiest solution for -unit testing is to use the :meth:`~flask.Flask.test_request_context` -context manager. In combination with the ``with`` statement it will bind a -test request so that you can interact with it. Here is an example:: - - from flask import request - - with app.test_request_context('/hello', method='POST'): - # now you can do something with the request until the - # end of the with block, such as basic assertions: - assert request.path == '/hello' - assert request.method == 'POST' - -The other possibility is passing a whole WSGI environment to the -:meth:`~flask.Flask.request_context` method:: - - with app.request_context(environ): - assert request.method == 'POST' - -The Request Object -`````````````````` - -The request object is documented in the API section and we will not cover -it here in detail (see :class:`~flask.Request`). Here is a broad overview of -some of the most common operations. First of all you have to import it from -the ``flask`` module:: - - from flask import request - -The current request method is available by using the -:attr:`~flask.Request.method` attribute. To access form data (data -transmitted in a ``POST`` or ``PUT`` request) you can use the -:attr:`~flask.Request.form` attribute. Here is a full example of the two -attributes mentioned above:: - - @app.route('/login', methods=['POST', 'GET']) - def login(): - error = None - if request.method == 'POST': - if valid_login(request.form['username'], - request.form['password']): - return log_the_user_in(request.form['username']) - else: - error = 'Invalid username/password' - # the code below is executed if the request method - # was GET or the credentials were invalid - return render_template('login.html', error=error) - -What happens if the key does not exist in the ``form`` attribute? In that -case a special :exc:`KeyError` is raised. You can catch it like a -standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request -error page is shown instead. So for many situations you don't have to -deal with that problem. - -To access parameters submitted in the URL (``?key=value``) you can use the -:attr:`~flask.Request.args` attribute:: - - searchword = request.args.get('key', '') - -We recommend accessing URL parameters with `get` or by catching the -:exc:`KeyError` because users might change the URL and presenting them a 400 -bad request page in that case is not user friendly. - -For a full list of methods and attributes of the request object, head over -to the :class:`~flask.Request` documentation. - - -File Uploads -```````````` - -You can handle uploaded files with Flask easily. Just make sure not to -forget to set the ``enctype="multipart/form-data"`` attribute on your HTML -form, otherwise the browser will not transmit your files at all. - -Uploaded files are stored in memory or at a temporary location on the -filesystem. You can access those files by looking at the -:attr:`~flask.request.files` attribute on the request object. Each -uploaded file is stored in that dictionary. It behaves just like a -standard Python :class:`file` object, but it also has a -:meth:`~werkzeug.datastructures.FileStorage.save` method that -allows you to store that file on the filesystem of the server. -Here is a simple example showing how that works:: - - from flask import request - - @app.route('/upload', methods=['GET', 'POST']) - def upload_file(): - if request.method == 'POST': - f = request.files['the_file'] - f.save('/var/www/uploads/uploaded_file.txt') - ... - -If you want to know how the file was named on the client before it was -uploaded to your application, you can access the -:attr:`~werkzeug.datastructures.FileStorage.filename` attribute. -However please keep in mind that this value can be forged -so never ever trust that value. If you want to use the filename -of the client to store the file on the server, pass it through the -:func:`~werkzeug.utils.secure_filename` function that -Werkzeug provides for you:: - - from werkzeug.utils import secure_filename - - @app.route('/upload', methods=['GET', 'POST']) - def upload_file(): - if request.method == 'POST': - file = request.files['the_file'] - file.save(f"/var/www/uploads/{secure_filename(file.filename)}") - ... - -For some better examples, see :doc:`patterns/fileuploads`. - -Cookies -``````` - -To access cookies you can use the :attr:`~flask.Request.cookies` -attribute. To set cookies you can use the -:attr:`~flask.Response.set_cookie` method of response objects. The -:attr:`~flask.Request.cookies` attribute of request objects is a -dictionary with all the cookies the client transmits. If you want to use -sessions, do not use the cookies directly but instead use the -:ref:`sessions` in Flask that add some security on top of cookies for you. - -Reading cookies:: - - from flask import request - - @app.route('/') - def index(): - username = request.cookies.get('username') - # use cookies.get(key) instead of cookies[key] to not get a - # KeyError if the cookie is missing. - -Storing cookies:: - - from flask import make_response - - @app.route('/') - def index(): - resp = make_response(render_template(...)) - resp.set_cookie('username', 'the username') - return resp - -Note that cookies are set on response objects. Since you normally -just return strings from the view functions Flask will convert them into -response objects for you. If you explicitly want to do that you can use -the :meth:`~flask.make_response` function and then modify it. - -Sometimes you might want to set a cookie at a point where the response -object does not exist yet. This is possible by utilizing the -:doc:`patterns/deferredcallbacks` pattern. - -For this also see :ref:`about-responses`. - -Redirects and Errors --------------------- - -To redirect a user to another endpoint, use the :func:`~flask.redirect` -function; to abort a request early with an error code, use the -:func:`~flask.abort` function:: - - from flask import abort, redirect, url_for - - @app.route('/') - def index(): - return redirect(url_for('login')) - - @app.route('/login') - def login(): - abort(401) - this_is_never_executed() - -This is a rather pointless example because a user will be redirected from -the index to a page they cannot access (401 means access denied) but it -shows how that works. - -By default a black and white error page is shown for each error code. If -you want to customize the error page, you can use the -:meth:`~flask.Flask.errorhandler` decorator:: - - from flask import render_template - - @app.errorhandler(404) - def page_not_found(error): - return render_template('page_not_found.html'), 404 - -Note the ``404`` after the :func:`~flask.render_template` call. This -tells Flask that the status code of that page should be 404 which means -not found. By default 200 is assumed which translates to: all went well. - -See :doc:`errorhandling` for more details. - -.. _about-responses: - -About Responses ---------------- - -The return value from a view function is automatically converted into -a response object for you. If the return value is a string it's -converted into a response object with the string as response body, a -``200 OK`` status code and a :mimetype:`text/html` mimetype. If the -return value is a dict or list, :func:`jsonify` is called to produce a -response. The logic that Flask applies to converting return values into -response objects is as follows: - -1. If a response object of the correct type is returned it's directly - returned from the view. -2. If it's a string, a response object is created with that data and - the default parameters. -3. If it's an iterator or generator returning strings or bytes, it is - treated as a streaming response. -4. If it's a dict or list, a response object is created using - :func:`~flask.json.jsonify`. -5. If a tuple is returned the items in the tuple can provide extra - information. Such tuples have to be in the form - ``(response, status)``, ``(response, headers)``, or - ``(response, status, headers)``. The ``status`` value will override - the status code and ``headers`` can be a list or dictionary of - additional header values. -6. If none of that works, Flask will assume the return value is a - valid WSGI application and convert that into a response object. - -If you want to get hold of the resulting response object inside the view -you can use the :func:`~flask.make_response` function. - -Imagine you have a view like this:: - - from flask import render_template - - @app.errorhandler(404) - def not_found(error): - return render_template('error.html'), 404 - -You just need to wrap the return expression with -:func:`~flask.make_response` and get the response object to modify it, then -return it:: - - from flask import make_response - - @app.errorhandler(404) - def not_found(error): - resp = make_response(render_template('error.html'), 404) - resp.headers['X-Something'] = 'A value' - return resp - - -APIs with JSON -`````````````` - -A common response format when writing an API is JSON. It's easy to get -started writing such an API with Flask. If you return a ``dict`` or -``list`` from a view, it will be converted to a JSON response. - -.. code-block:: python - - @app.route("/me") - def me_api(): - user = get_current_user() - return { - "username": user.username, - "theme": user.theme, - "image": url_for("user_image", filename=user.image), - } - - @app.route("/users") - def users_api(): - users = get_all_users() - return [user.to_json() for user in users] - -This is a shortcut to passing the data to the -:func:`~flask.json.jsonify` function, which will serialize any supported -JSON data type. That means that all the data in the dict or list must be -JSON serializable. - -For complex types such as database models, you'll want to use a -serialization library to convert the data to valid JSON types first. -There are many serialization libraries and Flask API extensions -maintained by the community that support more complex applications. - - -.. _sessions: - -Sessions --------- - -In addition to the request object there is also a second object called -:class:`~flask.session` which allows you to store information specific to a -user from one request to the next. This is implemented on top of cookies -for you and signs the cookies cryptographically. What this means is that -the user could look at the contents of your cookie but not modify it, -unless they know the secret key used for signing. - -In order to use sessions you have to set a secret key. Here is how -sessions work:: - - from flask import session - - # Set the secret key to some random bytes. Keep this really secret! - app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' - - @app.route('/') - def index(): - if 'username' in session: - return f'Logged in as {session["username"]}' - return 'You are not logged in' - - @app.route('/login', methods=['GET', 'POST']) - def login(): - if request.method == 'POST': - session['username'] = request.form['username'] - return redirect(url_for('index')) - return ''' -
-

-

-

- ''' - - @app.route('/logout') - def logout(): - # remove the username from the session if it's there - session.pop('username', None) - return redirect(url_for('index')) - -.. admonition:: How to generate good secret keys - - A secret key should be as random as possible. Your operating system has - ways to generate pretty random data based on a cryptographic random - generator. Use the following command to quickly generate a value for - :attr:`Flask.secret_key` (or :data:`SECRET_KEY`):: - - $ python -c 'import secrets; print(secrets.token_hex())' - '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' - -A note on cookie-based sessions: Flask will take the values you put into the -session object and serialize them into a cookie. If you are finding some -values do not persist across requests, cookies are indeed enabled, and you are -not getting a clear error message, check the size of the cookie in your page -responses compared to the size supported by web browsers. - -Besides the default client-side based sessions, if you want to handle -sessions on the server-side instead, there are several -Flask extensions that support this. - -Message Flashing ----------------- - -Good applications and user interfaces are all about feedback. If the user -does not get enough feedback they will probably end up hating the -application. Flask provides a really simple way to give feedback to a -user with the flashing system. The flashing system basically makes it -possible to record a message at the end of a request and access it on the next -(and only the next) request. This is usually combined with a layout -template to expose the message. - -To flash a message use the :func:`~flask.flash` method, to get hold of the -messages you can use :func:`~flask.get_flashed_messages` which is also -available in the templates. See :doc:`patterns/flashing` for a full -example. - -Logging -------- - -.. versionadded:: 0.3 - -Sometimes you might be in a situation where you deal with data that -should be correct, but actually is not. For example you may have -some client-side code that sends an HTTP request to the server -but it's obviously malformed. This might be caused by a user tampering -with the data, or the client code failing. Most of the time it's okay -to reply with ``400 Bad Request`` in that situation, but sometimes -that won't do and the code has to continue working. - -You may still want to log that something fishy happened. This is where -loggers come in handy. As of Flask 0.3 a logger is preconfigured for you -to use. - -Here are some example log calls:: - - app.logger.debug('A value for debugging') - app.logger.warning('A warning occurred (%d apples)', 42) - app.logger.error('An error occurred') - -The attached :attr:`~flask.Flask.logger` is a standard logging -:class:`~logging.Logger`, so head over to the official :mod:`logging` -docs for more information. - -See :doc:`errorhandling`. - - -Hooking in WSGI Middleware --------------------------- - -To add WSGI middleware to your Flask application, wrap the application's -``wsgi_app`` attribute. For example, to apply Werkzeug's -:class:`~werkzeug.middleware.proxy_fix.ProxyFix` middleware for running -behind Nginx: - -.. code-block:: python - - from werkzeug.middleware.proxy_fix import ProxyFix - app.wsgi_app = ProxyFix(app.wsgi_app) - -Wrapping ``app.wsgi_app`` instead of ``app`` means that ``app`` still -points at your Flask application, not at the middleware, so you can -continue to use and configure ``app`` directly. - -Using Flask Extensions ----------------------- - -Extensions are packages that help you accomplish common tasks. For -example, Flask-SQLAlchemy provides SQLAlchemy support that makes it simple -and easy to use with Flask. - -For more on Flask extensions, see :doc:`extensions`. - -Deploying to a Web Server -------------------------- - -Ready to deploy your new Flask app? See :doc:`deploying/index`. diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst deleted file mode 100644 index 4f1846a3..00000000 --- a/docs/reqcontext.rst +++ /dev/null @@ -1,243 +0,0 @@ -.. currentmodule:: flask - -The Request Context -=================== - -The request context keeps track of the request-level data during a -request. Rather than passing the request object to each function that -runs during a request, the :data:`request` and :data:`session` proxies -are accessed instead. - -This is similar to :doc:`/appcontext`, which keeps track of the -application-level data independent of a request. A corresponding -application context is pushed when a request context is pushed. - - -Purpose of the Context ----------------------- - -When the :class:`Flask` application handles a request, it creates a -:class:`Request` object based on the environment it received from the -WSGI server. Because a *worker* (thread, process, or coroutine depending -on the server) handles only one request at a time, the request data can -be considered global to that worker during that request. Flask uses the -term *context local* for this. - -Flask automatically *pushes* a request context when handling a request. -View functions, error handlers, and other functions that run during a -request will have access to the :data:`request` proxy, which points to -the request object for the current request. - - -Lifetime of the Context ------------------------ - -When a Flask application begins handling a request, it pushes a request -context, which also pushes an :doc:`app context `. When the -request ends it pops the request context then the application context. - -The context is unique to each thread (or other worker type). -:data:`request` cannot be passed to another thread, the other thread has -a different context space and will not know about the request the parent -thread was pointing to. - -Context locals are implemented using Python's :mod:`contextvars` and -Werkzeug's :class:`~werkzeug.local.LocalProxy`. Python manages the -lifetime of context vars automatically, and local proxy wraps that -low-level interface to make the data easier to work with. - - -Manually Push a Context ------------------------ - -If you try to access :data:`request`, or anything that uses it, outside -a request context, you'll get this error message: - -.. code-block:: pytb - - RuntimeError: Working outside of request context. - - This typically means that you attempted to use functionality that - needed an active HTTP request. Consult the documentation on testing - for information about how to avoid this problem. - -This should typically only happen when testing code that expects an -active request. One option is to use the -:meth:`test client ` to simulate a full request. Or -you can use :meth:`~Flask.test_request_context` in a ``with`` block, and -everything that runs in the block will have access to :data:`request`, -populated with your test data. :: - - def generate_report(year): - format = request.args.get("format") - ... - - with app.test_request_context( - "/make_report/2017", query_string={"format": "short"} - ): - generate_report() - -If you see that error somewhere else in your code not related to -testing, it most likely indicates that you should move that code into a -view function. - -For information on how to use the request context from the interactive -Python shell, see :doc:`/shell`. - - -How the Context Works ---------------------- - -The :meth:`Flask.wsgi_app` method is called to handle each request. It -manages the contexts during the request. Internally, the request and -application contexts work like stacks. When contexts are pushed, the -proxies that depend on them are available and point at information from -the top item. - -When the request starts, a :class:`~ctx.RequestContext` is created and -pushed, which creates and pushes an :class:`~ctx.AppContext` first if -a context for that application is not already the top context. While -these contexts are pushed, the :data:`current_app`, :data:`g`, -:data:`request`, and :data:`session` proxies are available to the -original thread handling the request. - -Other contexts may be pushed to change the proxies during a request. -While this is not a common pattern, it can be used in advanced -applications to, for example, do internal redirects or chain different -applications together. - -After the request is dispatched and a response is generated and sent, -the request context is popped, which then pops the application context. -Immediately before they are popped, the :meth:`~Flask.teardown_request` -and :meth:`~Flask.teardown_appcontext` functions are executed. These -execute even if an unhandled exception occurred during dispatch. - - -.. _callbacks-and-errors: - -Callbacks and Errors --------------------- - -Flask dispatches a request in multiple stages which can affect the -request, response, and how errors are handled. The contexts are active -during all of these stages. - -A :class:`Blueprint` can add handlers for these events that are specific -to the blueprint. The handlers for a blueprint will run if the blueprint -owns the route that matches the request. - -#. Before each request, :meth:`~Flask.before_request` functions are - called. If one of these functions return a value, the other - functions are skipped. The return value is treated as the response - and the view function is not called. - -#. If the :meth:`~Flask.before_request` functions did not return a - response, the view function for the matched route is called and - returns a response. - -#. The return value of the view is converted into an actual response - object and passed to the :meth:`~Flask.after_request` - functions. Each function returns a modified or new response object. - -#. After the response is returned, the contexts are popped, which calls - the :meth:`~Flask.teardown_request` and - :meth:`~Flask.teardown_appcontext` functions. These functions are - called even if an unhandled exception was raised at any point above. - -If an exception is raised before the teardown functions, Flask tries to -match it with an :meth:`~Flask.errorhandler` function to handle the -exception and return a response. If no error handler is found, or the -handler itself raises an exception, Flask returns a generic -``500 Internal Server Error`` response. The teardown functions are still -called, and are passed the exception object. - -If debug mode is enabled, unhandled exceptions are not converted to a -``500`` response and instead are propagated to the WSGI server. This -allows the development server to present the interactive debugger with -the traceback. - - -Teardown Callbacks -~~~~~~~~~~~~~~~~~~ - -The teardown callbacks are independent of the request dispatch, and are -instead called by the contexts when they are popped. The functions are -called even if there is an unhandled exception during dispatch, and for -manually pushed contexts. This means there is no guarantee that any -other parts of the request dispatch have run first. Be sure to write -these functions in a way that does not depend on other callbacks and -will not fail. - -During testing, it can be useful to defer popping the contexts after the -request ends, so that their data can be accessed in the test function. -Use the :meth:`~Flask.test_client` as a ``with`` block to preserve the -contexts until the ``with`` block exits. - -.. code-block:: python - - from flask import Flask, request - - app = Flask(__name__) - - @app.route('/') - def hello(): - print('during view') - return 'Hello, World!' - - @app.teardown_request - def show_teardown(exception): - print('after with block') - - with app.test_request_context(): - print('during with block') - - # teardown functions are called after the context with block exits - - with app.test_client() as client: - client.get('/') - # the contexts are not popped even though the request ended - print(request.path) - - # the contexts are popped and teardown functions are called after - # the client with block exits - -Signals -~~~~~~~ - -The following signals are sent: - -#. :data:`request_started` is sent before the :meth:`~Flask.before_request` functions - are called. -#. :data:`request_finished` is sent after the :meth:`~Flask.after_request` functions - are called. -#. :data:`got_request_exception` is sent when an exception begins to be handled, but - before an :meth:`~Flask.errorhandler` is looked up or called. -#. :data:`request_tearing_down` is sent after the :meth:`~Flask.teardown_request` - functions are called. - - -.. _notes-on-proxies: - -Notes On Proxies ----------------- - -Some of the objects provided by Flask are proxies to other objects. The -proxies are accessed in the same way for each worker thread, but -point to the unique object bound to each worker behind the scenes as -described on this page. - -Most of the time you don't have to care about that, but there are some -exceptions where it is good to know that this object is actually a proxy: - -- The proxy objects cannot fake their type as the actual object types. - If you want to perform instance checks, you have to do that on the - object being proxied. -- The reference to the proxied object is needed in some situations, - such as sending :doc:`signals` or passing data to a background - thread. - -If you need to access the underlying object that is proxied, use the -:meth:`~werkzeug.local.LocalProxy._get_current_object` method:: - - app = current_app._get_current_object() - my_signal.send(app) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3356b619..6966869c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1 @@ sphinx -sphinx-rtd-theme -myst-parser \ No newline at end of file diff --git a/docs/server.rst b/docs/server.rst deleted file mode 100644 index 11e976bc..00000000 --- a/docs/server.rst +++ /dev/null @@ -1,115 +0,0 @@ -.. currentmodule:: flask - -Development Server -================== - -Flask provides a ``run`` command to run the application with a development server. In -debug mode, this server provides an interactive debugger and will reload when code is -changed. - -.. warning:: - - Do not use the development server when deploying to production. It - is intended for use only during local development. It is not - designed to be particularly efficient, stable, or secure. - - See :doc:`/deploying/index` for deployment options. - -Command Line ------------- - -The ``flask run`` CLI command is the recommended way to run the development server. Use -the ``--app`` option to point to your application, and the ``--debug`` option to enable -debug mode. - -.. code-block:: text - - $ flask --app hello run --debug - -This enables debug mode, including the interactive debugger and reloader, and then -starts the server on http://localhost:5000/. Use ``flask run --help`` to see the -available options, and :doc:`/cli` for detailed instructions about configuring and using -the CLI. - - -.. _address-already-in-use: - -Address already in use -~~~~~~~~~~~~~~~~~~~~~~ - -If another program is already using port 5000, you'll see an ``OSError`` -when the server tries to start. It may have one of the following -messages: - -- ``OSError: [Errno 98] Address already in use`` -- ``OSError: [WinError 10013] An attempt was made to access a socket - in a way forbidden by its access permissions`` - -Either identify and stop the other program, or use -``flask run --port 5001`` to pick a different port. - -You can use ``netstat`` or ``lsof`` to identify what process id is using -a port, then use other operating system tools stop that process. The -following example shows that process id 6847 is using port 5000. - -.. tabs:: - - .. tab:: ``netstat`` (Linux) - - .. code-block:: text - - $ netstat -nlp | grep 5000 - tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 6847/python - - .. tab:: ``lsof`` (macOS / Linux) - - .. code-block:: text - - $ lsof -P -i :5000 - Python 6847 IPv4 TCP localhost:5000 (LISTEN) - - .. tab:: ``netstat`` (Windows) - - .. code-block:: text - - > netstat -ano | findstr 5000 - TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING 6847 - -macOS Monterey and later automatically starts a service that uses port -5000. You can choose to disable this service instead of using a different port by -searching for "AirPlay Receiver" in System Preferences and toggling it off. - - -Deferred Errors on Reload -~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using the ``flask run`` command with the reloader, the server will -continue to run even if you introduce syntax errors or other -initialization errors into the code. Accessing the site will show the -interactive debugger for the error, rather than crashing the server. - -If a syntax error is already present when calling ``flask run``, it will -fail immediately and show the traceback rather than waiting until the -site is accessed. This is intended to make errors more visible initially -while still allowing the server to handle errors on reload. - - -In Code -------- - -The development server can also be started from Python with the :meth:`Flask.run` -method. This method takes arguments similar to the CLI options to control the server. -The main difference from the CLI command is that the server will crash if there are -errors when reloading. ``debug=True`` can be passed to enable debug mode. - -Place the call in a main block, otherwise it will interfere when trying to import and -run the application with a production server later. - -.. code-block:: python - - if __name__ == "__main__": - app.run(debug=True) - -.. code-block:: text - - $ python hello.py diff --git a/docs/shell.rst b/docs/shell.rst deleted file mode 100644 index 7e42e285..00000000 --- a/docs/shell.rst +++ /dev/null @@ -1,100 +0,0 @@ -Working with the Shell -====================== - -.. versionadded:: 0.3 - -One of the reasons everybody loves Python is the interactive shell. It -basically allows you to execute Python commands in real time and -immediately get results back. Flask itself does not come with an -interactive shell, because it does not require any specific setup upfront, -just import your application and start playing around. - -There are however some handy helpers to make playing around in the shell a -more pleasant experience. The main issue with interactive console -sessions is that you're not triggering a request like a browser does which -means that :data:`~flask.g`, :data:`~flask.request` and others are not -available. But the code you want to test might depend on them, so what -can you do? - -This is where some helper functions come in handy. Keep in mind however -that these functions are not only there for interactive shell usage, but -also for unit testing and other situations that require a faked request -context. - -Generally it's recommended that you read :doc:`reqcontext` first. - -Command Line Interface ----------------------- - -Starting with Flask 0.11 the recommended way to work with the shell is the -``flask shell`` command which does a lot of this automatically for you. -For instance the shell is automatically initialized with a loaded -application context. - -For more information see :doc:`/cli`. - -Creating a Request Context --------------------------- - -The easiest way to create a proper request context from the shell is by -using the :attr:`~flask.Flask.test_request_context` method which creates -us a :class:`~flask.ctx.RequestContext`: - ->>> ctx = app.test_request_context() - -Normally you would use the ``with`` statement to make this request object -active, but in the shell it's easier to use the -:meth:`~flask.ctx.RequestContext.push` and -:meth:`~flask.ctx.RequestContext.pop` methods by hand: - ->>> ctx.push() - -From that point onwards you can work with the request object until you -call `pop`: - ->>> ctx.pop() - -Firing Before/After Request ---------------------------- - -By just creating a request context, you still don't have run the code that -is normally run before a request. This might result in your database -being unavailable if you are connecting to the database in a -before-request callback or the current user not being stored on the -:data:`~flask.g` object etc. - -This however can easily be done yourself. Just call -:meth:`~flask.Flask.preprocess_request`: - ->>> ctx = app.test_request_context() ->>> ctx.push() ->>> app.preprocess_request() - -Keep in mind that the :meth:`~flask.Flask.preprocess_request` function -might return a response object, in that case just ignore it. - -To shutdown a request, you need to trick a bit before the after request -functions (triggered by :meth:`~flask.Flask.process_response`) operate on -a response object: - ->>> app.process_response(app.response_class()) - ->>> ctx.pop() - -The functions registered as :meth:`~flask.Flask.teardown_request` are -automatically called when the context is popped. So this is the perfect -place to automatically tear down resources that were needed by the request -context (such as database connections). - - -Further Improving the Shell Experience --------------------------------------- - -If you like the idea of experimenting in a shell, create yourself a module -with stuff you want to star import into your interactive session. There -you could also define some more helper methods for common things such as -initializing the database, dropping tables etc. - -Just put them into a module (like `shelltools`) and import from there: - ->>> from shelltools import * diff --git a/docs/signals.rst b/docs/signals.rst deleted file mode 100644 index 739bb0b5..00000000 --- a/docs/signals.rst +++ /dev/null @@ -1,167 +0,0 @@ -Signals -======= - -Signals are a lightweight way to notify subscribers of certain events during the -lifecycle of the application and each request. When an event occurs, it emits the -signal, which calls each subscriber. - -Signals are implemented by the `Blinker`_ library. See its documentation for detailed -information. Flask provides some built-in signals. Extensions may provide their own. - -Many signals mirror Flask's decorator-based callbacks with similar names. For example, -the :data:`.request_started` signal is similar to the :meth:`~.Flask.before_request` -decorator. The advantage of signals over handlers is that they can be subscribed to -temporarily, and can't directly affect the application. This is useful for testing, -metrics, auditing, and more. For example, if you want to know what templates were -rendered at what parts of what requests, there is a signal that will notify you of that -information. - - -Core Signals ------------- - -See :ref:`core-signals-list` for a list of all built-in signals. The :doc:`lifecycle` -page also describes the order that signals and decorators execute. - - -Subscribing to Signals ----------------------- - -To subscribe to a signal, you can use the -:meth:`~blinker.base.Signal.connect` method of a signal. The first -argument is the function that should be called when the signal is emitted, -the optional second argument specifies a sender. To unsubscribe from a -signal, you can use the :meth:`~blinker.base.Signal.disconnect` method. - -For all core Flask signals, the sender is the application that issued the -signal. When you subscribe to a signal, be sure to also provide a sender -unless you really want to listen for signals from all applications. This is -especially true if you are developing an extension. - -For example, here is a helper context manager that can be used in a unit test -to determine which templates were rendered and what variables were passed -to the template:: - - from flask import template_rendered - from contextlib import contextmanager - - @contextmanager - def captured_templates(app): - recorded = [] - def record(sender, template, context, **extra): - recorded.append((template, context)) - template_rendered.connect(record, app) - try: - yield recorded - finally: - template_rendered.disconnect(record, app) - -This can now easily be paired with a test client:: - - with captured_templates(app) as templates: - rv = app.test_client().get('/') - assert rv.status_code == 200 - assert len(templates) == 1 - template, context = templates[0] - assert template.name == 'index.html' - assert len(context['items']) == 10 - -Make sure to subscribe with an extra ``**extra`` argument so that your -calls don't fail if Flask introduces new arguments to the signals. - -All the template rendering in the code issued by the application `app` -in the body of the ``with`` block will now be recorded in the `templates` -variable. Whenever a template is rendered, the template object as well as -context are appended to it. - -Additionally there is a convenient helper method -(:meth:`~blinker.base.Signal.connected_to`) that allows you to -temporarily subscribe a function to a signal with a context manager on -its own. Because the return value of the context manager cannot be -specified that way, you have to pass the list in as an argument:: - - from flask import template_rendered - - def captured_templates(app, recorded, **extra): - def record(sender, template, context): - recorded.append((template, context)) - return template_rendered.connected_to(record, app) - -The example above would then look like this:: - - templates = [] - with captured_templates(app, templates, **extra): - ... - template, context = templates[0] - -Creating Signals ----------------- - -If you want to use signals in your own application, you can use the -blinker library directly. The most common use case are named signals in a -custom :class:`~blinker.base.Namespace`. This is what is recommended -most of the time:: - - from blinker import Namespace - my_signals = Namespace() - -Now you can create new signals like this:: - - model_saved = my_signals.signal('model-saved') - -The name for the signal here makes it unique and also simplifies -debugging. You can access the name of the signal with the -:attr:`~blinker.base.NamedSignal.name` attribute. - -.. _signals-sending: - -Sending Signals ---------------- - -If you want to emit a signal, you can do so by calling the -:meth:`~blinker.base.Signal.send` method. It accepts a sender as first -argument and optionally some keyword arguments that are forwarded to the -signal subscribers:: - - class Model(object): - ... - - def save(self): - model_saved.send(self) - -Try to always pick a good sender. If you have a class that is emitting a -signal, pass ``self`` as sender. If you are emitting a signal from a random -function, you can pass ``current_app._get_current_object()`` as sender. - -.. admonition:: Passing Proxies as Senders - - Never pass :data:`~flask.current_app` as sender to a signal. Use - ``current_app._get_current_object()`` instead. The reason for this is - that :data:`~flask.current_app` is a proxy and not the real application - object. - - -Signals and Flask's Request Context ------------------------------------ - -Signals fully support :doc:`reqcontext` when receiving signals. -Context-local variables are consistently available between -:data:`~flask.request_started` and :data:`~flask.request_finished`, so you can -rely on :class:`flask.g` and others as needed. Note the limitations described -in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal. - - -Decorator Based Signal Subscriptions ------------------------------------- - -You can also easily subscribe to signals by using the -:meth:`~blinker.base.NamedSignal.connect_via` decorator:: - - from flask import template_rendered - - @template_rendered.connect_via(app) - def when_template_rendered(sender, template, context, **extra): - print(f'Template {template.name} is rendered with {context}') - - -.. _blinker: https://pypi.org/project/blinker/ diff --git a/docs/source/conf.py b/docs/source/conf.py index e97ed9b1..f1eebf38 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,11 +5,10 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - import os import sys -sys.path.insert(0, os.path.abspath('../../src')) +sys.path.insert(0, os.path.abspath('../../src')) project = 'Flask' copyright = '2025, Pallets Team' author = 'Pallets Team' @@ -18,9 +17,9 @@ author = 'Pallets Team' # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - 'sphinx.ext.autodoc', # Para generar doc automáticamente desde los docstrings - 'sphinx.ext.napoleon', # Para soportar docstrings estilo Google/NumPy - 'sphinx.ext.todo', # Para mostrar TODOs + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', # Para Google/NumPy docstrings + 'sphinx.ext.todo', ] templates_path = ['_templates'] @@ -33,5 +32,3 @@ exclude_patterns = [] html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] - -todo_include_todos = True diff --git a/docs/source/index.rst b/docs/source/index.rst index 0594f5bb..bef08e80 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,13 +1,16 @@ .. Flask documentation master file, created by - sphinx-quickstart on Thu Apr 10 15:29:04 2025. + sphinx-quickstart on Thu Apr 10 19:53:46 2025. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Flask documentation -=================== +Flask - Documentación Personalizada +==================================== + +Bienvenido a la documentación del proyecto Flask. .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: Contenido: modules + diff --git a/docs/templating.rst b/docs/templating.rst deleted file mode 100644 index 23cfee4c..00000000 --- a/docs/templating.rst +++ /dev/null @@ -1,229 +0,0 @@ -Templates -========= - -Flask leverages Jinja2 as its template engine. You are obviously free to use -a different template engine, but you still have to install Jinja2 to run -Flask itself. This requirement is necessary to enable rich extensions. -An extension can depend on Jinja2 being present. - -This section only gives a very quick introduction into how Jinja2 -is integrated into Flask. If you want information on the template -engine's syntax itself, head over to the official `Jinja2 Template -Documentation `_ for -more information. - -Jinja Setup ------------ - -Unless customized, Jinja2 is configured by Flask as follows: - -- autoescaping is enabled for all templates ending in ``.html``, - ``.htm``, ``.xml``, ``.xhtml``, as well as ``.svg`` when using - :func:`~flask.templating.render_template`. -- autoescaping is enabled for all strings when using - :func:`~flask.templating.render_template_string`. -- a template has the ability to opt in/out autoescaping with the - ``{% autoescape %}`` tag. -- Flask inserts a couple of global functions and helpers into the - Jinja2 context, additionally to the values that are present by - default. - -Standard Context ----------------- - -The following global variables are available within Jinja2 templates -by default: - -.. data:: config - :noindex: - - The current configuration object (:data:`flask.Flask.config`) - - .. versionadded:: 0.6 - - .. versionchanged:: 0.10 - This is now always available, even in imported templates. - -.. data:: request - :noindex: - - The current request object (:class:`flask.request`). This variable is - unavailable if the template was rendered without an active request - context. - -.. data:: session - :noindex: - - The current session object (:class:`flask.session`). This variable - is unavailable if the template was rendered without an active request - context. - -.. data:: g - :noindex: - - The request-bound object for global variables (:data:`flask.g`). This - variable is unavailable if the template was rendered without an active - request context. - -.. function:: url_for - :noindex: - - The :func:`flask.url_for` function. - -.. function:: get_flashed_messages - :noindex: - - The :func:`flask.get_flashed_messages` function. - -.. admonition:: The Jinja Context Behavior - - These variables are added to the context of variables, they are not - global variables. The difference is that by default these will not - show up in the context of imported templates. This is partially caused - by performance considerations, partially to keep things explicit. - - What does this mean for you? If you have a macro you want to import, - that needs to access the request object you have two possibilities: - - 1. you explicitly pass the request to the macro as parameter, or - the attribute of the request object you are interested in. - 2. you import the macro "with context". - - Importing with context looks like this: - - .. sourcecode:: jinja - - {% from '_helpers.html' import my_macro with context %} - - -Controlling Autoescaping ------------------------- - -Autoescaping is the concept of automatically escaping special characters -for you. Special characters in the sense of HTML (or XML, and thus XHTML) -are ``&``, ``>``, ``<``, ``"`` as well as ``'``. Because these characters -carry specific meanings in documents on their own you have to replace them -by so called "entities" if you want to use them for text. Not doing so -would not only cause user frustration by the inability to use these -characters in text, but can also lead to security problems. (see -:ref:`security-xss`) - -Sometimes however you will need to disable autoescaping in templates. -This can be the case if you want to explicitly inject HTML into pages, for -example if they come from a system that generates secure HTML like a -markdown to HTML converter. - -There are three ways to accomplish that: - -- In the Python code, wrap the HTML string in a :class:`~markupsafe.Markup` - object before passing it to the template. This is in general the - recommended way. -- Inside the template, use the ``|safe`` filter to explicitly mark a - string as safe HTML (``{{ myvariable|safe }}``) -- Temporarily disable the autoescape system altogether. - -To disable the autoescape system in templates, you can use the ``{% -autoescape %}`` block: - -.. sourcecode:: html+jinja - - {% autoescape false %} -

autoescaping is disabled here -

{{ will_not_be_escaped }} - {% endautoescape %} - -Whenever you do this, please be very cautious about the variables you are -using in this block. - -.. _registering-filters: - -Registering Filters -------------------- - -If you want to register your own filters in Jinja2 you have two ways to do -that. You can either put them by hand into the -:attr:`~flask.Flask.jinja_env` of the application or use the -:meth:`~flask.Flask.template_filter` decorator. - -The two following examples work the same and both reverse an object:: - - @app.template_filter('reverse') - def reverse_filter(s): - return s[::-1] - - def reverse_filter(s): - return s[::-1] - app.jinja_env.filters['reverse'] = reverse_filter - -In case of the decorator the argument is optional if you want to use the -function name as name of the filter. Once registered, you can use the filter -in your templates in the same way as Jinja2's builtin filters, for example if -you have a Python list in context called `mylist`:: - - {% for x in mylist | reverse %} - {% endfor %} - - -Context Processors ------------------- - -To inject new variables automatically into the context of a template, -context processors exist in Flask. Context processors run before the -template is rendered and have the ability to inject new values into the -template context. A context processor is a function that returns a -dictionary. The keys and values of this dictionary are then merged with -the template context, for all templates in the app:: - - @app.context_processor - def inject_user(): - return dict(user=g.user) - -The context processor above makes a variable called `user` available in -the template with the value of `g.user`. This example is not very -interesting because `g` is available in templates anyways, but it gives an -idea how this works. - -Variables are not limited to values; a context processor can also make -functions available to templates (since Python allows passing around -functions):: - - @app.context_processor - def utility_processor(): - def format_price(amount, currency="€"): - return f"{amount:.2f}{currency}" - return dict(format_price=format_price) - -The context processor above makes the `format_price` function available to all -templates:: - - {{ format_price(0.33) }} - -You could also build `format_price` as a template filter (see -:ref:`registering-filters`), but this demonstrates how to pass functions in a -context processor. - -Streaming ---------- - -It can be useful to not render the whole template as one complete -string, instead render it as a stream, yielding smaller incremental -strings. This can be used for streaming HTML in chunks to speed up -initial page load, or to save memory when rendering a very large -template. - -The Jinja2 template engine supports rendering a template piece -by piece, returning an iterator of strings. Flask provides the -:func:`~flask.stream_template` and :func:`~flask.stream_template_string` -functions to make this easier to use. - -.. code-block:: python - - from flask import stream_template - - @app.get("/timeline") - def timeline(): - return stream_template("timeline.html") - -These functions automatically apply the -:func:`~flask.stream_with_context` wrapper if a request is active, so -that it remains available in the template. diff --git a/docs/testing.rst b/docs/testing.rst deleted file mode 100644 index b1d52f9a..00000000 --- a/docs/testing.rst +++ /dev/null @@ -1,319 +0,0 @@ -Testing Flask Applications -========================== - -Flask provides utilities for testing an application. This documentation -goes over techniques for working with different parts of the application -in tests. - -We will use the `pytest`_ framework to set up and run our tests. - -.. code-block:: text - - $ pip install pytest - -.. _pytest: https://docs.pytest.org/ - -The :doc:`tutorial ` goes over how to write tests for -100% coverage of the sample Flaskr blog application. See -:doc:`the tutorial on tests ` for a detailed -explanation of specific tests for an application. - - -Identifying Tests ------------------ - -Tests are typically located in the ``tests`` folder. Tests are functions -that start with ``test_``, in Python modules that start with ``test_``. -Tests can also be further grouped in classes that start with ``Test``. - -It can be difficult to know what to test. Generally, try to test the -code that you write, not the code of libraries that you use, since they -are already tested. Try to extract complex behaviors as separate -functions to test individually. - - -Fixtures --------- - -Pytest *fixtures* allow writing pieces of code that are reusable across -tests. A simple fixture returns a value, but a fixture can also do -setup, yield a value, then do teardown. Fixtures for the application, -test client, and CLI runner are shown below, they can be placed in -``tests/conftest.py``. - -If you're using an -:doc:`application factory `, define an ``app`` -fixture to create and configure an app instance. You can add code before -and after the ``yield`` to set up and tear down other resources, such as -creating and clearing a database. - -If you're not using a factory, you already have an app object you can -import and configure directly. You can still use an ``app`` fixture to -set up and tear down resources. - -.. code-block:: python - - import pytest - from my_project import create_app - - @pytest.fixture() - def app(): - app = create_app() - app.config.update({ - "TESTING": True, - }) - - # other setup can go here - - yield app - - # clean up / reset resources here - - - @pytest.fixture() - def client(app): - return app.test_client() - - - @pytest.fixture() - def runner(app): - return app.test_cli_runner() - - -Sending Requests with the Test Client -------------------------------------- - -The test client makes requests to the application without running a live -server. Flask's client extends -:doc:`Werkzeug's client `, see those docs for additional -information. - -The ``client`` has methods that match the common HTTP request methods, -such as ``client.get()`` and ``client.post()``. They take many arguments -for building the request; you can find the full documentation in -:class:`~werkzeug.test.EnvironBuilder`. Typically you'll use ``path``, -``query_string``, ``headers``, and ``data`` or ``json``. - -To make a request, call the method the request should use with the path -to the route to test. A :class:`~werkzeug.test.TestResponse` is returned -to examine the response data. It has all the usual properties of a -response object. You'll usually look at ``response.data``, which is the -bytes returned by the view. If you want to use text, Werkzeug 2.1 -provides ``response.text``, or use ``response.get_data(as_text=True)``. - -.. code-block:: python - - def test_request_example(client): - response = client.get("/posts") - assert b"

Hello, World!

" in response.data - - -Pass a dict ``query_string={"key": "value", ...}`` to set arguments in -the query string (after the ``?`` in the URL). Pass a dict -``headers={}`` to set request headers. - -To send a request body in a POST or PUT request, pass a value to -``data``. If raw bytes are passed, that exact body is used. Usually, -you'll pass a dict to set form data. - - -Form Data -~~~~~~~~~ - -To send form data, pass a dict to ``data``. The ``Content-Type`` header -will be set to ``multipart/form-data`` or -``application/x-www-form-urlencoded`` automatically. - -If a value is a file object opened for reading bytes (``"rb"`` mode), it -will be treated as an uploaded file. To change the detected filename and -content type, pass a ``(file, filename, content_type)`` tuple. File -objects will be closed after making the request, so they do not need to -use the usual ``with open() as f:`` pattern. - -It can be useful to store files in a ``tests/resources`` folder, then -use ``pathlib.Path`` to get files relative to the current test file. - -.. code-block:: python - - from pathlib import Path - - # get the resources folder in the tests folder - resources = Path(__file__).parent / "resources" - - def test_edit_user(client): - response = client.post("/user/2/edit", data={ - "name": "Flask", - "theme": "dark", - "picture": (resources / "picture.png").open("rb"), - }) - assert response.status_code == 200 - - -JSON Data -~~~~~~~~~ - -To send JSON data, pass an object to ``json``. The ``Content-Type`` -header will be set to ``application/json`` automatically. - -Similarly, if the response contains JSON data, the ``response.json`` -attribute will contain the deserialized object. - -.. code-block:: python - - def test_json_data(client): - response = client.post("/graphql", json={ - "query": """ - query User($id: String!) { - user(id: $id) { - name - theme - picture_url - } - } - """, - variables={"id": 2}, - }) - assert response.json["data"]["user"]["name"] == "Flask" - - -Following Redirects -------------------- - -By default, the client does not make additional requests if the response -is a redirect. By passing ``follow_redirects=True`` to a request method, -the client will continue to make requests until a non-redirect response -is returned. - -:attr:`TestResponse.history ` is -a tuple of the responses that led up to the final response. Each -response has a :attr:`~werkzeug.test.TestResponse.request` attribute -which records the request that produced that response. - -.. code-block:: python - - def test_logout_redirect(client): - response = client.get("/logout", follow_redirects=True) - # Check that there was one redirect response. - assert len(response.history) == 1 - # Check that the second request was to the index page. - assert response.request.path == "/index" - - -Accessing and Modifying the Session ------------------------------------ - -To access Flask's context variables, mainly -:data:`~flask.session`, use the client in a ``with`` statement. -The app and request context will remain active *after* making a request, -until the ``with`` block ends. - -.. code-block:: python - - from flask import session - - def test_access_session(client): - with client: - client.post("/auth/login", data={"username": "flask"}) - # session is still accessible - assert session["user_id"] == 1 - - # session is no longer accessible - -If you want to access or set a value in the session *before* making a -request, use the client's -:meth:`~flask.testing.FlaskClient.session_transaction` method in a -``with`` statement. It returns a session object, and will save the -session once the block ends. - -.. code-block:: python - - from flask import session - - def test_modify_session(client): - with client.session_transaction() as session: - # set a user id without going through the login route - session["user_id"] = 1 - - # session is saved now - - response = client.get("/users/me") - assert response.json["username"] == "flask" - - -.. _testing-cli: - -Running Commands with the CLI Runner ------------------------------------- - -Flask provides :meth:`~flask.Flask.test_cli_runner` to create a -:class:`~flask.testing.FlaskCliRunner`, which runs CLI commands in -isolation and captures the output in a :class:`~click.testing.Result` -object. Flask's runner extends :doc:`Click's runner `, -see those docs for additional information. - -Use the runner's :meth:`~flask.testing.FlaskCliRunner.invoke` method to -call commands in the same way they would be called with the ``flask`` -command from the command line. - -.. code-block:: python - - import click - - @app.cli.command("hello") - @click.option("--name", default="World") - def hello_command(name): - click.echo(f"Hello, {name}!") - - def test_hello_command(runner): - result = runner.invoke(args="hello") - assert "World" in result.output - - result = runner.invoke(args=["hello", "--name", "Flask"]) - assert "Flask" in result.output - - -Tests that depend on an Active Context --------------------------------------- - -You may have functions that are called from views or commands, that -expect an active :doc:`application context ` or -:doc:`request context ` because they access ``request``, -``session``, or ``current_app``. Rather than testing them by making a -request or invoking the command, you can create and activate a context -directly. - -Use ``with app.app_context()`` to push an application context. For -example, database extensions usually require an active app context to -make queries. - -.. code-block:: python - - def test_db_post_model(app): - with app.app_context(): - post = db.session.query(Post).get(1) - -Use ``with app.test_request_context()`` to push a request context. It -takes the same arguments as the test client's request methods. - -.. code-block:: python - - def test_validate_user_edit(app): - with app.test_request_context( - "/user/2/edit", method="POST", data={"name": ""} - ): - # call a function that accesses `request` - messages = validate_edit_user() - - assert messages["name"][0] == "Name cannot be empty." - -Creating a test request context doesn't run any of the Flask dispatching -code, so ``before_request`` functions are not called. If you need to -call these, usually it's better to make a full request instead. However, -it's possible to call them manually. - -.. code-block:: python - - def test_auth_token(app): - with app.test_request_context("/user/2/edit", headers={"X-Auth-Token": "1"}): - app.preprocess_request() - assert g.user.name == "Flask" diff --git a/docs/tutorial/blog.rst b/docs/tutorial/blog.rst deleted file mode 100644 index b06329ea..00000000 --- a/docs/tutorial/blog.rst +++ /dev/null @@ -1,336 +0,0 @@ -.. currentmodule:: flask - -Blog Blueprint -============== - -You'll use the same techniques you learned about when writing the -authentication blueprint to write the blog blueprint. The blog should -list all posts, allow logged in users to create posts, and allow the -author of a post to edit or delete it. - -As you implement each view, keep the development server running. As you -save your changes, try going to the URL in your browser and testing them -out. - -The Blueprint -------------- - -Define the blueprint and register it in the application factory. - -.. code-block:: python - :caption: ``flaskr/blog.py`` - - from flask import ( - Blueprint, flash, g, redirect, render_template, request, url_for - ) - from werkzeug.exceptions import abort - - from flaskr.auth import login_required - from flaskr.db import get_db - - bp = Blueprint('blog', __name__) - -Import and register the blueprint from the factory using -:meth:`app.register_blueprint() `. Place the -new code at the end of the factory function before returning the app. - -.. code-block:: python - :caption: ``flaskr/__init__.py`` - - def create_app(): - app = ... - # existing code omitted - - from . import blog - app.register_blueprint(blog.bp) - app.add_url_rule('/', endpoint='index') - - return app - - -Unlike the auth blueprint, the blog blueprint does not have a -``url_prefix``. So the ``index`` view will be at ``/``, the ``create`` -view at ``/create``, and so on. The blog is the main feature of Flaskr, -so it makes sense that the blog index will be the main index. - -However, the endpoint for the ``index`` view defined below will be -``blog.index``. Some of the authentication views referred to a plain -``index`` endpoint. :meth:`app.add_url_rule() ` -associates the endpoint name ``'index'`` with the ``/`` url so that -``url_for('index')`` or ``url_for('blog.index')`` will both work, -generating the same ``/`` URL either way. - -In another application you might give the blog blueprint a -``url_prefix`` and define a separate ``index`` view in the application -factory, similar to the ``hello`` view. Then the ``index`` and -``blog.index`` endpoints and URLs would be different. - - -Index ------ - -The index will show all of the posts, most recent first. A ``JOIN`` is -used so that the author information from the ``user`` table is -available in the result. - -.. code-block:: python - :caption: ``flaskr/blog.py`` - - @bp.route('/') - def index(): - db = get_db() - posts = db.execute( - 'SELECT p.id, title, body, created, author_id, username' - ' FROM post p JOIN user u ON p.author_id = u.id' - ' ORDER BY created DESC' - ).fetchall() - return render_template('blog/index.html', posts=posts) - -.. code-block:: html+jinja - :caption: ``flaskr/templates/blog/index.html`` - - {% extends 'base.html' %} - - {% block header %} -

{% block title %}Posts{% endblock %}

- {% if g.user %} - New - {% endif %} - {% endblock %} - - {% block content %} - {% for post in posts %} -
-
-
-

{{ post['title'] }}

-
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
-
- {% if g.user['id'] == post['author_id'] %} - Edit - {% endif %} -
-

{{ post['body'] }}

-
- {% if not loop.last %} -
- {% endif %} - {% endfor %} - {% endblock %} - -When a user is logged in, the ``header`` block adds a link to the -``create`` view. When the user is the author of a post, they'll see an -"Edit" link to the ``update`` view for that post. ``loop.last`` is a -special variable available inside `Jinja for loops`_. It's used to -display a line after each post except the last one, to visually separate -them. - -.. _Jinja for loops: https://jinja.palletsprojects.com/templates/#for - - -Create ------- - -The ``create`` view works the same as the auth ``register`` view. Either -the form is displayed, or the posted data is validated and the post is -added to the database or an error is shown. - -The ``login_required`` decorator you wrote earlier is used on the blog -views. A user must be logged in to visit these views, otherwise they -will be redirected to the login page. - -.. code-block:: python - :caption: ``flaskr/blog.py`` - - @bp.route('/create', methods=('GET', 'POST')) - @login_required - def create(): - if request.method == 'POST': - title = request.form['title'] - body = request.form['body'] - error = None - - if not title: - error = 'Title is required.' - - if error is not None: - flash(error) - else: - db = get_db() - db.execute( - 'INSERT INTO post (title, body, author_id)' - ' VALUES (?, ?, ?)', - (title, body, g.user['id']) - ) - db.commit() - return redirect(url_for('blog.index')) - - return render_template('blog/create.html') - -.. code-block:: html+jinja - :caption: ``flaskr/templates/blog/create.html`` - - {% extends 'base.html' %} - - {% block header %} -

{% block title %}New Post{% endblock %}

- {% endblock %} - - {% block content %} -
- - - - - -
- {% endblock %} - - -Update ------- - -Both the ``update`` and ``delete`` views will need to fetch a ``post`` -by ``id`` and check if the author matches the logged in user. To avoid -duplicating code, you can write a function to get the ``post`` and call -it from each view. - -.. code-block:: python - :caption: ``flaskr/blog.py`` - - def get_post(id, check_author=True): - post = get_db().execute( - 'SELECT p.id, title, body, created, author_id, username' - ' FROM post p JOIN user u ON p.author_id = u.id' - ' WHERE p.id = ?', - (id,) - ).fetchone() - - if post is None: - abort(404, f"Post id {id} doesn't exist.") - - if check_author and post['author_id'] != g.user['id']: - abort(403) - - return post - -:func:`abort` will raise a special exception that returns an HTTP status -code. It takes an optional message to show with the error, otherwise a -default message is used. ``404`` means "Not Found", and ``403`` means -"Forbidden". (``401`` means "Unauthorized", but you redirect to the -login page instead of returning that status.) - -The ``check_author`` argument is defined so that the function can be -used to get a ``post`` without checking the author. This would be useful -if you wrote a view to show an individual post on a page, where the user -doesn't matter because they're not modifying the post. - -.. code-block:: python - :caption: ``flaskr/blog.py`` - - @bp.route('//update', methods=('GET', 'POST')) - @login_required - def update(id): - post = get_post(id) - - if request.method == 'POST': - title = request.form['title'] - body = request.form['body'] - error = None - - if not title: - error = 'Title is required.' - - if error is not None: - flash(error) - else: - db = get_db() - db.execute( - 'UPDATE post SET title = ?, body = ?' - ' WHERE id = ?', - (title, body, id) - ) - db.commit() - return redirect(url_for('blog.index')) - - return render_template('blog/update.html', post=post) - -Unlike the views you've written so far, the ``update`` function takes -an argument, ``id``. That corresponds to the ```` in the route. -A real URL will look like ``/1/update``. Flask will capture the ``1``, -ensure it's an :class:`int`, and pass it as the ``id`` argument. If you -don't specify ``int:`` and instead do ````, it will be a string. -To generate a URL to the update page, :func:`url_for` needs to be passed -the ``id`` so it knows what to fill in: -``url_for('blog.update', id=post['id'])``. This is also in the -``index.html`` file above. - -The ``create`` and ``update`` views look very similar. The main -difference is that the ``update`` view uses a ``post`` object and an -``UPDATE`` query instead of an ``INSERT``. With some clever refactoring, -you could use one view and template for both actions, but for the -tutorial it's clearer to keep them separate. - -.. code-block:: html+jinja - :caption: ``flaskr/templates/blog/update.html`` - - {% extends 'base.html' %} - - {% block header %} -

{% block title %}Edit "{{ post['title'] }}"{% endblock %}

- {% endblock %} - - {% block content %} -
- - - - - -
-
-
- -
- {% endblock %} - -This template has two forms. The first posts the edited data to the -current page (``//update``). The other form contains only a button -and specifies an ``action`` attribute that posts to the delete view -instead. The button uses some JavaScript to show a confirmation dialog -before submitting. - -The pattern ``{{ request.form['title'] or post['title'] }}`` is used to -choose what data appears in the form. When the form hasn't been -submitted, the original ``post`` data appears, but if invalid form data -was posted you want to display that so the user can fix the error, so -``request.form`` is used instead. :data:`request` is another variable -that's automatically available in templates. - - -Delete ------- - -The delete view doesn't have its own template, the delete button is part -of ``update.html`` and posts to the ``//delete`` URL. Since there -is no template, it will only handle the ``POST`` method and then redirect -to the ``index`` view. - -.. code-block:: python - :caption: ``flaskr/blog.py`` - - @bp.route('//delete', methods=('POST',)) - @login_required - def delete(id): - get_post(id) - db = get_db() - db.execute('DELETE FROM post WHERE id = ?', (id,)) - db.commit() - return redirect(url_for('blog.index')) - -Congratulations, you've now finished writing your application! Take some -time to try out everything in the browser. However, there's still more -to do before the project is complete. - -Continue to :doc:`install`. diff --git a/docs/tutorial/database.rst b/docs/tutorial/database.rst deleted file mode 100644 index 93abf93a..00000000 --- a/docs/tutorial/database.rst +++ /dev/null @@ -1,219 +0,0 @@ -.. currentmodule:: flask - -Define and Access the Database -============================== - -The application will use a `SQLite`_ database to store users and posts. -Python comes with built-in support for SQLite in the :mod:`sqlite3` -module. - -SQLite is convenient because it doesn't require setting up a separate -database server and is built-in to Python. However, if concurrent -requests try to write to the database at the same time, they will slow -down as each write happens sequentially. Small applications won't notice -this. Once you become big, you may want to switch to a different -database. - -The tutorial doesn't go into detail about SQL. If you are not familiar -with it, the SQLite docs describe the `language`_. - -.. _SQLite: https://sqlite.org/about.html -.. _language: https://sqlite.org/lang.html - - -Connect to the Database ------------------------ - -The first thing to do when working with a SQLite database (and most -other Python database libraries) is to create a connection to it. Any -queries and operations are performed using the connection, which is -closed after the work is finished. - -In web applications this connection is typically tied to the request. It -is created at some point when handling a request, and closed before the -response is sent. - -.. code-block:: python - :caption: ``flaskr/db.py`` - - import sqlite3 - from datetime import datetime - - import click - from flask import current_app, g - - - def get_db(): - if 'db' not in g: - g.db = sqlite3.connect( - current_app.config['DATABASE'], - detect_types=sqlite3.PARSE_DECLTYPES - ) - g.db.row_factory = sqlite3.Row - - return g.db - - - def close_db(e=None): - db = g.pop('db', None) - - if db is not None: - db.close() - -:data:`g` is a special object that is unique for each request. It is -used to store data that might be accessed by multiple functions during -the request. The connection is stored and reused instead of creating a -new connection if ``get_db`` is called a second time in the same -request. - -:data:`current_app` is another special object that points to the Flask -application handling the request. Since you used an application factory, -there is no application object when writing the rest of your code. -``get_db`` will be called when the application has been created and is -handling a request, so :data:`current_app` can be used. - -:func:`sqlite3.connect` establishes a connection to the file pointed at -by the ``DATABASE`` configuration key. This file doesn't have to exist -yet, and won't until you initialize the database later. - -:class:`sqlite3.Row` tells the connection to return rows that behave -like dicts. This allows accessing the columns by name. - -``close_db`` checks if a connection was created by checking if ``g.db`` -was set. If the connection exists, it is closed. Further down you will -tell your application about the ``close_db`` function in the application -factory so that it is called after each request. - - -Create the Tables ------------------ - -In SQLite, data is stored in *tables* and *columns*. These need to be -created before you can store and retrieve data. Flaskr will store users -in the ``user`` table, and posts in the ``post`` table. Create a file -with the SQL commands needed to create empty tables: - -.. code-block:: sql - :caption: ``flaskr/schema.sql`` - - DROP TABLE IF EXISTS user; - DROP TABLE IF EXISTS post; - - CREATE TABLE user ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT UNIQUE NOT NULL, - password TEXT NOT NULL - ); - - CREATE TABLE post ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - author_id INTEGER NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - title TEXT NOT NULL, - body TEXT NOT NULL, - FOREIGN KEY (author_id) REFERENCES user (id) - ); - -Add the Python functions that will run these SQL commands to the -``db.py`` file: - -.. code-block:: python - :caption: ``flaskr/db.py`` - - def init_db(): - db = get_db() - - with current_app.open_resource('schema.sql') as f: - db.executescript(f.read().decode('utf8')) - - - @click.command('init-db') - def init_db_command(): - """Clear the existing data and create new tables.""" - init_db() - click.echo('Initialized the database.') - - - sqlite3.register_converter( - "timestamp", lambda v: datetime.fromisoformat(v.decode()) - ) - -:meth:`open_resource() ` opens a file relative to -the ``flaskr`` package, which is useful since you won't necessarily know -where that location is when deploying the application later. ``get_db`` -returns a database connection, which is used to execute the commands -read from the file. - -:func:`click.command` defines a command line command called ``init-db`` -that calls the ``init_db`` function and shows a success message to the -user. You can read :doc:`/cli` to learn more about writing commands. - -The call to :func:`sqlite3.register_converter` tells Python how to -interpret timestamp values in the database. We convert the value to a -:class:`datetime.datetime`. - - -Register with the Application ------------------------------ - -The ``close_db`` and ``init_db_command`` functions need to be registered -with the application instance; otherwise, they won't be used by the -application. However, since you're using a factory function, that -instance isn't available when writing the functions. Instead, write a -function that takes an application and does the registration. - -.. code-block:: python - :caption: ``flaskr/db.py`` - - def init_app(app): - app.teardown_appcontext(close_db) - app.cli.add_command(init_db_command) - -:meth:`app.teardown_appcontext() ` tells -Flask to call that function when cleaning up after returning the -response. - -:meth:`app.cli.add_command() ` adds a new -command that can be called with the ``flask`` command. - -Import and call this function from the factory. Place the new code at -the end of the factory function before returning the app. - -.. code-block:: python - :caption: ``flaskr/__init__.py`` - - def create_app(): - app = ... - # existing code omitted - - from . import db - db.init_app(app) - - return app - - -Initialize the Database File ----------------------------- - -Now that ``init-db`` has been registered with the app, it can be called -using the ``flask`` command, similar to the ``run`` command from the -previous page. - -.. note:: - - If you're still running the server from the previous page, you can - either stop the server, or run this command in a new terminal. If - you use a new terminal, remember to change to your project directory - and activate the env as described in :doc:`/installation`. - -Run the ``init-db`` command: - -.. code-block:: none - - $ flask --app flaskr init-db - Initialized the database. - -There will now be a ``flaskr.sqlite`` file in the ``instance`` folder in -your project. - -Continue to :doc:`views`. diff --git a/docs/tutorial/deploy.rst b/docs/tutorial/deploy.rst deleted file mode 100644 index eb3a53ac..00000000 --- a/docs/tutorial/deploy.rst +++ /dev/null @@ -1,111 +0,0 @@ -Deploy to Production -==================== - -This part of the tutorial assumes you have a server that you want to -deploy your application to. It gives an overview of how to create the -distribution file and install it, but won't go into specifics about -what server or software to use. You can set up a new environment on your -development computer to try out the instructions below, but probably -shouldn't use it for hosting a real public application. See -:doc:`/deploying/index` for a list of many different ways to host your -application. - - -Build and Install ------------------ - -When you want to deploy your application elsewhere, you build a *wheel* -(``.whl``) file. Install and use the ``build`` tool to do this. - -.. code-block:: none - - $ pip install build - $ python -m build --wheel - -You can find the file in ``dist/flaskr-1.0.0-py3-none-any.whl``. The -file name is in the format of {project name}-{version}-{python tag} --{abi tag}-{platform tag}. - -Copy this file to another machine, -:ref:`set up a new virtualenv `, then install the -file with ``pip``. - -.. code-block:: none - - $ pip install flaskr-1.0.0-py3-none-any.whl - -Pip will install your project along with its dependencies. - -Since this is a different machine, you need to run ``init-db`` again to -create the database in the instance folder. - - .. code-block:: text - - $ flask --app flaskr init-db - -When Flask detects that it's installed (not in editable mode), it uses -a different directory for the instance folder. You can find it at -``.venv/var/flaskr-instance`` instead. - - -Configure the Secret Key ------------------------- - -In the beginning of the tutorial that you gave a default value for -:data:`SECRET_KEY`. This should be changed to some random bytes in -production. Otherwise, attackers could use the public ``'dev'`` key to -modify the session cookie, or anything else that uses the secret key. - -You can use the following command to output a random secret key: - -.. code-block:: none - - $ python -c 'import secrets; print(secrets.token_hex())' - - '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' - -Create the ``config.py`` file in the instance folder, which the factory -will read from if it exists. Copy the generated value into it. - -.. code-block:: python - :caption: ``.venv/var/flaskr-instance/config.py`` - - SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' - -You can also set any other necessary configuration here, although -``SECRET_KEY`` is the only one needed for Flaskr. - - -Run with a Production Server ----------------------------- - -When running publicly rather than in development, you should not use the -built-in development server (``flask run``). The development server is -provided by Werkzeug for convenience, but is not designed to be -particularly efficient, stable, or secure. - -Instead, use a production WSGI server. For example, to use `Waitress`_, -first install it in the virtual environment: - -.. code-block:: none - - $ pip install waitress - -You need to tell Waitress about your application, but it doesn't use -``--app`` like ``flask run`` does. You need to tell it to import and -call the application factory to get an application object. - -.. code-block:: none - - $ waitress-serve --call 'flaskr:create_app' - - Serving on http://0.0.0.0:8080 - -See :doc:`/deploying/index` for a list of many different ways to host -your application. Waitress is just an example, chosen for the tutorial -because it supports both Windows and Linux. There are many more WSGI -servers and deployment options that you may choose for your project. - -.. _Waitress: https://docs.pylonsproject.org/projects/waitress/en/stable/ - -Continue to :doc:`next`. diff --git a/docs/tutorial/factory.rst b/docs/tutorial/factory.rst deleted file mode 100644 index 39febd13..00000000 --- a/docs/tutorial/factory.rst +++ /dev/null @@ -1,162 +0,0 @@ -.. currentmodule:: flask - -Application Setup -================= - -A Flask application is an instance of the :class:`Flask` class. -Everything about the application, such as configuration and URLs, will -be registered with this class. - -The most straightforward way to create a Flask application is to create -a global :class:`Flask` instance directly at the top of your code, like -how the "Hello, World!" example did on the previous page. While this is -simple and useful in some cases, it can cause some tricky issues as the -project grows. - -Instead of creating a :class:`Flask` instance globally, you will create -it inside a function. This function is known as the *application -factory*. Any configuration, registration, and other setup the -application needs will happen inside the function, then the application -will be returned. - - -The Application Factory ------------------------ - -It's time to start coding! Create the ``flaskr`` directory and add the -``__init__.py`` file. The ``__init__.py`` serves double duty: it will -contain the application factory, and it tells Python that the ``flaskr`` -directory should be treated as a package. - -.. code-block:: none - - $ mkdir flaskr - -.. code-block:: python - :caption: ``flaskr/__init__.py`` - - import os - - from flask import Flask - - - def create_app(test_config=None): - # create and configure the app - app = Flask(__name__, instance_relative_config=True) - app.config.from_mapping( - SECRET_KEY='dev', - DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), - ) - - if test_config is None: - # load the instance config, if it exists, when not testing - app.config.from_pyfile('config.py', silent=True) - else: - # load the test config if passed in - app.config.from_mapping(test_config) - - # ensure the instance folder exists - try: - os.makedirs(app.instance_path) - except OSError: - pass - - # a simple page that says hello - @app.route('/hello') - def hello(): - return 'Hello, World!' - - return app - -``create_app`` is the application factory function. You'll add to it -later in the tutorial, but it already does a lot. - -#. ``app = Flask(__name__, instance_relative_config=True)`` creates the - :class:`Flask` instance. - - * ``__name__`` is the name of the current Python module. The app - needs to know where it's located to set up some paths, and - ``__name__`` is a convenient way to tell it that. - - * ``instance_relative_config=True`` tells the app that - configuration files are relative to the - :ref:`instance folder `. The instance folder - is located outside the ``flaskr`` package and can hold local - data that shouldn't be committed to version control, such as - configuration secrets and the database file. - -#. :meth:`app.config.from_mapping() ` sets - some default configuration that the app will use: - - * :data:`SECRET_KEY` is used by Flask and extensions to keep data - safe. It's set to ``'dev'`` to provide a convenient value - during development, but it should be overridden with a random - value when deploying. - - * ``DATABASE`` is the path where the SQLite database file will be - saved. It's under - :attr:`app.instance_path `, which is the - path that Flask has chosen for the instance folder. You'll learn - more about the database in the next section. - -#. :meth:`app.config.from_pyfile() ` overrides - the default configuration with values taken from the ``config.py`` - file in the instance folder if it exists. For example, when - deploying, this can be used to set a real ``SECRET_KEY``. - - * ``test_config`` can also be passed to the factory, and will be - used instead of the instance configuration. This is so the tests - you'll write later in the tutorial can be configured - independently of any development values you have configured. - -#. :func:`os.makedirs` ensures that - :attr:`app.instance_path ` exists. Flask - doesn't create the instance folder automatically, but it needs to be - created because your project will create the SQLite database file - there. - -#. :meth:`@app.route() ` creates a simple route so you can - see the application working before getting into the rest of the - tutorial. It creates a connection between the URL ``/hello`` and a - function that returns a response, the string ``'Hello, World!'`` in - this case. - - -Run The Application -------------------- - -Now you can run your application using the ``flask`` command. From the -terminal, tell Flask where to find your application, then run it in -debug mode. Remember, you should still be in the top-level -``flask-tutorial`` directory, not the ``flaskr`` package. - -Debug mode shows an interactive debugger whenever a page raises an -exception, and restarts the server whenever you make changes to the -code. You can leave it running and just reload the browser page as you -follow the tutorial. - -.. code-block:: text - - $ flask --app flaskr run --debug - -You'll see output similar to this: - -.. code-block:: text - - * Serving Flask app "flaskr" - * Debug mode: on - * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) - * Restarting with stat - * Debugger is active! - * Debugger PIN: nnn-nnn-nnn - -Visit http://127.0.0.1:5000/hello in a browser and you should see the -"Hello, World!" message. Congratulations, you're now running your Flask -web application! - -If another program is already using port 5000, you'll see -``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the -server tries to start. See :ref:`address-already-in-use` for how to -handle that. - -Continue to :doc:`database`. diff --git a/docs/tutorial/flaskr_edit.png b/docs/tutorial/flaskr_edit.png deleted file mode 100644 index 6cd6e398..00000000 Binary files a/docs/tutorial/flaskr_edit.png and /dev/null differ diff --git a/docs/tutorial/flaskr_index.png b/docs/tutorial/flaskr_index.png deleted file mode 100644 index aa2b50f5..00000000 Binary files a/docs/tutorial/flaskr_index.png and /dev/null differ diff --git a/docs/tutorial/flaskr_login.png b/docs/tutorial/flaskr_login.png deleted file mode 100644 index d482c645..00000000 Binary files a/docs/tutorial/flaskr_login.png and /dev/null differ diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst deleted file mode 100644 index d5dc5b3c..00000000 --- a/docs/tutorial/index.rst +++ /dev/null @@ -1,64 +0,0 @@ -Tutorial -======== - -.. toctree:: - :caption: Contents: - :maxdepth: 1 - - layout - factory - database - views - templates - static - blog - install - tests - deploy - next - -This tutorial will walk you through creating a basic blog application -called Flaskr. Users will be able to register, log in, create posts, -and edit or delete their own posts. You will be able to package and -install the application on other computers. - -.. image:: flaskr_index.png - :align: center - :class: screenshot - :alt: screenshot of index page - -It's assumed that you're already familiar with Python. The `official -tutorial`_ in the Python docs is a great way to learn or review first. - -.. _official tutorial: https://docs.python.org/3/tutorial/ - -While it's designed to give a good starting point, the tutorial doesn't -cover all of Flask's features. Check out the :doc:`/quickstart` for an -overview of what Flask can do, then dive into the docs to find out more. -The tutorial only uses what's provided by Flask and Python. In another -project, you might decide to use :doc:`/extensions` or other libraries -to make some tasks simpler. - -.. image:: flaskr_login.png - :align: center - :class: screenshot - :alt: screenshot of login page - -Flask is flexible. It doesn't require you to use any particular project -or code layout. However, when first starting, it's helpful to use a more -structured approach. This means that the tutorial will require a bit of -boilerplate up front, but it's done to avoid many common pitfalls that -new developers encounter, and it creates a project that's easy to expand -on. Once you become more comfortable with Flask, you can step out of -this structure and take full advantage of Flask's flexibility. - -.. image:: flaskr_edit.png - :align: center - :class: screenshot - :alt: screenshot of edit page - -:gh:`The tutorial project is available as an example in the Flask -repository `, if you want to compare your project -with the final product as you follow the tutorial. - -Continue to :doc:`layout`. diff --git a/docs/tutorial/install.rst b/docs/tutorial/install.rst deleted file mode 100644 index db83e106..00000000 --- a/docs/tutorial/install.rst +++ /dev/null @@ -1,89 +0,0 @@ -Make the Project Installable -============================ - -Making your project installable means that you can build a *wheel* file and install that -in another environment, just like you installed Flask in your project's environment. -This makes deploying your project the same as installing any other library, so you're -using all the standard Python tools to manage everything. - -Installing also comes with other benefits that might not be obvious from -the tutorial or as a new Python user, including: - -* Currently, Python and Flask understand how to use the ``flaskr`` - package only because you're running from your project's directory. - Installing means you can import it no matter where you run from. - -* You can manage your project's dependencies just like other packages - do, so ``pip install yourproject.whl`` installs them. - -* Test tools can isolate your test environment from your development - environment. - -.. note:: - This is being introduced late in the tutorial, but in your future - projects you should always start with this. - - -Describe the Project --------------------- - -The ``pyproject.toml`` file describes your project and how to build it. - -.. code-block:: toml - :caption: ``pyproject.toml`` - - [project] - name = "flaskr" - version = "1.0.0" - description = "The basic blog app built in the Flask tutorial." - dependencies = [ - "flask", - ] - - [build-system] - requires = ["flit_core<4"] - build-backend = "flit_core.buildapi" - -See the official `Packaging tutorial `_ for more -explanation of the files and options used. - -.. _packaging tutorial: https://packaging.python.org/tutorials/packaging-projects/ - - -Install the Project -------------------- - -Use ``pip`` to install your project in the virtual environment. - -.. code-block:: none - - $ pip install -e . - -This tells pip to find ``pyproject.toml`` in the current directory and install the -project in *editable* or *development* mode. Editable mode means that as you make -changes to your local code, you'll only need to re-install if you change the metadata -about the project, such as its dependencies. - -You can observe that the project is now installed with ``pip list``. - -.. code-block:: none - - $ pip list - - Package Version Location - -------------- --------- ---------------------------------- - click 6.7 - Flask 1.0 - flaskr 1.0.0 /home/user/Projects/flask-tutorial - itsdangerous 0.24 - Jinja2 2.10 - MarkupSafe 1.0 - pip 9.0.3 - Werkzeug 0.14.1 - -Nothing changes from how you've been running your project so far. -``--app`` is still set to ``flaskr`` and ``flask run`` still runs -the application, but you can call it from anywhere, not just the -``flask-tutorial`` directory. - -Continue to :doc:`tests`. diff --git a/docs/tutorial/layout.rst b/docs/tutorial/layout.rst deleted file mode 100644 index 6f8e59f4..00000000 --- a/docs/tutorial/layout.rst +++ /dev/null @@ -1,110 +0,0 @@ -Project Layout -============== - -Create a project directory and enter it: - -.. code-block:: none - - $ mkdir flask-tutorial - $ cd flask-tutorial - -Then follow the :doc:`installation instructions ` to set -up a Python virtual environment and install Flask for your project. - -The tutorial will assume you're working from the ``flask-tutorial`` -directory from now on. The file names at the top of each code block are -relative to this directory. - ----- - -A Flask application can be as simple as a single file. - -.. code-block:: python - :caption: ``hello.py`` - - from flask import Flask - - app = Flask(__name__) - - - @app.route('/') - def hello(): - return 'Hello, World!' - -However, as a project gets bigger, it becomes overwhelming to keep all -the code in one file. Python projects use *packages* to organize code -into multiple modules that can be imported where needed, and the -tutorial will do this as well. - -The project directory will contain: - -* ``flaskr/``, a Python package containing your application code and - files. -* ``tests/``, a directory containing test modules. -* ``.venv/``, a Python virtual environment where Flask and other - dependencies are installed. -* Installation files telling Python how to install your project. -* Version control config, such as `git`_. You should make a habit of - using some type of version control for all your projects, no matter - the size. -* Any other project files you might add in the future. - -.. _git: https://git-scm.com/ - -By the end, your project layout will look like this: - -.. code-block:: none - - /home/user/Projects/flask-tutorial - ├── flaskr/ - │ ├── __init__.py - │ ├── db.py - │ ├── schema.sql - │ ├── auth.py - │ ├── blog.py - │ ├── templates/ - │ │ ├── base.html - │ │ ├── auth/ - │ │ │ ├── login.html - │ │ │ └── register.html - │ │ └── blog/ - │ │ ├── create.html - │ │ ├── index.html - │ │ └── update.html - │ └── static/ - │ └── style.css - ├── tests/ - │ ├── conftest.py - │ ├── data.sql - │ ├── test_factory.py - │ ├── test_db.py - │ ├── test_auth.py - │ └── test_blog.py - ├── .venv/ - ├── pyproject.toml - └── MANIFEST.in - -If you're using version control, the following files that are generated -while running your project should be ignored. There may be other files -based on the editor you use. In general, ignore files that you didn't -write. For example, with git: - -.. code-block:: none - :caption: ``.gitignore`` - - .venv/ - - *.pyc - __pycache__/ - - instance/ - - .pytest_cache/ - .coverage - htmlcov/ - - dist/ - build/ - *.egg-info/ - -Continue to :doc:`factory`. diff --git a/docs/tutorial/next.rst b/docs/tutorial/next.rst deleted file mode 100644 index d41e8ef2..00000000 --- a/docs/tutorial/next.rst +++ /dev/null @@ -1,38 +0,0 @@ -Keep Developing! -================ - -You've learned about quite a few Flask and Python concepts throughout -the tutorial. Go back and review the tutorial and compare your code with -the steps you took to get there. Compare your project to the -:gh:`example project `, which might look a bit -different due to the step-by-step nature of the tutorial. - -There's a lot more to Flask than what you've seen so far. Even so, -you're now equipped to start developing your own web applications. Check -out the :doc:`/quickstart` for an overview of what Flask can do, then -dive into the docs to keep learning. Flask uses `Jinja`_, `Click`_, -`Werkzeug`_, and `ItsDangerous`_ behind the scenes, and they all have -their own documentation too. You'll also be interested in -:doc:`/extensions` which make tasks like working with the database or -validating form data easier and more powerful. - -If you want to keep developing your Flaskr project, here are some ideas -for what to try next: - -* A detail view to show a single post. Click a post's title to go to - its page. -* Like / unlike a post. -* Comments. -* Tags. Clicking a tag shows all the posts with that tag. -* A search box that filters the index page by name. -* Paged display. Only show 5 posts per page. -* Upload an image to go along with a post. -* Format posts using Markdown. -* An RSS feed of new posts. - -Have fun and make awesome applications! - -.. _Jinja: https://palletsprojects.com/p/jinja/ -.. _Click: https://palletsprojects.com/p/click/ -.. _Werkzeug: https://palletsprojects.com/p/werkzeug/ -.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ diff --git a/docs/tutorial/static.rst b/docs/tutorial/static.rst deleted file mode 100644 index 8e76c409..00000000 --- a/docs/tutorial/static.rst +++ /dev/null @@ -1,72 +0,0 @@ -Static Files -============ - -The authentication views and templates work, but they look very plain -right now. Some `CSS`_ can be added to add style to the HTML layout you -constructed. The style won't change, so it's a *static* file rather than -a template. - -Flask automatically adds a ``static`` view that takes a path relative -to the ``flaskr/static`` directory and serves it. The ``base.html`` -template already has a link to the ``style.css`` file: - -.. code-block:: html+jinja - - {{ url_for('static', filename='style.css') }} - -Besides CSS, other types of static files might be files with JavaScript -functions, or a logo image. They are all placed under the -``flaskr/static`` directory and referenced with -``url_for('static', filename='...')``. - -This tutorial isn't focused on how to write CSS, so you can just copy -the following into the ``flaskr/static/style.css`` file: - -.. code-block:: css - :caption: ``flaskr/static/style.css`` - - html { font-family: sans-serif; background: #eee; padding: 1rem; } - body { max-width: 960px; margin: 0 auto; background: white; } - h1 { font-family: serif; color: #377ba8; margin: 1rem 0; } - a { color: #377ba8; } - hr { border: none; border-top: 1px solid lightgray; } - nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } - nav h1 { flex: auto; margin: 0; } - nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } - nav ul { display: flex; list-style: none; margin: 0; padding: 0; } - nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } - .content { padding: 0 1rem 1rem; } - .content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } - .content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } - .flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } - .post > header { display: flex; align-items: flex-end; font-size: 0.85em; } - .post > header > div:first-of-type { flex: auto; } - .post > header h1 { font-size: 1.5em; margin-bottom: 0; } - .post .about { color: slategray; font-style: italic; } - .post .body { white-space: pre-line; } - .content:last-child { margin-bottom: 0; } - .content form { margin: 1em 0; display: flex; flex-direction: column; } - .content label { font-weight: bold; margin-bottom: 0.5em; } - .content input, .content textarea { margin-bottom: 1em; } - .content textarea { min-height: 12em; resize: vertical; } - input.danger { color: #cc2f2e; } - input[type=submit] { align-self: start; min-width: 10em; } - -You can find a less compact version of ``style.css`` in the -:gh:`example code `. - -Go to http://127.0.0.1:5000/auth/login and the page should look like the -screenshot below. - -.. image:: flaskr_login.png - :align: center - :class: screenshot - :alt: screenshot of login page - -You can read more about CSS from `Mozilla's documentation `_. If -you change a static file, refresh the browser page. If the change -doesn't show up, try clearing your browser's cache. - -.. _CSS: https://developer.mozilla.org/docs/Web/CSS - -Continue to :doc:`blog`. diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst deleted file mode 100644 index 1a5535cc..00000000 --- a/docs/tutorial/templates.rst +++ /dev/null @@ -1,187 +0,0 @@ -.. currentmodule:: flask - -Templates -========= - -You've written the authentication views for your application, but if -you're running the server and try to go to any of the URLs, you'll see a -``TemplateNotFound`` error. That's because the views are calling -:func:`render_template`, but you haven't written the templates yet. -The template files will be stored in the ``templates`` directory inside -the ``flaskr`` package. - -Templates are files that contain static data as well as placeholders -for dynamic data. A template is rendered with specific data to produce a -final document. Flask uses the `Jinja`_ template library to render -templates. - -In your application, you will use templates to render `HTML`_ which -will display in the user's browser. In Flask, Jinja is configured to -*autoescape* any data that is rendered in HTML templates. This means -that it's safe to render user input; any characters they've entered that -could mess with the HTML, such as ``<`` and ``>`` will be *escaped* with -*safe* values that look the same in the browser but don't cause unwanted -effects. - -Jinja looks and behaves mostly like Python. Special delimiters are used -to distinguish Jinja syntax from the static data in the template. -Anything between ``{{`` and ``}}`` is an expression that will be output -to the final document. ``{%`` and ``%}`` denotes a control flow -statement like ``if`` and ``for``. Unlike Python, blocks are denoted -by start and end tags rather than indentation since static text within -a block could change indentation. - -.. _Jinja: https://jinja.palletsprojects.com/templates/ -.. _HTML: https://developer.mozilla.org/docs/Web/HTML - - -The Base Layout ---------------- - -Each page in the application will have the same basic layout around a -different body. Instead of writing the entire HTML structure in each -template, each template will *extend* a base template and override -specific sections. - -.. code-block:: html+jinja - :caption: ``flaskr/templates/base.html`` - - - {% block title %}{% endblock %} - Flaskr - - -
-
- {% block header %}{% endblock %} -
- {% for message in get_flashed_messages() %} -
{{ message }}
- {% endfor %} - {% block content %}{% endblock %} -
- -:data:`g` is automatically available in templates. Based on if -``g.user`` is set (from ``load_logged_in_user``), either the username -and a log out link are displayed, or links to register and log in -are displayed. :func:`url_for` is also automatically available, and is -used to generate URLs to views instead of writing them out manually. - -After the page title, and before the content, the template loops over -each message returned by :func:`get_flashed_messages`. You used -:func:`flash` in the views to show error messages, and this is the code -that will display them. - -There are three blocks defined here that will be overridden in the other -templates: - -#. ``{% block title %}`` will change the title displayed in the - browser's tab and window title. - -#. ``{% block header %}`` is similar to ``title`` but will change the - title displayed on the page. - -#. ``{% block content %}`` is where the content of each page goes, such - as the login form or a blog post. - -The base template is directly in the ``templates`` directory. To keep -the others organized, the templates for a blueprint will be placed in a -directory with the same name as the blueprint. - - -Register --------- - -.. code-block:: html+jinja - :caption: ``flaskr/templates/auth/register.html`` - - {% extends 'base.html' %} - - {% block header %} -

{% block title %}Register{% endblock %}

- {% endblock %} - - {% block content %} -
- - - - - -
- {% endblock %} - -``{% extends 'base.html' %}`` tells Jinja that this template should -replace the blocks from the base template. All the rendered content must -appear inside ``{% block %}`` tags that override blocks from the base -template. - -A useful pattern used here is to place ``{% block title %}`` inside -``{% block header %}``. This will set the title block and then output -the value of it into the header block, so that both the window and page -share the same title without writing it twice. - -The ``input`` tags are using the ``required`` attribute here. This tells -the browser not to submit the form until those fields are filled in. If -the user is using an older browser that doesn't support that attribute, -or if they are using something besides a browser to make requests, you -still want to validate the data in the Flask view. It's important to -always fully validate the data on the server, even if the client does -some validation as well. - - -Log In ------- - -This is identical to the register template except for the title and -submit button. - -.. code-block:: html+jinja - :caption: ``flaskr/templates/auth/login.html`` - - {% extends 'base.html' %} - - {% block header %} -

{% block title %}Log In{% endblock %}

- {% endblock %} - - {% block content %} -
- - - - - -
- {% endblock %} - - -Register A User ---------------- - -Now that the authentication templates are written, you can register a -user. Make sure the server is still running (``flask run`` if it's not), -then go to http://127.0.0.1:5000/auth/register. - -Try clicking the "Register" button without filling out the form and see -that the browser shows an error message. Try removing the ``required`` -attributes from the ``register.html`` template and click "Register" -again. Instead of the browser showing an error, the page will reload and -the error from :func:`flash` in the view will be shown. - -Fill out a username and password and you'll be redirected to the login -page. Try entering an incorrect username, or the correct username and -incorrect password. If you log in you'll get an error because there's -no ``index`` view to redirect to yet. - -Continue to :doc:`static`. diff --git a/docs/tutorial/tests.rst b/docs/tutorial/tests.rst deleted file mode 100644 index f4744cda..00000000 --- a/docs/tutorial/tests.rst +++ /dev/null @@ -1,559 +0,0 @@ -.. currentmodule:: flask - -Test Coverage -============= - -Writing unit tests for your application lets you check that the code -you wrote works the way you expect. Flask provides a test client that -simulates requests to the application and returns the response data. - -You should test as much of your code as possible. Code in functions only -runs when the function is called, and code in branches, such as ``if`` -blocks, only runs when the condition is met. You want to make sure that -each function is tested with data that covers each branch. - -The closer you get to 100% coverage, the more comfortable you can be -that making a change won't unexpectedly change other behavior. However, -100% coverage doesn't guarantee that your application doesn't have bugs. -In particular, it doesn't test how the user interacts with the -application in the browser. Despite this, test coverage is an important -tool to use during development. - -.. note:: - This is being introduced late in the tutorial, but in your future - projects you should test as you develop. - -You'll use `pytest`_ and `coverage`_ to test and measure your code. -Install them both: - -.. code-block:: none - - $ pip install pytest coverage - -.. _pytest: https://pytest.readthedocs.io/ -.. _coverage: https://coverage.readthedocs.io/ - - -Setup and Fixtures ------------------- - -The test code is located in the ``tests`` directory. This directory is -*next to* the ``flaskr`` package, not inside it. The -``tests/conftest.py`` file contains setup functions called *fixtures* -that each test will use. Tests are in Python modules that start with -``test_``, and each test function in those modules also starts with -``test_``. - -Each test will create a new temporary database file and populate some -data that will be used in the tests. Write a SQL file to insert that -data. - -.. code-block:: sql - :caption: ``tests/data.sql`` - - INSERT INTO user (username, password) - VALUES - ('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'), - ('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'); - - INSERT INTO post (title, body, author_id, created) - VALUES - ('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00'); - -The ``app`` fixture will call the factory and pass ``test_config`` to -configure the application and database for testing instead of using your -local development configuration. - -.. code-block:: python - :caption: ``tests/conftest.py`` - - import os - import tempfile - - import pytest - from flaskr import create_app - from flaskr.db import get_db, init_db - - with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f: - _data_sql = f.read().decode('utf8') - - - @pytest.fixture - def app(): - db_fd, db_path = tempfile.mkstemp() - - app = create_app({ - 'TESTING': True, - 'DATABASE': db_path, - }) - - with app.app_context(): - init_db() - get_db().executescript(_data_sql) - - yield app - - os.close(db_fd) - os.unlink(db_path) - - - @pytest.fixture - def client(app): - return app.test_client() - - - @pytest.fixture - def runner(app): - return app.test_cli_runner() - -:func:`tempfile.mkstemp` creates and opens a temporary file, returning -the file descriptor and the path to it. The ``DATABASE`` path is -overridden so it points to this temporary path instead of the instance -folder. After setting the path, the database tables are created and the -test data is inserted. After the test is over, the temporary file is -closed and removed. - -:data:`TESTING` tells Flask that the app is in test mode. Flask changes -some internal behavior so it's easier to test, and other extensions can -also use the flag to make testing them easier. - -The ``client`` fixture calls -:meth:`app.test_client() ` with the application -object created by the ``app`` fixture. Tests will use the client to make -requests to the application without running the server. - -The ``runner`` fixture is similar to ``client``. -:meth:`app.test_cli_runner() ` creates a runner -that can call the Click commands registered with the application. - -Pytest uses fixtures by matching their function names with the names -of arguments in the test functions. For example, the ``test_hello`` -function you'll write next takes a ``client`` argument. Pytest matches -that with the ``client`` fixture function, calls it, and passes the -returned value to the test function. - - -Factory -------- - -There's not much to test about the factory itself. Most of the code will -be executed for each test already, so if something fails the other tests -will notice. - -The only behavior that can change is passing test config. If config is -not passed, there should be some default configuration, otherwise the -configuration should be overridden. - -.. code-block:: python - :caption: ``tests/test_factory.py`` - - from flaskr import create_app - - - def test_config(): - assert not create_app().testing - assert create_app({'TESTING': True}).testing - - - def test_hello(client): - response = client.get('/hello') - assert response.data == b'Hello, World!' - -You added the ``hello`` route as an example when writing the factory at -the beginning of the tutorial. It returns "Hello, World!", so the test -checks that the response data matches. - - -Database --------- - -Within an application context, ``get_db`` should return the same -connection each time it's called. After the context, the connection -should be closed. - -.. code-block:: python - :caption: ``tests/test_db.py`` - - import sqlite3 - - import pytest - from flaskr.db import get_db - - - def test_get_close_db(app): - with app.app_context(): - db = get_db() - assert db is get_db() - - with pytest.raises(sqlite3.ProgrammingError) as e: - db.execute('SELECT 1') - - assert 'closed' in str(e.value) - -The ``init-db`` command should call the ``init_db`` function and output -a message. - -.. code-block:: python - :caption: ``tests/test_db.py`` - - def test_init_db_command(runner, monkeypatch): - class Recorder(object): - called = False - - def fake_init_db(): - Recorder.called = True - - monkeypatch.setattr('flaskr.db.init_db', fake_init_db) - result = runner.invoke(args=['init-db']) - assert 'Initialized' in result.output - assert Recorder.called - -This test uses Pytest's ``monkeypatch`` fixture to replace the -``init_db`` function with one that records that it's been called. The -``runner`` fixture you wrote above is used to call the ``init-db`` -command by name. - - -Authentication --------------- - -For most of the views, a user needs to be logged in. The easiest way to -do this in tests is to make a ``POST`` request to the ``login`` view -with the client. Rather than writing that out every time, you can write -a class with methods to do that, and use a fixture to pass it the client -for each test. - -.. code-block:: python - :caption: ``tests/conftest.py`` - - class AuthActions(object): - def __init__(self, client): - self._client = client - - def login(self, username='test', password='test'): - return self._client.post( - '/auth/login', - data={'username': username, 'password': password} - ) - - def logout(self): - return self._client.get('/auth/logout') - - - @pytest.fixture - def auth(client): - return AuthActions(client) - -With the ``auth`` fixture, you can call ``auth.login()`` in a test to -log in as the ``test`` user, which was inserted as part of the test -data in the ``app`` fixture. - -The ``register`` view should render successfully on ``GET``. On ``POST`` -with valid form data, it should redirect to the login URL and the user's -data should be in the database. Invalid data should display error -messages. - -.. code-block:: python - :caption: ``tests/test_auth.py`` - - import pytest - from flask import g, session - from flaskr.db import get_db - - - def test_register(client, app): - assert client.get('/auth/register').status_code == 200 - response = client.post( - '/auth/register', data={'username': 'a', 'password': 'a'} - ) - assert response.headers["Location"] == "/auth/login" - - with app.app_context(): - assert get_db().execute( - "SELECT * FROM user WHERE username = 'a'", - ).fetchone() is not None - - - @pytest.mark.parametrize(('username', 'password', 'message'), ( - ('', '', b'Username is required.'), - ('a', '', b'Password is required.'), - ('test', 'test', b'already registered'), - )) - def test_register_validate_input(client, username, password, message): - response = client.post( - '/auth/register', - data={'username': username, 'password': password} - ) - assert message in response.data - -:meth:`client.get() ` makes a ``GET`` request -and returns the :class:`Response` object returned by Flask. Similarly, -:meth:`client.post() ` makes a ``POST`` -request, converting the ``data`` dict into form data. - -To test that the page renders successfully, a simple request is made and -checked for a ``200 OK`` :attr:`~Response.status_code`. If -rendering failed, Flask would return a ``500 Internal Server Error`` -code. - -:attr:`~Response.headers` will have a ``Location`` header with the login -URL when the register view redirects to the login view. - -:attr:`~Response.data` contains the body of the response as bytes. If -you expect a certain value to render on the page, check that it's in -``data``. Bytes must be compared to bytes. If you want to compare text, -use :meth:`get_data(as_text=True) ` -instead. - -``pytest.mark.parametrize`` tells Pytest to run the same test function -with different arguments. You use it here to test different invalid -input and error messages without writing the same code three times. - -The tests for the ``login`` view are very similar to those for -``register``. Rather than testing the data in the database, -:data:`session` should have ``user_id`` set after logging in. - -.. code-block:: python - :caption: ``tests/test_auth.py`` - - def test_login(client, auth): - assert client.get('/auth/login').status_code == 200 - response = auth.login() - assert response.headers["Location"] == "/" - - with client: - client.get('/') - assert session['user_id'] == 1 - assert g.user['username'] == 'test' - - - @pytest.mark.parametrize(('username', 'password', 'message'), ( - ('a', 'test', b'Incorrect username.'), - ('test', 'a', b'Incorrect password.'), - )) - def test_login_validate_input(auth, username, password, message): - response = auth.login(username, password) - assert message in response.data - -Using ``client`` in a ``with`` block allows accessing context variables -such as :data:`session` after the response is returned. Normally, -accessing ``session`` outside of a request would raise an error. - -Testing ``logout`` is the opposite of ``login``. :data:`session` should -not contain ``user_id`` after logging out. - -.. code-block:: python - :caption: ``tests/test_auth.py`` - - def test_logout(client, auth): - auth.login() - - with client: - auth.logout() - assert 'user_id' not in session - - -Blog ----- - -All the blog views use the ``auth`` fixture you wrote earlier. Call -``auth.login()`` and subsequent requests from the client will be logged -in as the ``test`` user. - -The ``index`` view should display information about the post that was -added with the test data. When logged in as the author, there should be -a link to edit the post. - -You can also test some more authentication behavior while testing the -``index`` view. When not logged in, each page shows links to log in or -register. When logged in, there's a link to log out. - -.. code-block:: python - :caption: ``tests/test_blog.py`` - - import pytest - from flaskr.db import get_db - - - def test_index(client, auth): - response = client.get('/') - assert b"Log In" in response.data - assert b"Register" in response.data - - auth.login() - response = client.get('/') - assert b'Log Out' in response.data - assert b'test title' in response.data - assert b'by test on 2018-01-01' in response.data - assert b'test\nbody' in response.data - assert b'href="/1/update"' in response.data - -A user must be logged in to access the ``create``, ``update``, and -``delete`` views. The logged in user must be the author of the post to -access ``update`` and ``delete``, otherwise a ``403 Forbidden`` status -is returned. If a ``post`` with the given ``id`` doesn't exist, -``update`` and ``delete`` should return ``404 Not Found``. - -.. code-block:: python - :caption: ``tests/test_blog.py`` - - @pytest.mark.parametrize('path', ( - '/create', - '/1/update', - '/1/delete', - )) - def test_login_required(client, path): - response = client.post(path) - assert response.headers["Location"] == "/auth/login" - - - def test_author_required(app, client, auth): - # change the post author to another user - with app.app_context(): - db = get_db() - db.execute('UPDATE post SET author_id = 2 WHERE id = 1') - db.commit() - - auth.login() - # current user can't modify other user's post - assert client.post('/1/update').status_code == 403 - assert client.post('/1/delete').status_code == 403 - # current user doesn't see edit link - assert b'href="/1/update"' not in client.get('/').data - - - @pytest.mark.parametrize('path', ( - '/2/update', - '/2/delete', - )) - def test_exists_required(client, auth, path): - auth.login() - assert client.post(path).status_code == 404 - -The ``create`` and ``update`` views should render and return a -``200 OK`` status for a ``GET`` request. When valid data is sent in a -``POST`` request, ``create`` should insert the new post data into the -database, and ``update`` should modify the existing data. Both pages -should show an error message on invalid data. - -.. code-block:: python - :caption: ``tests/test_blog.py`` - - def test_create(client, auth, app): - auth.login() - assert client.get('/create').status_code == 200 - client.post('/create', data={'title': 'created', 'body': ''}) - - with app.app_context(): - db = get_db() - count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0] - assert count == 2 - - - def test_update(client, auth, app): - auth.login() - assert client.get('/1/update').status_code == 200 - client.post('/1/update', data={'title': 'updated', 'body': ''}) - - with app.app_context(): - db = get_db() - post = db.execute('SELECT * FROM post WHERE id = 1').fetchone() - assert post['title'] == 'updated' - - - @pytest.mark.parametrize('path', ( - '/create', - '/1/update', - )) - def test_create_update_validate(client, auth, path): - auth.login() - response = client.post(path, data={'title': '', 'body': ''}) - assert b'Title is required.' in response.data - -The ``delete`` view should redirect to the index URL and the post should -no longer exist in the database. - -.. code-block:: python - :caption: ``tests/test_blog.py`` - - def test_delete(client, auth, app): - auth.login() - response = client.post('/1/delete') - assert response.headers["Location"] == "/" - - with app.app_context(): - db = get_db() - post = db.execute('SELECT * FROM post WHERE id = 1').fetchone() - assert post is None - - -Running the Tests ------------------ - -Some extra configuration, which is not required but makes running tests with coverage -less verbose, can be added to the project's ``pyproject.toml`` file. - -.. code-block:: toml - :caption: ``pyproject.toml`` - - [tool.pytest.ini_options] - testpaths = ["tests"] - - [tool.coverage.run] - branch = true - source = ["flaskr"] - -To run the tests, use the ``pytest`` command. It will find and run all -the test functions you've written. - -.. code-block:: none - - $ pytest - - ========================= test session starts ========================== - platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0 - rootdir: /home/user/Projects/flask-tutorial - collected 23 items - - tests/test_auth.py ........ [ 34%] - tests/test_blog.py ............ [ 86%] - tests/test_db.py .. [ 95%] - tests/test_factory.py .. [100%] - - ====================== 24 passed in 0.64 seconds ======================= - -If any tests fail, pytest will show the error that was raised. You can -run ``pytest -v`` to get a list of each test function rather than dots. - -To measure the code coverage of your tests, use the ``coverage`` command -to run pytest instead of running it directly. - -.. code-block:: none - - $ coverage run -m pytest - -You can either view a simple coverage report in the terminal: - -.. code-block:: none - - $ coverage report - - Name Stmts Miss Branch BrPart Cover - ------------------------------------------------------ - flaskr/__init__.py 21 0 2 0 100% - flaskr/auth.py 54 0 22 0 100% - flaskr/blog.py 54 0 16 0 100% - flaskr/db.py 24 0 4 0 100% - ------------------------------------------------------ - TOTAL 153 0 44 0 100% - -An HTML report allows you to see which lines were covered in each file: - -.. code-block:: none - - $ coverage html - -This generates files in the ``htmlcov`` directory. Open -``htmlcov/index.html`` in your browser to see the report. - -Continue to :doc:`deploy`. diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst deleted file mode 100644 index 7092dbc2..00000000 --- a/docs/tutorial/views.rst +++ /dev/null @@ -1,305 +0,0 @@ -.. currentmodule:: flask - -Blueprints and Views -==================== - -A view function is the code you write to respond to requests to your -application. Flask uses patterns to match the incoming request URL to -the view that should handle it. The view returns data that Flask turns -into an outgoing response. Flask can also go the other direction and -generate a URL to a view based on its name and arguments. - - -Create a Blueprint ------------------- - -A :class:`Blueprint` is a way to organize a group of related views and -other code. Rather than registering views and other code directly with -an application, they are registered with a blueprint. Then the blueprint -is registered with the application when it is available in the factory -function. - -Flaskr will have two blueprints, one for authentication functions and -one for the blog posts functions. The code for each blueprint will go -in a separate module. Since the blog needs to know about authentication, -you'll write the authentication one first. - -.. code-block:: python - :caption: ``flaskr/auth.py`` - - import functools - - from flask import ( - Blueprint, flash, g, redirect, render_template, request, session, url_for - ) - from werkzeug.security import check_password_hash, generate_password_hash - - from flaskr.db import get_db - - bp = Blueprint('auth', __name__, url_prefix='/auth') - -This creates a :class:`Blueprint` named ``'auth'``. Like the application -object, the blueprint needs to know where it's defined, so ``__name__`` -is passed as the second argument. The ``url_prefix`` will be prepended -to all the URLs associated with the blueprint. - -Import and register the blueprint from the factory using -:meth:`app.register_blueprint() `. Place the -new code at the end of the factory function before returning the app. - -.. code-block:: python - :caption: ``flaskr/__init__.py`` - - def create_app(): - app = ... - # existing code omitted - - from . import auth - app.register_blueprint(auth.bp) - - return app - -The authentication blueprint will have views to register new users and -to log in and log out. - - -The First View: Register ------------------------- - -When the user visits the ``/auth/register`` URL, the ``register`` view -will return `HTML`_ with a form for them to fill out. When they submit -the form, it will validate their input and either show the form again -with an error message or create the new user and go to the login page. - -.. _HTML: https://developer.mozilla.org/docs/Web/HTML - -For now you will just write the view code. On the next page, you'll -write templates to generate the HTML form. - -.. code-block:: python - :caption: ``flaskr/auth.py`` - - @bp.route('/register', methods=('GET', 'POST')) - def register(): - if request.method == 'POST': - username = request.form['username'] - password = request.form['password'] - db = get_db() - error = None - - if not username: - error = 'Username is required.' - elif not password: - error = 'Password is required.' - - if error is None: - try: - db.execute( - "INSERT INTO user (username, password) VALUES (?, ?)", - (username, generate_password_hash(password)), - ) - db.commit() - except db.IntegrityError: - error = f"User {username} is already registered." - else: - return redirect(url_for("auth.login")) - - flash(error) - - return render_template('auth/register.html') - -Here's what the ``register`` view function is doing: - -#. :meth:`@bp.route ` associates the URL ``/register`` - with the ``register`` view function. When Flask receives a request - to ``/auth/register``, it will call the ``register`` view and use - the return value as the response. - -#. If the user submitted the form, - :attr:`request.method ` will be ``'POST'``. In this - case, start validating the input. - -#. :attr:`request.form ` is a special type of - :class:`dict` mapping submitted form keys and values. The user will - input their ``username`` and ``password``. - -#. Validate that ``username`` and ``password`` are not empty. - -#. If validation succeeds, insert the new user data into the database. - - - :meth:`db.execute ` takes a SQL - query with ``?`` placeholders for any user input, and a tuple of - values to replace the placeholders with. The database library - will take care of escaping the values so you are not vulnerable - to a *SQL injection attack*. - - - For security, passwords should never be stored in the database - directly. Instead, - :func:`~werkzeug.security.generate_password_hash` is used to - securely hash the password, and that hash is stored. Since this - query modifies data, - :meth:`db.commit() ` needs to be - called afterwards to save the changes. - - - An :exc:`sqlite3.IntegrityError` will occur if the username - already exists, which should be shown to the user as another - validation error. - -#. After storing the user, they are redirected to the login page. - :func:`url_for` generates the URL for the login view based on its - name. This is preferable to writing the URL directly as it allows - you to change the URL later without changing all code that links to - it. :func:`redirect` generates a redirect response to the generated - URL. - -#. If validation fails, the error is shown to the user. :func:`flash` - stores messages that can be retrieved when rendering the template. - -#. When the user initially navigates to ``auth/register``, or - there was a validation error, an HTML page with the registration - form should be shown. :func:`render_template` will render a template - containing the HTML, which you'll write in the next step of the - tutorial. - - -Login ------ - -This view follows the same pattern as the ``register`` view above. - -.. code-block:: python - :caption: ``flaskr/auth.py`` - - @bp.route('/login', methods=('GET', 'POST')) - def login(): - if request.method == 'POST': - username = request.form['username'] - password = request.form['password'] - db = get_db() - error = None - user = db.execute( - 'SELECT * FROM user WHERE username = ?', (username,) - ).fetchone() - - if user is None: - error = 'Incorrect username.' - elif not check_password_hash(user['password'], password): - error = 'Incorrect password.' - - if error is None: - session.clear() - session['user_id'] = user['id'] - return redirect(url_for('index')) - - flash(error) - - return render_template('auth/login.html') - -There are a few differences from the ``register`` view: - -#. The user is queried first and stored in a variable for later use. - - :meth:`~sqlite3.Cursor.fetchone` returns one row from the query. - If the query returned no results, it returns ``None``. Later, - :meth:`~sqlite3.Cursor.fetchall` will be used, which returns a list - of all results. - -#. :func:`~werkzeug.security.check_password_hash` hashes the submitted - password in the same way as the stored hash and securely compares - them. If they match, the password is valid. - -#. :data:`session` is a :class:`dict` that stores data across requests. - When validation succeeds, the user's ``id`` is stored in a new - session. The data is stored in a *cookie* that is sent to the - browser, and the browser then sends it back with subsequent requests. - Flask securely *signs* the data so that it can't be tampered with. - -Now that the user's ``id`` is stored in the :data:`session`, it will be -available on subsequent requests. At the beginning of each request, if -a user is logged in their information should be loaded and made -available to other views. - -.. code-block:: python - :caption: ``flaskr/auth.py`` - - @bp.before_app_request - def load_logged_in_user(): - user_id = session.get('user_id') - - if user_id is None: - g.user = None - else: - g.user = get_db().execute( - 'SELECT * FROM user WHERE id = ?', (user_id,) - ).fetchone() - -:meth:`bp.before_app_request() ` registers -a function that runs before the view function, no matter what URL is -requested. ``load_logged_in_user`` checks if a user id is stored in the -:data:`session` and gets that user's data from the database, storing it -on :data:`g.user `, which lasts for the length of the request. If -there is no user id, or if the id doesn't exist, ``g.user`` will be -``None``. - - -Logout ------- - -To log out, you need to remove the user id from the :data:`session`. -Then ``load_logged_in_user`` won't load a user on subsequent requests. - -.. code-block:: python - :caption: ``flaskr/auth.py`` - - @bp.route('/logout') - def logout(): - session.clear() - return redirect(url_for('index')) - - -Require Authentication in Other Views -------------------------------------- - -Creating, editing, and deleting blog posts will require a user to be -logged in. A *decorator* can be used to check this for each view it's -applied to. - -.. code-block:: python - :caption: ``flaskr/auth.py`` - - def login_required(view): - @functools.wraps(view) - def wrapped_view(**kwargs): - if g.user is None: - return redirect(url_for('auth.login')) - - return view(**kwargs) - - return wrapped_view - -This decorator returns a new view function that wraps the original view -it's applied to. The new function checks if a user is loaded and -redirects to the login page otherwise. If a user is loaded the original -view is called and continues normally. You'll use this decorator when -writing the blog views. - -Endpoints and URLs ------------------- - -The :func:`url_for` function generates the URL to a view based on a name -and arguments. The name associated with a view is also called the -*endpoint*, and by default it's the same as the name of the view -function. - -For example, the ``hello()`` view that was added to the app -factory earlier in the tutorial has the name ``'hello'`` and can be -linked to with ``url_for('hello')``. If it took an argument, which -you'll see later, it would be linked to using -``url_for('hello', who='World')``. - -When using a blueprint, the name of the blueprint is prepended to the -name of the function, so the endpoint for the ``login`` function you -wrote above is ``'auth.login'`` because you added it to the ``'auth'`` -blueprint. - -Continue to :doc:`templates`. diff --git a/docs/views.rst b/docs/views.rst deleted file mode 100644 index f2210270..00000000 --- a/docs/views.rst +++ /dev/null @@ -1,324 +0,0 @@ -Class-based Views -================= - -.. currentmodule:: flask.views - -This page introduces using the :class:`View` and :class:`MethodView` -classes to write class-based views. - -A class-based view is a class that acts as a view function. Because it -is a class, different instances of the class can be created with -different arguments, to change the behavior of the view. This is also -known as generic, reusable, or pluggable views. - -An example of where this is useful is defining a class that creates an -API based on the database model it is initialized with. - -For more complex API behavior and customization, look into the various -API extensions for Flask. - - -Basic Reusable View -------------------- - -Let's walk through an example converting a view function to a view -class. We start with a view function that queries a list of users then -renders a template to show the list. - -.. code-block:: python - - @app.route("/users/") - def user_list(): - users = User.query.all() - return render_template("users.html", users=users) - -This works for the user model, but let's say you also had more models -that needed list pages. You'd need to write another view function for -each model, even though the only thing that would change is the model -and template name. - -Instead, you can write a :class:`View` subclass that will query a model -and render a template. As the first step, we'll convert the view to a -class without any customization. - -.. code-block:: python - - from flask.views import View - - class UserList(View): - def dispatch_request(self): - users = User.query.all() - return render_template("users.html", objects=users) - - app.add_url_rule("/users/", view_func=UserList.as_view("user_list")) - -The :meth:`View.dispatch_request` method is the equivalent of the view -function. Calling :meth:`View.as_view` method will create a view -function that can be registered on the app with its -:meth:`~flask.Flask.add_url_rule` method. The first argument to -``as_view`` is the name to use to refer to the view with -:func:`~flask.url_for`. - -.. note:: - - You can't decorate the class with ``@app.route()`` the way you'd - do with a basic view function. - -Next, we need to be able to register the same view class for different -models and templates, to make it more useful than the original function. -The class will take two arguments, the model and template, and store -them on ``self``. Then ``dispatch_request`` can reference these instead -of hard-coded values. - -.. code-block:: python - - class ListView(View): - def __init__(self, model, template): - self.model = model - self.template = template - - def dispatch_request(self): - items = self.model.query.all() - return render_template(self.template, items=items) - -Remember, we create the view function with ``View.as_view()`` instead of -creating the class directly. Any extra arguments passed to ``as_view`` -are then passed when creating the class. Now we can register the same -view to handle multiple models. - -.. code-block:: python - - app.add_url_rule( - "/users/", - view_func=ListView.as_view("user_list", User, "users.html"), - ) - app.add_url_rule( - "/stories/", - view_func=ListView.as_view("story_list", Story, "stories.html"), - ) - - -URL Variables -------------- - -Any variables captured by the URL are passed as keyword arguments to the -``dispatch_request`` method, as they would be for a regular view -function. - -.. code-block:: python - - class DetailView(View): - def __init__(self, model): - self.model = model - self.template = f"{model.__name__.lower()}/detail.html" - - def dispatch_request(self, id) - item = self.model.query.get_or_404(id) - return render_template(self.template, item=item) - - app.add_url_rule( - "/users/", - view_func=DetailView.as_view("user_detail", User) - ) - - -View Lifetime and ``self`` --------------------------- - -By default, a new instance of the view class is created every time a -request is handled. This means that it is safe to write other data to -``self`` during the request, since the next request will not see it, -unlike other forms of global state. - -However, if your view class needs to do a lot of complex initialization, -doing it for every request is unnecessary and can be inefficient. To -avoid this, set :attr:`View.init_every_request` to ``False``, which will -only create one instance of the class and use it for every request. In -this case, writing to ``self`` is not safe. If you need to store data -during the request, use :data:`~flask.g` instead. - -In the ``ListView`` example, nothing writes to ``self`` during the -request, so it is more efficient to create a single instance. - -.. code-block:: python - - class ListView(View): - init_every_request = False - - def __init__(self, model, template): - self.model = model - self.template = template - - def dispatch_request(self): - items = self.model.query.all() - return render_template(self.template, items=items) - -Different instances will still be created each for each ``as_view`` -call, but not for each request to those views. - - -View Decorators ---------------- - -The view class itself is not the view function. View decorators need to -be applied to the view function returned by ``as_view``, not the class -itself. Set :attr:`View.decorators` to a list of decorators to apply. - -.. code-block:: python - - class UserList(View): - decorators = [cache(minutes=2), login_required] - - app.add_url_rule('/users/', view_func=UserList.as_view()) - -If you didn't set ``decorators``, you could apply them manually instead. -This is equivalent to: - -.. code-block:: python - - view = UserList.as_view("users_list") - view = cache(minutes=2)(view) - view = login_required(view) - app.add_url_rule('/users/', view_func=view) - -Keep in mind that order matters. If you're used to ``@decorator`` style, -this is equivalent to: - -.. code-block:: python - - @app.route("/users/") - @login_required - @cache(minutes=2) - def user_list(): - ... - - -Method Hints ------------- - -A common pattern is to register a view with ``methods=["GET", "POST"]``, -then check ``request.method == "POST"`` to decide what to do. Setting -:attr:`View.methods` is equivalent to passing the list of methods to -``add_url_rule`` or ``route``. - -.. code-block:: python - - class MyView(View): - methods = ["GET", "POST"] - - def dispatch_request(self): - if request.method == "POST": - ... - ... - - app.add_url_rule('/my-view', view_func=MyView.as_view('my-view')) - -This is equivalent to the following, except further subclasses can -inherit or change the methods. - -.. code-block:: python - - app.add_url_rule( - "/my-view", - view_func=MyView.as_view("my-view"), - methods=["GET", "POST"], - ) - - -Method Dispatching and APIs ---------------------------- - -For APIs it can be helpful to use a different function for each HTTP -method. :class:`MethodView` extends the basic :class:`View` to dispatch -to different methods of the class based on the request method. Each HTTP -method maps to a method of the class with the same (lowercase) name. - -:class:`MethodView` automatically sets :attr:`View.methods` based on the -methods defined by the class. It even knows how to handle subclasses -that override or define other methods. - -We can make a generic ``ItemAPI`` class that provides get (detail), -patch (edit), and delete methods for a given model. A ``GroupAPI`` can -provide get (list) and post (create) methods. - -.. code-block:: python - - from flask.views import MethodView - - class ItemAPI(MethodView): - init_every_request = False - - def __init__(self, model): - self.model = model - self.validator = generate_validator(model) - - def _get_item(self, id): - return self.model.query.get_or_404(id) - - def get(self, id): - item = self._get_item(id) - return jsonify(item.to_json()) - - def patch(self, id): - item = self._get_item(id) - errors = self.validator.validate(item, request.json) - - if errors: - return jsonify(errors), 400 - - item.update_from_json(request.json) - db.session.commit() - return jsonify(item.to_json()) - - def delete(self, id): - item = self._get_item(id) - db.session.delete(item) - db.session.commit() - return "", 204 - - class GroupAPI(MethodView): - init_every_request = False - - def __init__(self, model): - self.model = model - self.validator = generate_validator(model, create=True) - - def get(self): - items = self.model.query.all() - return jsonify([item.to_json() for item in items]) - - def post(self): - errors = self.validator.validate(request.json) - - if errors: - return jsonify(errors), 400 - - db.session.add(self.model.from_json(request.json)) - db.session.commit() - return jsonify(item.to_json()) - - def register_api(app, model, name): - item = ItemAPI.as_view(f"{name}-item", model) - group = GroupAPI.as_view(f"{name}-group", model) - app.add_url_rule(f"/{name}/", view_func=item) - app.add_url_rule(f"/{name}/", view_func=group) - - register_api(app, User, "users") - register_api(app, Story, "stories") - -This produces the following views, a standard REST API! - -================= ========== =================== -URL Method Description ------------------ ---------- ------------------- -``/users/`` ``GET`` List all users -``/users/`` ``POST`` Create a new user -``/users/`` ``GET`` Show a single user -``/users/`` ``PATCH`` Update a user -``/users/`` ``DELETE`` Delete a user -``/stories/`` ``GET`` List all stories -``/stories/`` ``POST`` Create a new story -``/stories/`` ``GET`` Show a single story -``/stories/`` ``PATCH`` Update a story -``/stories/`` ``DELETE`` Delete a story -================= ========== =================== diff --git a/docs/web-security.rst b/docs/web-security.rst deleted file mode 100644 index d742056f..00000000 --- a/docs/web-security.rst +++ /dev/null @@ -1,295 +0,0 @@ -Security Considerations -======================= - -Web applications face many types of potential security problems, and it can be -hard to get everything right, or even to know what "right" is in general. Flask -tries to solve a few of these things by default, but there are other parts you -may have to take care of yourself. Many of these solutions are tradeoffs, and -will depend on each application's specific needs and threat model. Many hosting -platforms may take care of certain types of problems without the need for the -Flask application to handle them. - -Resource Use ------------- - -A common category of attacks is "Denial of Service" (DoS or DDoS). This is a -very broad category, and different variants target different layers in a -deployed application. In general, something is done to increase how much -processing time or memory is used to handle each request, to the point where -there are not enough resources to handle legitimate requests. - -Flask provides a few configuration options to handle resource use. They can -also be set on individual requests to customize only that request. The -documentation for each goes into more detail. - -- :data:`MAX_CONTENT_LENGTH` or :attr:`.Request.max_content_length` controls - how much data will be read from a request. It is not set by default, - although it will still block truly unlimited streams unless the WSGI server - indicates support. -- :data:`MAX_FORM_MEMORY_SIZE` or :attr:`.Request.max_form_memory_size` - controls how large any non-file ``multipart/form-data`` field can be. It is - set to 500kB by default. -- :data:`MAX_FORM_PARTS` or :attr:`.Request.max_form_parts` controls how many - ``multipart/form-data`` fields can be parsed. It is set to 1000 by default. - Combined with the default `max_form_memory_size`, this means that a form - will occupy at most 500MB of memory. - -Regardless of these settings, you should also review what settings are available -from your operating system, container deployment (Docker etc), WSGI server, HTTP -server, and hosting platform. They typically have ways to set process resource -limits, timeouts, and other checks regardless of how Flask is configured. - -.. _security-xss: - -Cross-Site Scripting (XSS) --------------------------- - -Cross site scripting is the concept of injecting arbitrary HTML (and with -it JavaScript) into the context of a website. To remedy this, developers -have to properly escape text so that it cannot include arbitrary HTML -tags. For more information on that have a look at the Wikipedia article -on `Cross-Site Scripting -`_. - -Flask configures Jinja2 to automatically escape all values unless -explicitly told otherwise. This should rule out all XSS problems caused -in templates, but there are still other places where you have to be -careful: - -- generating HTML without the help of Jinja2 -- calling :class:`~markupsafe.Markup` on data submitted by users -- sending out HTML from uploaded files, never do that, use the - ``Content-Disposition: attachment`` header to prevent that problem. -- sending out textfiles from uploaded files. Some browsers are using - content-type guessing based on the first few bytes so users could - trick a browser to execute HTML. - -Another thing that is very important are unquoted attributes. While -Jinja2 can protect you from XSS issues by escaping HTML, there is one -thing it cannot protect you from: XSS by attribute injection. To counter -this possible attack vector, be sure to always quote your attributes with -either double or single quotes when using Jinja expressions in them: - -.. sourcecode:: html+jinja - - - -Why is this necessary? Because if you would not be doing that, an -attacker could easily inject custom JavaScript handlers. For example an -attacker could inject this piece of HTML+JavaScript: - -.. sourcecode:: html - - onmouseover=alert(document.cookie) - -When the user would then move with the mouse over the input, the cookie -would be presented to the user in an alert window. But instead of showing -the cookie to the user, a good attacker might also execute any other -JavaScript code. In combination with CSS injections the attacker might -even make the element fill out the entire page so that the user would -just have to have the mouse anywhere on the page to trigger the attack. - -There is one class of XSS issues that Jinja's escaping does not protect -against. The ``a`` tag's ``href`` attribute can contain a `javascript:` URI, -which the browser will execute when clicked if not secured properly. - -.. sourcecode:: html - - click here - click here - -To prevent this, you'll need to set the :ref:`security-csp` response header. - -Cross-Site Request Forgery (CSRF) ---------------------------------- - -Another big problem is CSRF. This is a very complex topic and I won't -outline it here in detail just mention what it is and how to theoretically -prevent it. - -If your authentication information is stored in cookies, you have implicit -state management. The state of "being logged in" is controlled by a -cookie, and that cookie is sent with each request to a page. -Unfortunately that includes requests triggered by 3rd party sites. If you -don't keep that in mind, some people might be able to trick your -application's users with social engineering to do stupid things without -them knowing. - -Say you have a specific URL that, when you sent ``POST`` requests to will -delete a user's profile (say ``http://example.com/user/delete``). If an -attacker now creates a page that sends a post request to that page with -some JavaScript they just have to trick some users to load that page and -their profiles will end up being deleted. - -Imagine you were to run Facebook with millions of concurrent users and -someone would send out links to images of little kittens. When users -would go to that page, their profiles would get deleted while they are -looking at images of fluffy cats. - -How can you prevent that? Basically for each request that modifies -content on the server you would have to either use a one-time token and -store that in the cookie **and** also transmit it with the form data. -After receiving the data on the server again, you would then have to -compare the two tokens and ensure they are equal. - -Why does Flask not do that for you? The ideal place for this to happen is -the form validation framework, which does not exist in Flask. - -.. _security-json: - -JSON Security -------------- - -In Flask 0.10 and lower, :func:`~flask.jsonify` did not serialize top-level -arrays to JSON. This was because of a security vulnerability in ECMAScript 4. - -ECMAScript 5 closed this vulnerability, so only extremely old browsers are -still vulnerable. All of these browsers have `other more serious -vulnerabilities -`_, so -this behavior was changed and :func:`~flask.jsonify` now supports serializing -arrays. - -Security Headers ----------------- - -Browsers recognize various response headers in order to control security. We -recommend reviewing each of the headers below for use in your application. -The `Flask-Talisman`_ extension can be used to manage HTTPS and the security -headers for you. - -.. _Flask-Talisman: https://github.com/GoogleCloudPlatform/flask-talisman - -HTTP Strict Transport Security (HSTS) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tells the browser to convert all HTTP requests to HTTPS, preventing -man-in-the-middle (MITM) attacks. :: - - response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' - -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security - -.. _security-csp: - -Content Security Policy (CSP) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tell the browser where it can load various types of resource from. This header -should be used whenever possible, but requires some work to define the correct -policy for your site. A very strict policy would be:: - - response.headers['Content-Security-Policy'] = "default-src 'self'" - -- https://csp.withgoogle.com/docs/index.html -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy - -X-Content-Type-Options -~~~~~~~~~~~~~~~~~~~~~~ - -Forces the browser to honor the response content type instead of trying to -detect it, which can be abused to generate a cross-site scripting (XSS) -attack. :: - - response.headers['X-Content-Type-Options'] = 'nosniff' - -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options - -X-Frame-Options -~~~~~~~~~~~~~~~ - -Prevents external sites from embedding your site in an ``iframe``. This -prevents a class of attacks where clicks in the outer frame can be translated -invisibly to clicks on your page's elements. This is also known as -"clickjacking". :: - - response.headers['X-Frame-Options'] = 'SAMEORIGIN' - -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options - -.. _security-cookie: - -Set-Cookie options -~~~~~~~~~~~~~~~~~~ - -These options can be added to a ``Set-Cookie`` header to improve their -security. Flask has configuration options to set these on the session cookie. -They can be set on other cookies too. - -- ``Secure`` limits cookies to HTTPS traffic only. -- ``HttpOnly`` protects the contents of cookies from being read with - JavaScript. -- ``SameSite`` restricts how cookies are sent with requests from - external sites. Can be set to ``'Lax'`` (recommended) or ``'Strict'``. - ``Lax`` prevents sending cookies with CSRF-prone requests from - external sites, such as submitting a form. ``Strict`` prevents sending - cookies with all external requests, including following regular links. - -:: - - app.config.update( - SESSION_COOKIE_SECURE=True, - SESSION_COOKIE_HTTPONLY=True, - SESSION_COOKIE_SAMESITE='Lax', - ) - - response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax') - -Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after -the given time, or the current time plus the age, respectively. If neither -option is set, the cookie will be removed when the browser is closed. :: - - # cookie expires after 10 minutes - response.set_cookie('snakes', '3', max_age=600) - -For the session cookie, if :attr:`session.permanent ` -is set, then :data:`PERMANENT_SESSION_LIFETIME` is used to set the expiration. -Flask's default cookie implementation validates that the cryptographic -signature is not older than this value. Lowering this value may help mitigate -replay attacks, where intercepted cookies can be sent at a later time. :: - - app.config.update( - PERMANENT_SESSION_LIFETIME=600 - ) - - @app.route('/login', methods=['POST']) - def login(): - ... - session.clear() - session['user_id'] = user.id - session.permanent = True - ... - -Use :class:`itsdangerous.TimedSerializer` to sign and validate other cookie -values (or any values that need secure signatures). - -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie - -.. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute - - -Copy/Paste to Terminal ----------------------- - -Hidden characters such as the backspace character (``\b``, ``^H``) can -cause text to render differently in HTML than how it is interpreted if -`pasted into a terminal `__. - -For example, ``import y\bose\bm\bi\bt\be\b`` renders as -``import yosemite`` in HTML, but the backspaces are applied when pasted -into a terminal, and it becomes ``import os``. - -If you expect users to copy and paste untrusted code from your site, -such as from comments posted by users on a technical blog, consider -applying extra filtering, such as replacing all ``\b`` characters. - -.. code-block:: python - - body = body.replace("\b", "") - -Most modern terminals will warn about and remove hidden characters when -pasting, so this isn't strictly necessary. It's also possible to craft -dangerous commands in other ways that aren't possible to filter. -Depending on your site's use case, it may be good to show a warning -about copying code in general.