Skip to content

Commit

Permalink
Rename coro_sync() to await_sync. (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
kristjanvalur authored Sep 3, 2023
1 parent 83c98e3 commit 2e0b5ea
Show file tree
Hide file tree
Showing 6 changed files with 20 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* Rename `coro_sync` to `await_sync`
* Add Monitor start() and try_await() methods
* Add support for PyPy
* Drop support for EOL Python 3.7
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ way Python's `asyncio` module does things.

- Helper tools for controlling coroutine execution, such as `CoroStart` and `Monitor`
- Utility classes such as `GeneratorObject`
- Coroutine helpers such as `coro_sync()`, `coro_iter()` and the `awaitmethod()` decorator
- Coroutine helpers such as `await_sync()`, `coro_iter()` and the `awaitmethod()` decorator
- Scheduling helpers for `asyncio`, and extended event-loop implementations
- _eager_ execution of Tasks
- Limited support for `anyio` and `trio`.
Expand Down Expand Up @@ -109,7 +109,7 @@ just as would happen if it were directly turned into a `Task`.

`func_eager()` is a decorator which automatically applies `coro_eager()` to the coroutine returned by an async function.

## `coro_sync()` - Running coroutines synchronously
## `await_sync()` - Running coroutines synchronously

If you are writing code which should work both synchronously and asynchronously,
you can now write the code fully _async_ and then run it _synchronously_ in the absence
Expand All @@ -124,7 +124,7 @@ async def async_get_processed_data(datagetter):

# raises SynchronousError if datagetter blocks
def sync_get_processed_data(datagetter):
return asynkit.coro_sync(async_get_processed_data(datagetter))
return asynkit.await_sync(async_get_processed_data(datagetter))
```

This sort of code might previously have been written thus:
Expand Down Expand Up @@ -160,14 +160,14 @@ is common in library code which needs to work both in synchronous and asynchrono
context. Needless to say, it is very convoluted, hard to debug and contains a lot
of code duplication where the same logic is repeated inside async helper closures.

Using `coro_sync()` it is possible to write the entire logic as `async` methods and
Using `await_sync()` it is possible to write the entire logic as `async` methods and
then simply fail if the code tries to invoke any truly async operations.
If the invoked coroutine blocks, a `SynchronousError` is raised _from_ a `SynchronousAbort` exception which
contains a traceback. This makes it easy to pinpoint the location in the code where the
async code blocked. If the code tries to access the event loop, e.g. by creating a `Task`, a `RuntimeError` will be raised.

The `syncfunction()` decorator can be used to automatically wrap an async function
so that it is executed using `coro_sync()`:
so that it is executed using `await_sync()`:

```pycon
>>> @asynkit.syncfunction
Expand All @@ -182,7 +182,7 @@ so that it is executed using `coro_sync()`:
```

the `asyncfunction()` utility can be used when passing synchronous callbacks to async
code, to make them async. This, along with `syncfunction()` and `coro_sync()`,
code, to make them async. This, along with `syncfunction()` and `await_sync()`,
can be used to integrate synchronous code with async middleware:

```python
Expand All @@ -197,7 +197,7 @@ for synchronous code, while avoiding the hybrid function _antipattern._

## `CoroStart`

This class manages the state of a partially run coroutine and is what what powers the `coro_eager()` and `coro_sync()` functions.
This class manages the state of a partially run coroutine and is what what powers the `coro_eager()` and `await_sync()` functions.
When initialized, it will _start_ the coroutine, running it until it either suspends, returns, or raises
an exception. It can subsequently be _awaited_ to retreive the result.

Expand Down Expand Up @@ -440,7 +440,7 @@ while True:
```

This pattern can even be employed in non-async applications, by using
the `coro_sync()` method instead of the `await` keyword to drive the `Monitor`.
the `await_sync()` method instead of the `await` keyword to drive the `Monitor`.

For a more complete example, have a look at `examples/test_resp.py`

Expand Down
4 changes: 2 additions & 2 deletions examples/example_middleware_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Fully asynchronous middleware, assuming async callables
and returning async results.
When used in synchronous environment, the entire middleware
is invoked using asynkit.coro_sync.
is invoked using asynkit.await_sync.
"""


Expand Down Expand Up @@ -37,7 +37,7 @@ def data_getter():
return "hello"

middleware = AsyncMiddleWare(data_getter=asynkit.asyncfunction(data_getter))
assert asynkit.coro_sync(middleware.get_processed_data()) == "hellohello"
assert asynkit.await_sync(middleware.get_processed_data()) == "hellohello"


def test_sync():
Expand Down
6 changes: 3 additions & 3 deletions examples/example_resp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from asynkit import Monitor, coro_sync
from asynkit import Monitor, await_sync

"""
Example of a stateful parser, implemented via recursive async calls,
Expand Down Expand Up @@ -145,10 +145,10 @@ def test_monitor_sync() -> None:
# create the parser
m: Monitor[Tuple[Any, bytes]] = Monitor()
parser = parse_resp_mon(m, b"")
coro_sync(m.start(parser))
await_sync(m.start(parser))

while True:
parsed = coro_sync(m.try_await(parser, chunks.pop(0)))
parsed = await_sync(m.try_await(parser, chunks.pop(0)))
if parsed is not None:
break

Expand Down
8 changes: 4 additions & 4 deletions src/asynkit/coroutine.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"coro_is_suspended",
"coro_is_finished",
"coro_iter",
"coro_sync",
"await_sync",
"SynchronousError",
"SynchronousAbort",
"asyncfunction",
Expand Down Expand Up @@ -497,7 +497,7 @@ def wrapper(self: S) -> Iterator[T]:
return wrapper


def coro_sync(coro: Coroutine[Any, Any, T]) -> T:
def await_sync(coro: Coroutine[Any, Any, T]) -> T:
"""Runs a corouting synchronlously. If the coroutine blocks, a
SynchronousError is raised.
"""
Expand All @@ -521,12 +521,12 @@ def coro_sync(coro: Coroutine[Any, Any, T]) -> T:

def syncfunction(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, T]:
"""Make an async function synchronous, by invoking
`coro_sync()` on its coroutine. Useful as a decorator.
`await_sync()` on its coroutine. Useful as a decorator.
"""

@functools.wraps(func)
def helper(*args: P.args, **kwargs: P.kwargs) -> T:
return coro_sync(func(*args, **kwargs))
return await_sync(func(*args, **kwargs))

return helper

Expand Down
4 changes: 2 additions & 2 deletions tests/test_coro.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ async def sync_genexit(self):
return await self.genexit()

def test_simple(self):
assert asynkit.coro_sync(self.simple()) == "simple"
assert asynkit.await_sync(self.simple()) == "simple"

def test_sync_simple(self):
assert self.sync_simple() == "simple"
Expand All @@ -581,7 +581,7 @@ def test_genexit(self):

def test_noexit(self):
with pytest.raises(asynkit.SynchronousError) as err:
asynkit.coro_sync(self.noexit())
asynkit.await_sync(self.noexit())
assert err.match("failed to complete synchronously")


Expand Down

0 comments on commit 2e0b5ea

Please sign in to comment.