We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Concurrency with Threads and asyncio step 24 of 56
Build a Paginator You Can Close Early
Build Paginator, an async iterator over an API that returns results one
page at a time. It fetches the next page only when the caller needs another
item, and the caller can close it early.
# fetch(0) returns [1, 2], fetch(1) returns [3], fetch(2) returns []
paginator = Paginator(fetch)
async with aclosing(paginator):
async for item in paginator:
print(item) # 1, then 2, then 3
fetch(n) is a coroutine function that returns page n as a list. An empty
page means there are no more pages.
What to write
The starter already has the constructor. It stores fetch, sets _page to
0, makes an empty deque called _buffer, and sets _exhausted and closed
to False. Fill in three methods:
__aiter__()is a plaindefthat returnsself. It changes nothing.async def __anext__()returns the next item:- If
_bufferholds an item, remove the first one and return it. - Otherwise, if
closedor_exhaustedisTrue, raiseStopAsyncIterationwithout fetching. - Otherwise, await
self._fetch(self._page). When the fetch succeeds, add one to_page. Put the items of a nonempty page in the buffer and return the first one. For an empty page, set_exhaustedtoTrueand raiseStopAsyncIteration.
- If
async def aclose()setsclosedtoTrueand empties the buffer. A second call changes nothing. After it, every__anext__()raisesStopAsyncIterationwithout fetching.
Also follow these rules:
- Fetch only when the buffer is empty and the caller asks for an item. Never fetch ahead.
- An empty page ends the stream for good, even if later pages have items. Don't skip over it.
- If
fetchraises, let the error propagate and leave_pageas it was, so a retry asks for the same page.
What the driver reports
Keep _drive and solve as they are. The driver's fetch returns a copy of
pages[n], or [] past the last page, and counts its calls. The driver
loops over your paginator inside async with aclosing(paginator). If the
test passes stop_after, the loop breaks once it has that many items. After
the block, the driver calls await anext(paginator, sentinel) once more.
items: the items the loop collected, in order.fetch_calls: how many timesfetchran, counting the call that returned the empty page.closed:paginator.closedafter the block.aclosingcalled youraclose(), so this isTrue.after_close: the result of that lastanext. A closed paginator stops, so this issentinel, which is-1unless the test sets it.
With pages=[[1, 2], [3]], reading everything gives [1, 2, 3] after 3
fetches, two for the pages and one for the empty page. With stop_after=2
it takes 1 fetch, and with stop_after=3 it takes 2, because the loop stops
before it asks for a fourth item.
With pages=[] there is still 1 fetch. With pages=[[1], [], [2]] the
stream ends at the empty middle page, so the paginator never fetches [2].
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.