Skip to content

Commit

Permalink
Apply review comments.
Browse files Browse the repository at this point in the history
* Kept add_map() (formerly add_variables) and add_app() methods
  sepearte since they are often called in disjoint contexts where the
  other is out-of-scope.

* Renamed variable_maps to maps.

* Updated the docs related to add_subapp(), MatchInfo, and
  DynamicSubAppResource.
  • Loading branch information
achimnol committed Mar 1, 2018
1 parent 7b4b606 commit 4d33cba
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 28 deletions.
4 changes: 4 additions & 0 deletions aiohttp/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def apps(self):
def add_app(self, app):
"""Add application to the nested apps stack."""

@abstractmethod
def add_map(self, match_dict):
"""Add matched variables to the nested apps stack."""

@abstractmethod
def freeze(self):
"""Freeze the match info.
Expand Down
21 changes: 11 additions & 10 deletions aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,10 @@ async def handle_expect_header(self, request):
class UrlMappingMatchInfo(AbstractMatchInfo, Mapping):

def __init__(self, match_dict, route):
super().__init__()
self._route = route
self._apps = ()
self._variables = collections.ChainMap(match_dict)
self._current_app = None
self._variables = collections.ChainMap(match_dict)
self._frozen = False

@property
Expand Down Expand Up @@ -228,15 +227,17 @@ def set_current_app(self, app):
finally:
self._current_app = prev

def freeze(self):
self._frozen = True
def add_map(self, match_dict):
if self._frozen:
raise RuntimeError("Cannot change maps stack after .freeze() call")
self._variables.maps.append(match_dict)

@property
def variable_maps(self):
def maps(self):
return tuple(reversed(self._variables.maps))

def add_variables(self, match_dict):
self._variables.maps.append(match_dict)
def freeze(self):
self._frozen = True

def __getitem__(self, key):
return self._variables[key]
Expand All @@ -248,7 +249,7 @@ def __len__(self):
return self._variables.__len__()

def __repr__(self):
return "<MatchInfo {}: {}>".format(dict(self).__repr__(), self._route)
return "<MatchInfo {}: {}>".format(repr(self._variables), self._route)


class MatchInfoError(UrlMappingMatchInfo):
Expand Down Expand Up @@ -673,8 +674,8 @@ async def resolve(self, request):
if not request.url.raw_path.startswith(self._prefix):
return None, set()
match_info = await self._app.router.resolve(request)
match_info.add_variables({})
match_info.add_app(self._app)
match_info.add_map({})
if isinstance(match_info.http_exception, HTTPMethodNotAllowed):
methods = match_info.http_exception.allowed_methods
else:
Expand Down Expand Up @@ -724,8 +725,8 @@ async def resolve(self, request):
subrequest = request.clone(
rel_url=request.url.with_path(request.url.raw_path[mend:]))
match_info = await self._app.router.resolve(subrequest)
match_info.add_variables(mdict)
match_info.add_app(self._app)
match_info.add_map(mdict)
if isinstance(match_info.http_exception, HTTPMethodNotAllowed):
methods = match_info.http_exception.allowed_methods
else:
Expand Down
63 changes: 49 additions & 14 deletions docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ WebSocketResponse

:param int compress: sets specific level of compression for
single message,
``None`` for not overriding per-socket setting.
``None`` for not overriding per-socket setting.

:raise RuntimeError: if connection is not started or closing

Expand Down Expand Up @@ -1626,19 +1626,20 @@ Router is any object that implements :class:`AbstractRouter` interface.
In resolving process if request's path starts with *prefix* then
further resolving is passed to *subapp*.

You may use variables in *prefix*. If a matched resource in the
sub-application defines another variable with the same name, the
sub-application's match result will override the *prefix*'s match result.
In web handlers, all overridden results can be accessed via ``maps``
attribute of ``request.match_info`` object as it is a merged view
(:class:`collections.ChainMap`) of all nested dynamically prefixed
sub-applications in the reversed order.
You may use variables in *prefix*. ``request.match_info`` has the
matched variables defined by this *prefix* along with the variables
defined by the matched resource.

If there are variables with duplicate names in prefixes and resources,
the innermost values are exposed on the match info, but you still have
access to the overridden values via its ``maps`` property.

:param str prefix: path's prefix for the resource.

:param Application subapp: nested application attached under *prefix*.

:returns: a :class:`PrefixedSubAppResource` instance.
:returns: a :class:`PrefixedSubAppResource` or
:class:`DynamicSubAppResource` instance.

.. comethod:: resolve(request)

Expand Down Expand Up @@ -1899,6 +1900,17 @@ Resource classes hierarchy::
The call is not allowed, it raises :exc:`RuntimeError`.


.. class:: DynamicSubAppResource

A resource for serving nested applications with dynamic variables in its prefix.
The class instance is returned by
:class:`~aiohttp.web.Application.add_subapp` call.

.. method:: url_for(**kwargs)

The call is not allowed, it raises :exc:`RuntimeError`.


.. _aiohttp-web-route:

Route
Expand Down Expand Up @@ -2189,20 +2201,43 @@ In general the result may be any object derived from

.. class:: UrlMappingMatchInfo

Inherited from :class:`collections.ChainMap` and :class:`AbstractMatchInfo`.
Dict items are filled by matching info and is :term:`resource`\-specific.
Implements :class:`typing.Mapping` and :class:`AbstractMatchInfo`
interfaces. Mapping items are filled by matching variables defined
in the :term:`resource` paths.

.. attribute:: expect_handler

A coroutine for handling ``100-continue``.

.. attribute:: handler

A coroutine for handling request.
A coroutine for handling the request.

.. attribute:: route

:class:`Route` instance for url matching.
:class:`Route` instance matched with the URL and method.

.. attribute:: current_app

The innermost :class:`Application` instance matched through the resolving
process.

.. attribute:: apps

A tuple of :class:`Application` instances matched through the resolving
process. The innermost application comes at last.

.. attribute:: maps

A tuple of :class:`dict` instances where each instance corresponds to the
variables matched for each dynamic prefix of in the nested application
hierarchy. The match dict of the innermost match comes at last.
Static prefixes without any variable insert an empty dict here.

This provides a decomposed view of the match info, while the match info
provides a merged view where innermost variables override outer variables
with duplicate names defined from dynamic prefixes in different levels of
the hierarchy.


View
Expand Down Expand Up @@ -2540,7 +2575,7 @@ Utilities
.. versionadded:: 3.0

Support *access_log_class* parameter.

Support *reuse_address*, *reuse_port* parameter.


Expand Down
8 changes: 4 additions & 4 deletions tests/test_urldispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ async def test_regular_match_info(router):
req = make_request('GET', '/get/john')
match_info = await router.resolve(req)
assert {'name': 'john'} == match_info
assert re.match("<MatchInfo {'name': 'john'}: .+<Dynamic.+>>",
assert re.match(r"<MatchInfo ChainMap\({'name': 'john'}\): .+<Dynamic.+>>",
repr(match_info))


Expand Down Expand Up @@ -1134,9 +1134,9 @@ async def test_dynamic_subapp_resolution_overridden(app, loop):
ret = await resource.resolve(
make_mocked_request('GET', '/andrew/abc.py'))
assert 'abc' == ret[0]['name']
assert 'andrew' == ret[0].variable_maps[0]['name']
assert 'abc' == ret[0].variable_maps[1]['name']
assert 2 == len(ret[0].variable_maps)
assert 'andrew' == ret[0].maps[0]['name']
assert 'abc' == ret[0].maps[1]['name']
assert 2 == len(ret[0].maps)
assert set() == ret[1]


Expand Down

0 comments on commit 4d33cba

Please sign in to comment.