Skip to content

Commit ba6dc9f

Browse files
committed
fixed comments
1 parent 3a7e5fa commit ba6dc9f

File tree

4 files changed

+145
-3
lines changed

4 files changed

+145
-3
lines changed

google/api_core/retry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ class Retry(object):
313313
will be retried. Defaults to False.
314314
To avoid duplicate values, retryable streams should typically be
315315
wrapped in additional filter logic before use. For more details, see
316-
``google/api_core/retry_streaming.RetryaleGenerator``.
316+
``google.api_core.retry_streaming.retry_target_generator``.
317317
deadline (float): DEPRECATED: use `timeout` instead. For backward
318318
compatibility, if specified it will override the ``timeout`` parameter.
319319
"""

google/api_core/retry_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class AsyncRetry:
184184
function call itself will be retried. Defaults to False.
185185
To avoid duplicate values, retryable streams should typically be
186186
wrapped in additional filter logic before use. For more details, see
187-
``google.api_core.retry_streaming_async.AsyncRetryableGenerator``.
187+
``google.api_core.retry_streaming_async.retry_target_generator``.
188188
deadline (float): DEPRECATED use ``timeout`` instead. If set it will
189189
override ``timeout`` parameter.
190190
"""

google/api_core/retry_streaming.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,74 @@ def retry_target_generator(
8686
] = None,
8787
**kwargs,
8888
) -> Generator[T, Any, None]:
89+
"""
90+
Generator wrapper for retryable streaming RPCs.
91+
This function will be used when initilizing a retry with
92+
``Retry(is_stream=True)``.
93+
94+
When ``is_stream=False``, the target is treated as a callable,
95+
and will retry when the callable returns an error. When ``is_stream=True``,
96+
the target will be treated as a callable that retruns an iterable. Instead
97+
of just wrapping the initial call in retry logic, the entire iterable is
98+
wrapped, with each yield passing through the retryable generator. If any yield
99+
in the stream raises a retryable exception, the entire stream will be
100+
retried.
101+
102+
Important Note: when a stream is encounters a retryable error, it will
103+
silently construct a fresh iterator instance in the background
104+
and continue yielding (likely duplicate) values as if no error occurred.
105+
This is the most general way to retry a stream, but it often is not the
106+
desired behavior. Example: iter([1, 2, 1/0]) -> [1, 2, 1, 2, ...]
107+
108+
There are two ways to build more advanced retry logic for streams:
109+
110+
1. Wrap the target
111+
Use a ``target`` that maintains state between retries, and creates a
112+
different generator on each retry call. For example, you can wrap a
113+
network call in a function that modifies the request based on what has
114+
already been returned:
115+
116+
```
117+
def attempt_with_modified_request(target, request, seen_items=[]):
118+
# remove seen items from request on each attempt
119+
new_request = modify_request(request, seen_items)
120+
new_generator = target(new_request)
121+
for item in new_generator:
122+
yield item
123+
seen_items.append(item)
124+
125+
retry_wrapped = Retry(is_stream=True)(attempt_with_modified_request, target, request, [])
126+
```
127+
128+
2. Wrap the retry generator
129+
Alternatively, you can wrap the retryable generator itself before
130+
passing it to the end-user to add a filter on the stream. For
131+
example, you can keep track of the items that were successfully yielded
132+
in previous retry attempts, and only yield new items when the
133+
new attempt surpasses the previous ones:
134+
135+
``
136+
def retryable_with_filter(target):
137+
stream_idx = 0
138+
# reset stream_idx when the stream is retried
139+
def on_error(e):
140+
nonlocal stream_idx
141+
stream_idx = 0
142+
# build retryable
143+
retryable_gen = Retry(is_stream=True, on_error=on_error, ...)(target)
144+
# keep track of what has been yielded out of filter
145+
yielded_items = []
146+
for item in retryable_gen:
147+
if stream_idx >= len(yielded_items):
148+
yield item
149+
yielded_items.append(item)
150+
elif item != previous_stream[stream_idx]:
151+
raise ValueError("Stream differs from last attempt")"
152+
stream_idx += 1
153+
154+
filter_retry_wrapped = retryable_with_filter(target)
155+
```
156+
"""
89157
timeout = kwargs.get("deadline", timeout)
90158
deadline: Optional[float] = time.monotonic() + timeout if timeout else None
91159
error_list: List[Exception] = []

