{"id":9069,"library":"later","title":"later","description":"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.","status":"active","version":"26.1.1","language":"en","source_language":"en","source_url":"https://github.com/facebookincubator/later/","tags":["asyncio","testing","task management","concurrency","meta","utilities"],"install":[{"cmd":"pip install later","lang":"bash","label":"Install latest version"}],"dependencies":[],"imports":[{"note":"A specialized asyncio.IsolatedAsyncioTestCase that ensures tasks are not orphaned.","symbol":"TestCase","correct":"from later.unittest import TestCase"},{"note":"A factory for easily mocking AsyncContextManager instances in tests.","symbol":"AsyncContextManager","correct":"from later.unittest.mock import AsyncContextManager"},{"note":"The recommended way to cancel an asyncio.Task/Future and ensure it is awaited properly.","symbol":"cancel","correct":"from later import cancel"},{"note":"Decorator to turn coroutines into asyncio.Tasks automatically.","symbol":"as_task","correct":"from later import as_task"},{"note":"A task management utility similar to asyncio.TaskGroup, designed to watch tasks and act upon their completion or failure.","symbol":"Watcher","correct":"from later import Watcher"},{"note":"A decorator for coroutines providing basic thundering herd protection.","symbol":"herd","correct":"from later import herd"},{"note":"A two-way asyncio.Event for handshake-style synchronization between coroutines.","symbol":"BiDirectionalEvent","correct":"from later.event import BiDirectionalEvent"}],"quickstart":{"code":"import asyncio\nfrom later import as_task, Watcher, cancel\n\nasync def worker(name, delay):\n    print(f\"{name}: Starting work...\")\n    try:\n        await asyncio.sleep(delay)\n        print(f\"{name}: Work done.\")\n    except asyncio.CancelledError:\n        print(f\"{name}: Work cancelled.\")\n\nasync def main():\n    watcher = Watcher()\n    task1 = await watcher.spawn(worker('Worker 1', 2))\n    task2 = await watcher.spawn(worker('Worker 2', 5))\n\n    await asyncio.sleep(1) # Let tasks start\n    print(\"Main: Cancelling Worker 1\")\n    await cancel(task1)\n\n    # Wait for all tasks in the watcher to complete or be cancelled\n    await watcher.join()\n    print(\"Main: All tasks managed by Watcher have completed.\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n","lang":"python","description":"This quickstart demonstrates using `later.Watcher` to manage `asyncio` tasks and `later.cancel` for safe task cancellation. It spawns two worker coroutines, cancels one after a delay, and then waits for all managed tasks to finish."},"warnings":[{"fix":"Always prefer `from later import cancel` when cancelling tasks. `later.cancel` ensures that the task's cancellation is awaited, preventing orphaned tasks and allowing for proper cleanup.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Declare all test methods within `later.unittest.TestCase` as `async def test_something(self):` to ensure proper `asyncio` test execution and task management.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Always test `later` components thoroughly when upgrading to a new major Python version. Consult the `later` GitHub repository's issues and release notes for compatibility information with new Python releases.","message":"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.","severity":"breaking","affected_versions":"Major Python version upgrades (e.g., 3.10 to 3.11, 3.11 to 3.12)"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"If 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.","cause":"Attempting to run an asynchronous coroutine function without properly awaiting it, often seen in `unittest` methods that are not declared `async`.","error":"RuntimeWarning: coroutine 'my_coroutine' was never awaited"},{"fix":"Inspect 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.","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.","error":"TypeError: object NoneType can't be used in 'await' expression"},{"fix":"When 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.","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.","error":"Task exception was never retrieved"}]}