@@ -121,6 +121,9 @@ def __init__(
121121 # The hourly request quota (``x-ratelimit-limit``), shown as the
122122 # denominator when the server reports it.
123123 self .rate_limit : str | None = None
124+ # Transient note shown while a sub-request backs off before a
125+ # retry; cleared by the next page/chunk so it doesn't linger.
126+ self .retry_note : str | None = None
124127 self ._last_len = 0
125128 # Whether anything was actually written to the stream — drives whether
126129 # close() needs a terminating newline. (``current_chunk`` is a poor
@@ -140,13 +143,33 @@ def start_chunk(self, index: int) -> None:
140143 avoids a premature "0 pages" frame before the first page arrives.
141144 """
142145 self .current_chunk = index
146+ self .retry_note = None
143147 if self .total_chunks > 1 :
144148 self ._render ()
145149
146150 def add_page (self , rows : int = 0 ) -> None :
147151 """Record one fetched page carrying ``rows`` rows and redraw."""
148152 self .pages += 1
149153 self .rows += int (rows )
154+ self .retry_note = None
155+ self ._render ()
156+
157+ def note_retry (self , * , attempt : int , wait : float ) -> None :
158+ """Show that a sub-request is backing off before retry ``attempt``.
159+
160+ Cleared by the next :meth:`add_page` / :meth:`start_chunk` (or by
161+ :meth:`close`) so the line returns to normal once the retry resolves.
162+ """
163+ # Keep sub-second waits explicit (avoid misleading ``0s``) while
164+ # rendering whole-second waits without unnecessary ``.0`` noise.
165+ # ``float()`` to support Python 3.9-3.11: ``round(int, 1)`` returns an
166+ # int and ``int.is_integer()`` (used below) only exists on 3.12+.
167+ wait_1dp = round (float (wait ), 1 )
168+ if wait_1dp < 1 or not wait_1dp .is_integer ():
169+ secs = f"{ wait_1dp :.1f} s"
170+ else :
171+ secs = f"{ wait_1dp :.0f} s"
172+ self .retry_note = f"retrying (attempt { attempt } , waiting { secs } )"
150173 self ._render ()
151174
152175 def set_rate_remaining (
@@ -179,6 +202,8 @@ def _format(self) -> str:
179202 else :
180203 segment = f"{ remaining } requests remaining"
181204 parts .append (segment )
205+ if self .retry_note is not None :
206+ parts .append (self .retry_note )
182207 if self .service :
183208 return f"Retrieving: { self .service } · " + " · " .join (parts )
184209 return "Progress: " + " · " .join (parts )
@@ -209,6 +234,13 @@ def close(self) -> None:
209234 """
210235 if self ._closed :
211236 return
237+ # A retry note set during the final backoff would otherwise freeze as
238+ # the persisted last line of a call that has since completed or given
239+ # up; clear it and redraw (while still un-closed, so ``_render`` runs)
240+ # so the final state isn't a stale "retrying".
241+ if self .enabled and self ._rendered and self .retry_note is not None :
242+ self .retry_note = None
243+ self ._render ()
212244 self ._closed = True
213245 if not (self .enabled and self ._rendered ):
214246 return
0 commit comments