We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Concurrency with Threads and asyncio step 28 of 56
Fetch Many Pages with a TaskGroup
Write two functions that fetch many keys at the same time, with different
failure policies. fetch_all is all or nothing. fetch_all_lenient returns
partial results.
await fetch_all(fetch, ["a", "b", "a"])
# {"a": b"payload:a", "b": b"payload:b"}, and "a" is fetched once
await fetch_all_lenient(fetch, ["a", "b", "c"])
# if "b" fails: ({"a": b"payload:a", "c": b"payload:c"}, {"b": ValueError(...)})
Rules for both functions
async def fetch_all[T](
fetch: Callable[[str], Awaitable[T]], keys: Sequence[str]
) -> dict[str, T]
async def fetch_all_lenient[T](
fetch: Callable[[str], Awaitable[T]], keys: Sequence[str]
) -> tuple[dict[str, T], dict[str, BaseException]]
- Fetch each distinct key once, all at the same time, in one
asyncio.TaskGroup. - Build each returned dictionary in first-seen key order, with only the keys that belong in it.
fetchreturns anAwaitable, butcreate_taskneeds a coroutine. Write a smallasync defwrapper that awaitsfetch(key), and start the wrapper as the task. With the wrapper, an error from callingfetch(key)also stays inside the task.- Keep
Tin your types. - Empty
keysreturns{}fromfetch_alland({}, {})fromfetch_all_lenient.
fetch_all is all or nothing
- If a fetch raises, the group cancels the other fetches, waits for them, and
raises an
ExceptionGroup. Let it propagate. - On success, read each value with
task.result()after theasync withblock. - If a fetch raises
CancelledErroritself, its task ends cancelled and the group carries on. Reading that task's result raisesCancelledError. Let it propagate rather than leave the key out.
fetch_all_lenient returns partial results
- Every fetch runs to the end, even when another one fails.
- Return
(values, errors).valuesmaps each key that succeeded to its value, anderrorsmaps each key that failed to the exception it raised. An exception object thatfetchreturns is a value. - Catch
BaseExceptionin the wrapper, so that aCancelledErrora fetch raises itself goes intoerrorstoo. - If the caller is cancelled, cancel the fetches, wait for them, and let the
cancellation propagate instead of returning. A
TaskGrouparound the wrappers does this for you.
What the driver reports
solve builds a test fetch. The fetch for each key waits its hops count of
loop turns, then returns b"payload:" + key, or raises ValueError(f"fetch {key} failed")
if the key is in failing. A negative hop count makes the fetch raise
CancelledError(f"fetch {key} cancelled") itself. For a repeated key, the
driver uses the last hop count given for it.
valuesholds the values returned, and stays empty if the call raised.errorsshows each failure fromfetch_all_lenientas"Type: message".startedlists the keys whose fetch began, in order.cancelledlists, in sorted order, the keys whose fetch saw aCancelledError.outcomeis"ok","ExceptionGroup", or another exception's name. For a group,group_sizecounts its exceptions andgroup_typeslists their sorted type names.pendingcounts tasks still running after the call, apart from the driver. It must be0.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.