Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
retry_streaming.py264 linesDownload Raw Back to retry
1# Copyright 2023 Google LLC2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15"""16Generator wrapper for retryable streaming RPCs.17"""18from __future__ import annotations19 20from typing import (21    Callable,22    Optional,23    List,24    Tuple,25    Iterable,26    Generator,27    TypeVar,28    Any,29    TYPE_CHECKING,30)31 32import sys33import time34import functools35 36from google.api_core.retry.retry_base import _BaseRetry37from google.api_core.retry.retry_base import _retry_error_helper38from google.api_core.retry import exponential_sleep_generator39from google.api_core.retry import build_retry_error40from google.api_core.retry import RetryFailureReason41 42if TYPE_CHECKING:43    if sys.version_info >= (3, 10):44        from typing import ParamSpec45    else:46        from typing_extensions import ParamSpec47 48    _P = ParamSpec("_P")  # target function call parameters49    _Y = TypeVar("_Y")  # yielded values50 51 52def retry_target_stream(53    target: Callable[_P, Iterable[_Y]],54    predicate: Callable[[Exception], bool],55    sleep_generator: Iterable[float],56    timeout: Optional[float] = None,57    on_error: Optional[Callable[[Exception], None]] = None,58    exception_factory: Callable[59        [List[Exception], RetryFailureReason, Optional[float]],60        Tuple[Exception, Optional[Exception]],61    ] = build_retry_error,62    init_args: _P.args = (),63    init_kwargs: _P.kwargs = {},64    **kwargs,65) -> Generator[_Y, Any, None]:66    """Create a generator wrapper that retries the wrapped stream if it fails.67 68    This is the lowest-level retry helper. Generally, you'll use the69    higher-level retry helper :class:`Retry`.70 71    Args:72        target: The generator function to call and retry.73        predicate: A callable used to determine if an74            exception raised by the target should be considered retryable.75            It should return True to retry or False otherwise.76        sleep_generator: An infinite iterator that determines77            how long to sleep between retries.78        timeout: How long to keep retrying the target.79            Note: timeout is only checked before initiating a retry, so the target may80            run past the timeout value as long as it is healthy.81        on_error: If given, the on_error callback will be called with each82            retryable exception raised by the target. Any error raised by this83            function will *not* be caught.84        exception_factory: A function that is called when the retryable reaches85            a terminal failure state, used to construct an exception to be raised.86            It takes a list of all exceptions encountered, a retry.RetryFailureReason87            enum indicating the failure cause, and the original timeout value88            as arguments. It should return a tuple of the exception to be raised,89            along with the cause exception if any. The default implementation will raise90            a RetryError on timeout, or the last exception encountered otherwise.91        init_args: Positional arguments to pass to the target function.92        init_kwargs: Keyword arguments to pass to the target function.93 94    Returns:95        Generator: A retryable generator that wraps the target generator function.96 97    Raises:98        ValueError: If the sleep generator stops yielding values.99        Exception: a custom exception specified by the exception_factory if provided.100            If no exception_factory is provided:101                google.api_core.RetryError: If the timeout is exceeded while retrying.102                Exception: If the target raises an error that isn't retryable.103    """104 105    timeout = kwargs.get("deadline", timeout)106    deadline: Optional[float] = (107        time.monotonic() + timeout if timeout is not None else None108    )109    error_list: list[Exception] = []110 111    for sleep in sleep_generator:112        # Start a new retry loop113        try:114            # Note: in the future, we can add a ResumptionStrategy object115            # to generate new args between calls. For now, use the same args116            # for each attempt.117            subgenerator = target(*init_args, **init_kwargs)118            return (yield from subgenerator)119        # handle exceptions raised by the subgenerator120        # pylint: disable=broad-except121        # This function explicitly must deal with broad exceptions.122        except Exception as exc:123            # defer to shared logic for handling errors124            _retry_error_helper(125                exc,126                deadline,127                sleep,128                error_list,129                predicate,130                on_error,131                exception_factory,132                timeout,133            )134            # if exception not raised, sleep before next attempt135            time.sleep(sleep)136 137    raise ValueError("Sleep generator stopped yielding sleep values.")138 139 140class StreamingRetry(_BaseRetry):141    """Exponential retry decorator for streaming synchronous RPCs.142 143    This class returns a Generator when called, which wraps the target144    stream in retry logic. If any exception is raised by the target, the145    entire stream will be retried within the wrapper.146 147    Although the default behavior is to retry transient API errors, a148    different predicate can be provided to retry other exceptions.149 150    Important Note: when a stream encounters a retryable error, it will151    silently construct a fresh iterator instance in the background152    and continue yielding (likely duplicate) values as if no error occurred.153    This is the most general way to retry a stream, but it often is not the154    desired behavior. Example: iter([1, 2, 1/0]) -> [1, 2, 1, 2, ...]155 156    There are two ways to build more advanced retry logic for streams:157 158    1. Wrap the target159        Use a ``target`` that maintains state between retries, and creates a160        different generator on each retry call. For example, you can wrap a161        network call in a function that modifies the request based on what has162        already been returned:163 164        .. code-block:: python165 166            def attempt_with_modified_request(target, request, seen_items=[]):167                # remove seen items from request on each attempt168                new_request = modify_request(request, seen_items)169                new_generator = target(new_request)170                for item in new_generator:171                    yield item172                    seen_items.append(item)173 174            retry_wrapped_fn = StreamingRetry()(attempt_with_modified_request)175            retryable_generator = retry_wrapped_fn(target, request)176 177    2. Wrap the retry generator178        Alternatively, you can wrap the retryable generator itself before179        passing it to the end-user to add a filter on the stream. For180        example, you can keep track of the items that were successfully yielded181        in previous retry attempts, and only yield new items when the182        new attempt surpasses the previous ones:183 184        .. code-block:: python185 186            def retryable_with_filter(target):187                stream_idx = 0188                # reset stream_idx when the stream is retried189                def on_error(e):190                    nonlocal stream_idx191                    stream_idx = 0192                # build retryable193                retryable_gen = StreamingRetry(...)(target)194                # keep track of what has been yielded out of filter195                seen_items = []196                for item in retryable_gen():197                    if stream_idx >= len(seen_items):198                        seen_items.append(item)199                        yield item200                    elif item != seen_items[stream_idx]:201                        raise ValueError("Stream differs from last attempt")202                    stream_idx += 1203 204            filter_retry_wrapped = retryable_with_filter(target)205 206    Args:207        predicate (Callable[Exception]): A callable that should return ``True``208            if the given exception is retryable.209        initial (float): The minimum amount of time to delay in seconds. This210            must be greater than 0.211        maximum (float): The maximum amount of time to delay in seconds.212        multiplier (float): The multiplier applied to the delay.213        timeout (float): How long to keep retrying, in seconds.214            Note: timeout is only checked before initiating a retry, so the target may215            run past the timeout value as long as it is healthy.216        on_error (Callable[Exception]): A function to call while processing217            a retryable exception. Any error raised by this function will218            *not* be caught.219        deadline (float): DEPRECATED: use `timeout` instead. For backward220            compatibility, if specified it will override the ``timeout`` parameter.221    """222 223    def __call__(224        self,225        func: Callable[_P, Iterable[_Y]],226        on_error: Callable[[Exception], Any] | None = None,227    ) -> Callable[_P, Generator[_Y, Any, None]]:228        """Wrap a callable with retry behavior.229 230        Args:231            func (Callable): The callable to add retry behavior to.232            on_error (Optional[Callable[Exception]]): If given, the233                on_error callback will be called with each retryable exception234                raised by the wrapped function. Any error raised by this235                function will *not* be caught. If on_error was specified in the236                constructor, this value will be ignored.237 238        Returns:239            Callable: A callable that will invoke ``func`` with retry240                behavior.241        """242        if self._on_error is not None:243            on_error = self._on_error244 245        @functools.wraps(func)246        def retry_wrapped_func(247            *args: _P.args, **kwargs: _P.kwargs248        ) -> Generator[_Y, Any, None]:249            """A wrapper that calls target function with retry."""250            sleep_generator = exponential_sleep_generator(251                self._initial, self._maximum, multiplier=self._multiplier252            )253            return retry_target_stream(254                func,255                predicate=self._predicate,256                sleep_generator=sleep_generator,257                timeout=self._timeout,258                on_error=on_error,259                init_args=args,260                init_kwargs=kwargs,261            )262 263        return retry_wrapped_func264 
codekingpro/portable-devtools · Team Ai