later
Later is a Python library developed by Meta Platforms, Inc., offering a collection of "batteries included" for building `asyncio` services. It provides tools for unittesting asynchronous code, managing `asyncio` tasks, and handling asynchronous events. The current version is 26.1.1, and it maintains an active development status, with releases focusing on robust `asyncio` patterns.
Common errors
-
RuntimeWarning: coroutine 'my_coroutine' was never awaited
cause Attempting to run an asynchronous coroutine function without properly awaiting it, often seen in `unittest` methods that are not declared `async`.fixIf this error occurs in a test method, ensure the method is defined as `async def test_my_feature(self):`. If elsewhere, ensure you are using `await` or `asyncio.create_task()` to schedule the coroutine. -
TypeError: object NoneType can't be used in 'await' expression
cause This typically happens when an asynchronous function or operation that is expected to return a coroutine (and thus be awaited) actually returns `None` due to an error, early exit, or incorrect mock setup.fixInspect the function or mock returning `None`. Ensure all paths return a coroutine, a Future, or a Task. When mocking `async` context managers with `later.unittest.mock.AsyncContextManager`, confirm the mock correctly provides `__aenter__` and `__aexit__` methods. -
Task exception was never retrieved
cause An `asyncio` task raised an unhandled exception, and no code explicitly `await`ed the task to retrieve its result or handle the exception. This is a common `asyncio` footgun.fixWhen creating tasks (e.g., with `asyncio.create_task()` or `later.as_task`), always ensure they are `await`ed at some point (e.g., via `asyncio.gather()`, `later.Watcher.join()`, or by explicitly `await task`) to propagate exceptions or retrieve results.
Warnings
- gotcha Directly cancelling asyncio.Task objects using `task.cancel()` can lead to unawaited tasks and resource leaks if not handled carefully, especially in complex scenarios or when tasks create sub-tasks.
- gotcha When using `later.unittest.TestCase`, ensure your test methods are `async` functions. Forgetting `async def` will cause the test runner to treat them as regular synchronous methods, leading to `RuntimeWarning` for unawaited coroutines or incorrect test behavior.
- breaking While specific breaking changes are not extensively documented publicly for minor versions, being an `asyncio` utility, internal changes to `asyncio` in new Python versions (e.g., Python 3.11, 3.12, 3.13) could potentially necessitate updates in `later`'s implementation or usage patterns, particularly for advanced features interacting directly with the `asyncio` event loop.
Install
-
pip install later
Imports
- TestCase
from later.unittest import TestCase
- AsyncContextManager
from later.unittest.mock import AsyncContextManager
- cancel
from later import cancel
- as_task
from later import as_task
- Watcher
from later import Watcher
- herd
from later import herd
- BiDirectionalEvent
from later.event import BiDirectionalEvent
Quickstart
import asyncio
from later import as_task, Watcher, cancel
async def worker(name, delay):
print(f"{name}: Starting work...")
try:
await asyncio.sleep(delay)
print(f"{name}: Work done.")
except asyncio.CancelledError:
print(f"{name}: Work cancelled.")
async def main():
watcher = Watcher()
task1 = await watcher.spawn(worker('Worker 1', 2))
task2 = await watcher.spawn(worker('Worker 2', 5))
await asyncio.sleep(1) # Let tasks start
print("Main: Cancelling Worker 1")
await cancel(task1)
# Wait for all tasks in the watcher to complete or be cancelled
await watcher.join()
print("Main: All tasks managed by Watcher have completed.")
if __name__ == "__main__":
asyncio.run(main())