From b9b5afdafff75b70e9af62491edd001d3bb0ac47 Mon Sep 17 00:00:00 2001 From: Vincent Maillol Date: Tue, 14 Aug 2018 08:39:11 +0200 Subject: [PATCH 1/3] Maintain cleanup context and startup insertion order (#3191) --- CHANGES/3191.feature | 1 + CONTRIBUTORS.txt | 1 + aiohttp/web_app.py | 52 ++++++++++++++++++++++++++++++------------- docs/web_advanced.rst | 7 ++++++ tests/test_web_app.py | 37 ++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 CHANGES/3191.feature diff --git a/CHANGES/3191.feature b/CHANGES/3191.feature new file mode 100644 index 00000000000..291fd21c3da --- /dev/null +++ b/CHANGES/3191.feature @@ -0,0 +1 @@ +You can mix `app.on_startup` and `app.cleanup_ctx` usage, the insertion order is preserved. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 147d8cb996c..cd2434e1f52 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -198,6 +198,7 @@ Vamsi Krishna Avula Vasiliy Faronov Vasyl Baran Victor Kovtun +Vincent Maillol Vikas Kawadia Viktor Danyliuk Vitalik Verhovodov diff --git a/aiohttp/web_app.py b/aiohttp/web_app.py index acdc1d587ce..19e6ffa769e 100644 --- a/aiohttp/web_app.py +++ b/aiohttp/web_app.py @@ -74,8 +74,7 @@ def __init__(self, *, self._on_startup = Signal(self) # type: _AppSignal self._on_shutdown = Signal(self) # type: _AppSignal self._on_cleanup = Signal(self) # type: _AppSignal - self._cleanup_ctx = CleanupContext() - self._on_startup.append(self._cleanup_ctx._on_startup) + self._cleanup_ctx = CleanupContext(self._on_startup) self._on_cleanup.append(self._cleanup_ctx._on_cleanup) self._client_max_size = client_max_size @@ -378,28 +377,49 @@ def exceptions(self): class CleanupContext(FrozenList): - def __init__(self): - super().__init__() - self._exits = [] + class CleanupContextItem: + """ + CleanupContext uses this class to wrap an asynchronous generator + before adding to itself. + """ + + def __init__(self, cb): + self._cb = cb + self._iterator_at_exit = None - async def _on_startup(self, app): - for cb in self: - it = cb(app).__aiter__() + async def enter(self, app): + it = self._cb(app).__aiter__() await it.__anext__() - self._exits.append(it) + self._iterator_at_exit = it - async def _on_cleanup(self, app): - errors = [] - for it in reversed(self._exits): + async def exit(self, app): + if self._iterator_at_exit is None: + return try: - await it.__anext__() + await self._iterator_at_exit.__anext__() except StopAsyncIteration: pass + else: + raise RuntimeError("{!r} has more than one 'yield'" + .format(self._iterator_at_exit)) + + def __init__(self, on_startup): + super().__init__() + self._on_startup = on_startup + + def append(self, item): + cleanup_ctx_item = self.CleanupContextItem(item) + super().append(cleanup_ctx_item.exit) + self._on_startup.append(cleanup_ctx_item.enter) + + async def _on_cleanup(self, app): + errors = [] + for cb in reversed(self): + try: + await cb(app) except Exception as exc: errors.append(exc) - else: - errors.append(RuntimeError("{!r} has more than one 'yield'" - .format(it))) + if errors: if len(errors) == 1: raise errors[0] diff --git a/docs/web_advanced.rst b/docs/web_advanced.rst index 5f9981d5220..5975ea5339b 100644 --- a/docs/web_advanced.rst +++ b/docs/web_advanced.rst @@ -619,6 +619,13 @@ one ``yield``. *aiohttp* guarantees that *cleanup code* is called if and only if *startup code* was successfully finished. +*statup codes* are called in order of insertion. The order is preserved between +callbacks in *cleanup* signal and *statup codes*. + +*cleanup codes* are called in reverse order of insertion before any +callback in *cleanup* signal. + + Asynchronous generators are supported by Python 3.6+, on Python 3.5 please use `async_generator `_ library. diff --git a/tests/test_web_app.py b/tests/test_web_app.py index f210ed40e50..7f9d042924f 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -401,6 +401,43 @@ async def inner(app): assert out == ['pre_1', 'post_1'] +async def test_mixe_cleanup_ctx_on_startup_and_on_cleanup(): + app = web.Application() + out = [] + + def startup(num): + async def inner(app): + out.append('pre_' + str(num)) + return inner + + def cleanup(num): + async def inner(app): + out.append('post_' + str(num)) + return inner + + def cleanup_ctx(num): + @async_generator + async def inner(app): + out.append('pre_' + str(num)) + await yield_(None) + out.append('post_' + str(num)) + return inner + + app.on_startup.append(startup(1)) + app.cleanup_ctx.append(cleanup_ctx(2)) + app.on_startup.append(startup(3)) + app.cleanup_ctx.append(cleanup_ctx(4)) + app.on_startup.append(startup(5)) + + app.freeze() + await app.startup() + assert out == ['pre_1', 'pre_2', 'pre_3', 'pre_4', 'pre_5'] + + del out[:] + await app.cleanup() + assert out == ['post_4', 'post_2'] + + async def test_subapp_chained_config_dict_visibility(aiohttp_client): async def main_handler(request): From 3c5427a3df703bb0fbcaf50568e773aa4e3702b9 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 15 Oct 2018 18:48:12 +0300 Subject: [PATCH 2/3] Update web_app.py --- aiohttp/web_app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiohttp/web_app.py b/aiohttp/web_app.py index 19e6ffa769e..5faae6ee9f1 100644 --- a/aiohttp/web_app.py +++ b/aiohttp/web_app.py @@ -387,12 +387,12 @@ def __init__(self, cb): self._cb = cb self._iterator_at_exit = None - async def enter(self, app): + async def _enter(self, app): it = self._cb(app).__aiter__() await it.__anext__() self._iterator_at_exit = it - async def exit(self, app): + async def _exit(self, app): if self._iterator_at_exit is None: return try: @@ -409,8 +409,8 @@ def __init__(self, on_startup): def append(self, item): cleanup_ctx_item = self.CleanupContextItem(item) - super().append(cleanup_ctx_item.exit) - self._on_startup.append(cleanup_ctx_item.enter) + super().append(cleanup_ctx_item._exit) + self._on_startup.append(cleanup_ctx_item._enter) async def _on_cleanup(self, app): errors = [] From 22e00d3ca8471fcff427ee651dab8b4d88c46312 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 16 Oct 2018 12:03:56 +0300 Subject: [PATCH 3/3] Update CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 1cf29a569fb..dfc00399e84 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -207,9 +207,9 @@ Vamsi Krishna Avula Vasiliy Faronov Vasyl Baran Victor Kovtun -Vincent Maillol Vikas Kawadia Viktor Danyliuk +Vincent Maillol Vitalik Verhovodov Vitaly Haritonsky Vitaly Magerya