google/api_core/retry_streaming_async.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,19 @@
3636
from functools import partial
3737

3838
from google.api_core.retry_streaming import _build_timeout_error
39-
from google.api_core.retry_streaming import _TerminalException
4039

4140
_LOGGER = logging.getLogger(__name__)
4241

4342
T = TypeVar("T")
4443

4544

45+
class _TerminalException(Exception):
46+
"""
47+
Exception to bypasses retry logic and raises __cause__ immediately.
48+
"""
49+
pass
50+
51+
4652
async def retry_target_generator(
4753
target: Union[
4854
Callable[[], AsyncIterable[T]],
@@ -59,6 +65,74 @@ async def retry_target_generator(
5965
] = None,
6066
**kwargs,
6167
) -> AsyncGenerator[T, None]:
68+
"""
69+
Generator wrapper for retryable streaming RPCs.
70+
This function will be used when initilizing a retry with
71+
``AsyncRetry(is_stream=True)``.
72+
73+
When ``is_stream=False``, the target is treated as a coroutine,
74+
and will retry when the coroutine returns an error. When ``is_stream=True``,
75+
the target will be treated as a callable that retruns an AsyncIterable. Instead
76+
of just wrapping the initial call in retry logic, the entire iterable is
77+
wrapped, with each yield passing through the retryable generatpr. If any yield
78+
in the stream raises a retryable exception, the entire stream will be
79+
retried.
80+
81+
Important Note: when a stream is encounters a retryable error, it will
82+
silently construct a fresh iterator instance in the background
83+
and continue yielding (likely duplicate) values as if no error occurred.
84+
This is the most general way to retry a stream, but it often is not the
85+
desired behavior. Example: iter([1, 2, 1/0]) -> [1, 2, 1, 2, ...]
86+
87+
There are two ways to build more advanced retry logic for streams:
88+
89+
1. Wrap the target
90+
Use a ``target`` that maintains state between retries, and creates a
91+
different generator on each retry call. For example, you can wrap a
92+
grpc call in a function that modifies the request based on what has
93+
already been returned:
94+
95+
```
96+
async def attempt_with_modified_request(target, request, seen_items=[]):
97+
# remove seen items from request on each attempt
98+
new_request = modify_request(request, seen_items)
99+
new_generator = await target(new_request)
100+
async for item in new_generator:
101+
yield item
102+
seen_items.append(item)
103+
104+
retry_wrapped = AsyncRetry(is_stream=True)(attempt_with_modified_request, target, request, [])
105+
```
106+
107+
2. Wrap the retry generator
108+
Alternatively, you can wrap the retryable generator itself before
109+
passing it to the end-user to add a filter on the stream. For
110+
example, you can keep track of the items that were successfully yielded
111+
in previous retry attempts, and only yield new items when the
112+
new attempt surpasses the previous ones:
113+
114+
``
115+
async def retryable_with_filter(target):
116+
stream_idx = 0
117+
# reset stream_idx when the stream is retried
118+
def on_error(e):
119+
nonlocal stream_idx
120+
stream_idx = 0
121+
# build retryable
122+
retryable_gen = AsyncRetry(is_stream=True, on_error=on_error, ...)(target)
123+
# keep track of what has been yielded out of filter
124+
yielded_items = []
125+
async for item in retryable_gen:
126+
if stream_idx >= len(yielded_items):
127+
yield item
128+
yielded_items.append(item)
129+
elif item != previous_stream[stream_idx]:
130+
raise ValueError("Stream differs from last attempt")"
131+
stream_idx += 1
132+
133+
filter_retry_wrapped = retryable_with_filter(target)
134+
```
135+
"""
62136
subgenerator = None
63137

64138
timeout = kwargs.get("deadline", timeout)

0 commit comments

Comments
 (0)