Skip to content

Commit

Permalink
Request config (#2949)
Browse files Browse the repository at this point in the history
  • Loading branch information
asvetlov committed Apr 25, 2018
1 parent b25434f commit fb0106a
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGES/2949.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``request.config_dict`` for exposing nested applications data.
46 changes: 45 additions & 1 deletion aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import time
import weakref
from collections import namedtuple
from collections.abc import Mapping
from contextlib import suppress
from math import ceil
from pathlib import Path
Expand All @@ -30,7 +31,7 @@
from .log import client_logger


__all__ = ('BasicAuth',)
__all__ = ('BasicAuth', 'ChainMapProxy')

PY_36 = sys.version_info >= (3, 6)
PY_37 = sys.version_info >= (3, 7)
Expand Down Expand Up @@ -741,3 +742,46 @@ def set_result(fut, result):
def set_exception(fut, exc):
if not fut.done():
fut.set_exception(exc)


class ChainMapProxy(Mapping):
__slots__ = ('_maps',)

def __init__(self, maps):
self._maps = tuple(maps)

def __init_subclass__(cls):
raise TypeError("Inheritance class {} from ChainMapProxy "
"is forbidden".format(cls.__name__))

def __getitem__(self, key):
for mapping in self._maps:
try:
return mapping[key]
except KeyError:
pass
raise KeyError(key)

def get(self, key, default=None):
return self[key] if key in self else default

def __len__(self):
# reuses stored hash values if possible
return len(set().union(*self._maps))

def __iter__(self):
d = {}
for mapping in reversed(self._maps):
# reuses stored hash values if possible
d.update(mapping)
return iter(d)

def __contains__(self, key):
return any(key in m for m in self._maps)

def __bool__(self):
return any(self._maps)

def __repr__(self):
content = ", ".join(map(repr, self._maps))
return 'ChainMapProxy({})'.format(content)
10 changes: 9 additions & 1 deletion aiohttp/web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from yarl import URL

from . import hdrs, multipart
from .helpers import DEBUG, HeadersMixin, reify, sentinel
from .helpers import DEBUG, ChainMapProxy, HeadersMixin, reify, sentinel
from .streams import EmptyStreamReader
from .web_exceptions import HTTPRequestEntityTooLarge

Expand Down Expand Up @@ -664,6 +664,14 @@ def app(self):
"""Application instance."""
return self._match_info.current_app

@property
def config_dict(self):
lst = self._match_info.apps
app = self.app
idx = lst.index(app)
sublist = list(reversed(lst[:idx + 1]))
return ChainMapProxy(sublist)

async def _prepare_hook(self, response):
match_info = self._match_info
if match_info is None:
Expand Down
55 changes: 55 additions & 0 deletions docs/structures.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.. _aiohttp-structures:


Common data structures
======================

.. module:: aiohttp

.. currentmodule:: aiohttp


Common data structures used by *aiohttp* internally.


FrozenList
----------

A list-like structure which implements
:class:`collections.abc.MutableSequence`.

The list is *mutable* unless :meth:`FrozenList.freeze` is called,
after that the list modification raises :exc:`RuntimeError`.


.. class:: FrozenList(items)

Construct a new *non-frozen* list from *items* iterable.

The list implements all :class:`collections.abc.MutableSequence`
methods plus two additional APIs.

.. attribute:: frozen

A read-only property, ``True`` is the list is *frozen*
(modifications are forbidden).

.. method:: freeze()

Freeze the list. There is no way to *thaw* it back.


ChainMapProxy
-------------

An *immutable* version of :class:`collections.ChainMap`. Internally
the proxy is a list of mappings (dictionaries), if the requested key
is not present in the first mapping the second is looked up and so on.

The class supports :class:`collections.abc.Mapping` interface.

.. class:: ChainMapProxy(maps)

Create a new chained mapping proxy from a list of mappings (*maps*).

.. versionadded:: 3.2
1 change: 1 addition & 0 deletions docs/utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ Miscellaneous API Shared between Client And Server.
multipart_reference
streams
signals
structures
websocket_utilities
74 changes: 51 additions & 23 deletions docs/web_advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ internal data structures and could terminate them gracefully::
app.router.add_get('/', handler)

All not finished jobs will be terminated on
:attr:`aiohttp.web.Application.on_cleanup` signal.
:attr:`Application.on_cleanup` signal.

To prevent cancellation of the whole :term:`web-handler` use
``@atomic`` decorator::
Expand Down Expand Up @@ -306,7 +306,7 @@ handling every incoming request.
.. warning::

Parallel reads from websocket are forbidden, there is no
possibility to call :meth:`aiohttp.web.WebSocketResponse.receive`
possibility to call :meth:`WebSocketResponse.receive`
from two tasks.

See :ref:`FAQ section <aiohttp_faq_parallel_event_sources>` for
Expand All @@ -321,12 +321,18 @@ Data Sharing aka No Singletons Please
:mod:`aiohttp.web` discourages the use of *global variables*, aka *singletons*.
Every variable should have its own context that is *not global*.

So, :class:`aiohttp.web.Application` and :class:`aiohttp.web.Request`
So, :class:`Application` and :class:`Request`
support a :class:`collections.abc.MutableMapping` interface (i.e. they are
dict-like objects), allowing them to be used as data stores.


.. _aiohttp-web-data-sharing-app-config:

Application's config
^^^^^^^^^^^^^^^^^^^^

For storing *global-like* variables, feel free to save them in an
:class:`~.Application` instance::
:class:`Application` instance::

app['my_private_key'] = data

Expand All @@ -335,8 +341,24 @@ and get it back in the :term:`web-handler`::
async def handler(request):
data = request.app['my_private_key']

Variables that are only needed for the lifetime of a :class:`~.Request`, can be
stored in a :class:`~.Request`::
In case of :ref:`nested applications
<aiohttp-web-nested-applications>` the desired lookup strategy could
be the following:

1. Search the key in the current nested application.
2. If the key is not found continue searching in the parent application(s).

For this please use :attr:`Request.config_dict` read-only property::

async def handler(request):
data = request.config_dict['my_private_key']


Request's storage
^^^^^^^^^^^^^^^^^

Variables that are only needed for the lifetime of a :class:`Request`, can be
stored in a :class:`Request`::

async def handler(request):
request['my_private_key'] = "data"
Expand All @@ -346,7 +368,10 @@ This is mostly useful for :ref:`aiohttp-web-middlewares` and
:ref:`aiohttp-web-signals` handlers to store data for further processing by the
next handlers in the chain.

:class:`aiohttp.web.StreamResponse` and :class:`aiohttp.web.Response` objects
Response's storage
^^^^^^^^^^^^^^^^^^

:class:`StreamResponse` and :class:`Response` objects
also support :class:`collections.abc.MutableMapping` interface. This is useful
when you want to share data with signals and middlewares once all the work in
the handler is done::
Expand All @@ -357,6 +382,9 @@ the handler is done::
return response


Naming hint
^^^^^^^^^^^

To avoid clashing with other *aiohttp* users and third-party libraries, please
choose a unique key name for storing data.

Expand Down Expand Up @@ -392,8 +420,8 @@ response. For example, here's a simple *middleware* which appends
Every *middleware* should accept two parameters, a :class:`request
<Request>` instance and a *handler*, and return the response or raise
an exception. If the exception is not an instance of
:exc:`aiohttp.web.HTTPException` it is converted to ``500``
:exc:`aiohttp.web.HTTPInternalServerError` after processing the
:exc:`HTTPException` it is converted to ``500``
:exc:`HTTPInternalServerError` after processing the
middlewares chain.

.. warning::
Expand Down Expand Up @@ -507,19 +535,19 @@ has been prepared, they can't customize a :class:`Response` **while** it's
being prepared. For this :mod:`aiohttp.web` provides *signals*.

For example, a middleware can only change HTTP headers for *unprepared*
responses (see :meth:`~aiohttp.web.StreamResponse.prepare`), but sometimes we
responses (see :meth:`StreamResponse.prepare`), but sometimes we
need a hook for changing HTTP headers for streamed responses and WebSockets.
This can be accomplished by subscribing to the
:attr:`~aiohttp.web.Application.on_response_prepare` signal::
:attr:`Application.on_response_prepare` signal::

async def on_prepare(request, response):
response.headers['My-Header'] = 'value'

app.on_response_prepare.append(on_prepare)


Additionally, the :attr:`~aiohttp.web.Application.on_startup` and
:attr:`~aiohttp.web.Application.on_cleanup` signals can be subscribed to for
Additionally, the :attr:`Application.on_startup` and
:attr:`Application.on_cleanup` signals can be subscribed to for
application component setup and tear down accordingly.

The following example will properly initialize and dispose an aiopg connection
Expand Down Expand Up @@ -621,7 +649,7 @@ toolbar URLs are served by prefix like ``/admin``.

Thus we'll create a totally separate application named ``admin`` and
connect it to main app with prefix by
:meth:`~aiohttp.web.Application.add_subapp`::
:meth:`Application.add_subapp`::

admin = web.Application()
# setup admin routes, signals and middlewares
Expand All @@ -635,13 +663,13 @@ It means that if URL is ``'/admin/something'`` middlewares from
the call chain.

The same is going for
:attr:`~aiohttp.web.Application.on_response_prepare` signal -- the
:attr:`Application.on_response_prepare` signal -- the
signal is delivered to both top level ``app`` and ``admin`` if
processing URL is routed to ``admin`` sub-application.

Common signals like :attr:`~aiohttp.web.Application.on_startup`,
:attr:`~aiohttp.web.Application.on_shutdown` and
:attr:`~aiohttp.web.Application.on_cleanup` are delivered to all
Common signals like :attr:`Application.on_startup`,
:attr:`Application.on_shutdown` and
:attr:`Application.on_cleanup` are delivered to all
registered sub-applications. The passed parameter is sub-application
instance, not top-level application.

Expand Down Expand Up @@ -895,8 +923,8 @@ As discussed in :ref:`aiohttp-deployment` the preferable way is
deploying *aiohttp* web server behind a *Reverse Proxy Server* like
:term:`nginx` for production usage.

In this way properties like :attr:`~BaseRequest.scheme`
:attr:`~BaseRequest.host` and :attr:`~BaseRequest.remote` are
In this way properties like :attr:`BaseRequest.scheme`
:attr:`BaseRequest.host` and :attr:`BaseRequest.remote` are
incorrect.

Real values should be given from proxy server, usually either
Expand All @@ -910,9 +938,9 @@ headers too, pushing non-trusted data values.
That's why *aiohttp server* should setup *forwarded* headers in custom
middleware in tight conjunction with *reverse proxy configuration*.

For changing :attr:`~BaseRequest.scheme` :attr:`~BaseRequest.host` and
:attr:`~BaseRequest.remote` the middleware might use
:meth:`~BaseRequest.clone`.
For changing :attr:`BaseRequest.scheme` :attr:`BaseRequest.host` and
:attr:`BaseRequest.remote` the middleware might use
:meth:`BaseRequest.clone`.

.. seealso::

Expand Down
11 changes: 11 additions & 0 deletions docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,16 @@ and :ref:`aiohttp-web-signals` handlers.
An :class:`Application` instance used to call :ref:`request handler
<aiohttp-web-handler>`, Read-only property.

.. attribute:: config_dict

A :class:`aiohttp.ChainMapProxy` instance for mapping all properties
from the current application returned by :attr:`app` property
and all its parents.

.. seealso:: :ref:`aiohttp-web-data-sharing-app-config`

.. versionadded:: 3.2

.. note::

You should never create the :class:`Request` instance manually
Expand All @@ -498,6 +508,7 @@ and :ref:`aiohttp-web-signals` handlers.




.. _aiohttp-web-response:


Expand Down
Loading

0 comments on commit fb0106a

Please sign in to comment.