Skip to content

Commit

Permalink
docs: Update docs for the defer module.
Browse files Browse the repository at this point in the history
  • Loading branch information
achimnol committed Feb 25, 2020
1 parent 696fa17 commit 82ceb4c
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 5 deletions.
10 changes: 10 additions & 0 deletions docs/aiotools.defer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Async Deferred Function Tools
=============================

.. automodule:: aiotools.defer

.. currentmodule:: aiotools.defer

.. autofunction:: aiotools.defer.defer

.. autofunction:: aiotools.defer.adefer
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ aiotools Documentation
:maxdepth: 2

aiotools.context
aiotools.defer
aiotools.func
aiotools.iter
aiotools.server
Expand Down
69 changes: 64 additions & 5 deletions src/aiotools/defer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
"""
Provides a Golang-like ``defer()`` API using decorators, which allows grouping
resource initialization and cleanup in one place without extra indentations.
Example:
.. code-block:: python3
async def init(x):
...
async def cleanup(x):
...
@aiotools.adefer
async def do(defer): # <-- be aware of defer argument!
x = SomeResource()
await init(x)
defer(cleanup(x))
...
...
This is equivalent to:
.. code-block:: python3
async def do():
x = SomeResource()
await init(x)
try:
...
...
finally:
await cleanup(x)
Note that :class:`aiotools.context.AsyncContextGroup` or
:class:`contextlib.AsyncExitStack` serves well for the same purpose, but for simple
cleanups, this defer API makes your codes simple because it steps aside the main
execution context without extra indentations.
.. warning::
Any exception in the deferred functions is raised transparently, and may block
execution of the remaining deferred functions.
This behavior may be changed in the future versions, though.
"""

from collections import deque
import functools
import inspect
from typing import (
Expand All @@ -8,12 +56,16 @@


def defer(func):
"""
A synchronous version of the defer API.
It can only defer normal functions.
"""
assert not inspect.iscoroutinefunction(func), \
'the decorated function must not be async'

@functools.wraps(func)
def _wrapped(*args, **kwargs):
deferreds = []
deferreds = deque()

def defer(f: Callable) -> None:
assert not inspect.iscoroutinefunction(f), \
Expand All @@ -25,32 +77,39 @@ def defer(f: Callable) -> None:
try:
return func(defer, *args, **kwargs)
finally:
for f in reversed(deferreds):
while deferreds:
f = deferreds.pop()
f()

_wrapped.__wrapped__ = func
return _wrapped


def adefer(func):
"""
An asynchronous version of the defer API.
It can defer coroutine functions, coroutines, and normal functions.
"""
assert inspect.iscoroutinefunction(func), \
'the decorated function must be async'

@functools.wraps(func)
async def _wrapped(*args, **kwargs):
deferreds = []
deferreds = deque()

def defer(f: Union[Callable, Awaitable]) -> None:
deferreds.append(f)

try:
return await func(defer, *args, **kwargs)
finally:
for f in reversed(deferreds):
while deferreds:
f = deferreds.pop()
if inspect.iscoroutinefunction(f):
await f()
elif inspect.iscoroutine(f):
await f
else:
f()

_wrapped.__wrapped__ = func
return _wrapped

0 comments on commit 82ceb4c

Please sign in to comment.