Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alex/configurable retrier #14330

Merged
merged 126 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
114daea
checkout files from test branch
girarda Jun 29, 2022
6d47a6d
read_incremental works
girarda Jun 29, 2022
dbff403
reset to master
girarda Jun 29, 2022
24f54db
remove dead code
girarda Jun 29, 2022
e3f8d50
comment
girarda Jun 29, 2022
cbe4e57
fix
girarda Jun 29, 2022
ca44a97
Merge branch 'master' into alex/declarativeStreamIncrementalFix
girarda Jun 29, 2022
3d3f321
Add test
girarda Jun 29, 2022
4ea9a2b
comments
girarda Jun 29, 2022
2f09e79
utc
girarda Jun 29, 2022
67f174d
format
girarda Jun 29, 2022
096b54e
small fix
girarda Jun 30, 2022
db01257
Add test with rfc3339
girarda Jun 30, 2022
f097ee2
remove unused param
girarda Jun 30, 2022
b40eb69
Merge branch 'master' into alex/declarativeStreamIncrementalFix
girarda Jun 30, 2022
eae5bb5
fix test
girarda Jun 30, 2022
e1a59aa
configurable state checkpointing
girarda Jun 30, 2022
f534b20
update test
girarda Jun 30, 2022
22a2296
start working on retrier
girarda Jun 30, 2022
5ade34d
retry predicate
girarda Jun 30, 2022
eef3a2b
return response status
girarda Jun 30, 2022
16c33a5
look in error message
girarda Jun 30, 2022
355fdc4
cleanup test
girarda Jun 30, 2022
2b4da45
constant backoff strategy
girarda Jun 30, 2022
e58b977
chain backoff strategy
girarda Jun 30, 2022
94b0ab1
chain retrier
girarda Jun 30, 2022
d9d9004
Add to class types registry
girarda Jun 30, 2022
6296403
extract backoff time from header
girarda Jun 30, 2022
a97d730
wait until
girarda Jun 30, 2022
814088a
update
girarda Jul 1, 2022
7972c20
split file
girarda Jul 1, 2022
f94be2c
parse_records
girarda Jul 1, 2022
d50f832
merge master
girarda Jul 1, 2022
892270c
classmethod
girarda Jul 1, 2022
57ed90e
Merge branch 'master' into alex/configurableRetrier
girarda Jul 1, 2022
fb88553
delete dead code
girarda Jul 1, 2022
51fc8eb
comment
girarda Jul 1, 2022
efc40b5
comment
girarda Jul 1, 2022
e33e004
comments
girarda Jul 1, 2022
fc41d54
fix
girarda Jul 1, 2022
e2ef317
test for instantiating chain retrier
girarda Jul 1, 2022
b8faa8d
fix parsing
girarda Jul 1, 2022
6f96b26
cleanup
girarda Jul 1, 2022
d864b01
fix
girarda Jul 1, 2022
d209dd6
reset
girarda Jul 1, 2022
04fca0d
never raise on http error
girarda Jul 1, 2022
0301e9a
remove print
girarda Jul 1, 2022
0367f0f
comment
girarda Jul 1, 2022
994d125
comment
girarda Jul 1, 2022
f3a0af4
comment
girarda Jul 1, 2022
2140c28
comment
girarda Jul 1, 2022
77a0325
remove prints
girarda Jul 1, 2022
ecbfcf3
add declarative stream to registry
girarda Jul 1, 2022
f507f91
Merge branch 'master' into alex/configurableRetrier
girarda Jul 6, 2022
b510b17
Delete dead code
girarda Jul 6, 2022
d03c809
Add docstrings
girarda Jul 6, 2022
6471b15
Merge branch 'master' into alex/configurableRetrier
girarda Jul 7, 2022
b8b3c3a
quick fix
girarda Jul 7, 2022
71fa551
exponential backoff
girarda Jul 7, 2022
e0da35d
fix test
girarda Jul 7, 2022
f7bc427
fix
girarda Jul 7, 2022
0fca96c
delete unused properties
girarda Jul 7, 2022
04fc03e
fix
girarda Jul 7, 2022
dda7b9c
missing unit tests
girarda Jul 7, 2022
a35c9bc
merge master
girarda Jul 8, 2022
9862b4b
uppercase
girarda Jul 8, 2022
ad1cce6
docstrings
girarda Jul 8, 2022
15c3f4e
rename to success
girarda Jul 8, 2022
eca2f45
compare full request instead of just url
girarda Jul 8, 2022
5a756bb
renmae module
girarda Jul 8, 2022
9b289cb
rename test file
girarda Jul 8, 2022
fff16ed
rename interface
girarda Jul 8, 2022
918e31a
rename default retrier
girarda Jul 8, 2022
82fb6e8
rename to compositeerrorhandler
girarda Jul 8, 2022
98e17eb
fix missing renames
girarda Jul 8, 2022
9ab1008
move action to filter
girarda Jul 8, 2022
2a6c9d2
str -> minmaxdatetime
girarda Jul 8, 2022
8a72ac0
small fixes
girarda Jul 8, 2022
73279d7
plural
girarda Jul 8, 2022
181fb74
add example
girarda Jul 8, 2022
104ec1b
handle header variations
girarda Jul 8, 2022
cf17cae
also fix wait time from
girarda Jul 8, 2022
08a3663
allow using a regex to extract the value
girarda Jul 8, 2022
764eca0
group()
girarda Jul 8, 2022
34f3a77
docstring
girarda Jul 8, 2022
5cde974
add docs
girarda Jul 8, 2022
573dd90
update comment
girarda Jul 8, 2022
efdca4a
docstrings
girarda Jul 8, 2022
45df5d9
Merge branch 'master' into alex/configurableRetrier
girarda Jul 8, 2022
9bf3808
Merge branch 'master' into alex/configurableRetrier
girarda Jul 11, 2022
db47529
update comment
girarda Jul 11, 2022
9e5fddc
Merge branch 'master' into alex/configurableRetrier
girarda Jul 11, 2022
7aae31d
Update airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/…
girarda Jul 12, 2022
6656af3
version: Update Parquet library to latest release (#14502)
blarghmatey Jul 11, 2022
1a49c95
merge
girarda Jul 12, 2022
d30aa5b
🎉 Source Github: improve schema for stream `pull_request_commits` add…
grubberr Jul 12, 2022
a525afe
Docs: Fixed broken links (#14622)
Amruta-Ranade Jul 12, 2022
64750ec
source-hubspot: change mentioning of Mailchimp into HubSpot doc (#14…
bjgbeelen Jul 12, 2022
f3fc604
Helm Chart: Add external temporal option (#14597)
marcosmarxm Jul 12, 2022
2d2ef71
🎉 Add YAML format to source-file reader (#14588)
ChristopheDuong Jul 12, 2022
8b422c7
:tada: Source Okta: add GroupMembers stream (#14380)
YiyangLi Jul 12, 2022
73b47b1
split test files
girarda Jul 12, 2022
f97cfee
renames
girarda Jul 12, 2022
18e562a
missing unit test
girarda Jul 12, 2022
2d1312e
add missing unit tests
girarda Jul 12, 2022
a0ab86b
rename
girarda Jul 12, 2022
d4c7db1
merge
girarda Jul 12, 2022
32e34dc
assert isinstance
girarda Jul 12, 2022
187858f
start extracting to their own files
girarda Jul 12, 2022
bbc8cf5
use final instead of classmethod
girarda Jul 12, 2022
4647033
assert we retry 429 errors
girarda Jul 12, 2022
a201c3f
Add log
girarda Jul 12, 2022
198c1d2
replace asserts with valueexceptions
girarda Jul 12, 2022
0dbe4a9
delete superfluous print statement
girarda Jul 12, 2022
d996baa
fix factory so we don't need to union everything with strings
girarda Jul 12, 2022
47a56d8
get class_name from type
girarda Jul 13, 2022
22521bf
remove from class types registry
girarda Jul 13, 2022
6b77952
process error handlers one at a time
girarda Jul 13, 2022
598c36a
sort
girarda Jul 13, 2022
72dc105
delete print statement
girarda Jul 13, 2022
2d291b6
comment
girarda Jul 13, 2022
7673b52
comment
girarda Jul 13, 2022
7bdc243
merge master
girarda Jul 13, 2022
ee7de21
format
girarda Jul 13, 2022
58d154a
delete unused file
girarda Jul 13, 2022
a00c6a8
merge master
girarda Jul 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def newfunc(*fargs, **fkeywords):

# interpolate the parameters
interpolated_keywords = InterpolatedMapping(fully_created, interpolation).eval(config, **{"options": options})
interpolated_keywords = {k: v for k, v in interpolated_keywords.items() if v is not None}
interpolated_keywords = {k: v for k, v in interpolated_keywords.items() if v}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we'll be able to get rid of "compile-time" interpolation" once we get the interpolated authenticators in

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean compile-time interpolation? I think i don't understand the subtlety here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm referring to the strings evaluated by jinja, eg "{{ 1 + 1 }}"

they can either be evaluated when parsing the yaml file and instantiating the objects, or at runtime on every request/response.

we generally want the strings to be evaluated at runtime because they can depend on the requests/response/records/etc, but right now we also need to try evaluating them during the parsing step because the authenticators use normal strings, not InterpolatedString.

That won't be the case once #13993 is merged and we'll be able to delete these lines.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!


all_keywords.update(interpolated_keywords)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
from typing import Any, List, Mapping

from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean
from airbyte_cdk.sources.declarative.types import Record
from airbyte_cdk.sources.declarative.types import Config, Record


class RecordFilter:
def __init__(self, config, condition: str = None):
def __init__(self, config: Config, condition: str = None):
self._config = config
self._filter_interpolator = InterpolatedBoolean(condition)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,39 @@
from typing import Mapping, Type

from airbyte_cdk.sources.declarative.datetime.min_max_datetime import MinMaxDatetime
from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream
from airbyte_cdk.sources.declarative.extractors.jello import JelloExtractor
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.constant_backoff_strategy import ConstantBackoffStrategy
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.exponential_backoff_strategy import (
ExponentialBackoffStrategy,
)
from airbyte_cdk.sources.declarative.requesters.error_handlers.composite_error_handler import CompositeErrorHandler
from airbyte_cdk.sources.declarative.requesters.error_handlers.default_error_handler import DefaultErrorHandler
from airbyte_cdk.sources.declarative.requesters.http_requester import HttpRequester
from airbyte_cdk.sources.declarative.requesters.paginators.interpolated_paginator import InterpolatedPaginator
from airbyte_cdk.sources.declarative.requesters.paginators.next_page_url_paginator import NextPageUrlPaginator
from airbyte_cdk.sources.declarative.requesters.paginators.offset_paginator import OffsetPaginator
from airbyte_cdk.sources.declarative.stream_slicers.cartesian_product_stream_slicer import CartesianProductStreamSlicer
from airbyte_cdk.sources.declarative.stream_slicers.datetime_stream_slicer import DatetimeStreamSlicer
from airbyte_cdk.sources.declarative.stream_slicers.list_stream_slicer import ListStreamSlicer
from airbyte_cdk.sources.declarative.transformations import RemoveFields
from airbyte_cdk.sources.streams.http.requests_native_auth.token import TokenAuthenticator

CLASS_TYPES_REGISTRY: Mapping[str, Type] = {
"CartesianProductStreamSlicer": CartesianProductStreamSlicer,
"CompositeErrorHandler": CompositeErrorHandler,
"ConstantBackoffStrategy": ConstantBackoffStrategy,
"DatetimeStreamSlicer": DatetimeStreamSlicer,
"DeclarativeStream": DeclarativeStream,
"DefaultErrorHandler": DefaultErrorHandler,
"ExponentialBackoffStrategy": ExponentialBackoffStrategy,
"HttpRequester": HttpRequester,
"InterpolatedPaginator": InterpolatedPaginator,
"JelloExtractor": JelloExtractor,
"ListStreamSlicer": ListStreamSlicer,
"MinMaxDatetime": MinMaxDatetime,
"NextPageUrlPaginator": NextPageUrlPaginator,
"OffsetPaginator": OffsetPaginator,
"TokenAuthenticator": TokenAuthenticator,
"RemoveFields": RemoveFields,
"TokenAuthenticator": TokenAuthenticator,
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@

from airbyte_cdk.sources.declarative.checks.check_stream import CheckStream
from airbyte_cdk.sources.declarative.checks.connection_checker import ConnectionChecker
from airbyte_cdk.sources.declarative.datetime.min_max_datetime import MinMaxDatetime
from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream
from airbyte_cdk.sources.declarative.decoders.decoder import Decoder
from airbyte_cdk.sources.declarative.decoders.json_decoder import JsonDecoder
from airbyte_cdk.sources.declarative.extractors.http_selector import HttpSelector
from airbyte_cdk.sources.declarative.extractors.jello import JelloExtractor
from airbyte_cdk.sources.declarative.extractors.record_selector import RecordSelector
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
from airbyte_cdk.sources.declarative.requesters.error_handlers.default_error_handler import DefaultErrorHandler
from airbyte_cdk.sources.declarative.requesters.error_handlers.error_handler import ErrorHandler
from airbyte_cdk.sources.declarative.requesters.error_handlers.http_response_filter import HttpResponseFilter
from airbyte_cdk.sources.declarative.requesters.http_requester import HttpRequester
from airbyte_cdk.sources.declarative.requesters.paginators.no_pagination import NoPagination
from airbyte_cdk.sources.declarative.requesters.paginators.paginator import Paginator
from airbyte_cdk.sources.declarative.requesters.requester import Requester
from airbyte_cdk.sources.declarative.requesters.retriers.default_retrier import DefaultRetrier
from airbyte_cdk.sources.declarative.requesters.retriers.retrier import Retrier
from airbyte_cdk.sources.declarative.retrievers.retriever import Retriever
from airbyte_cdk.sources.declarative.retrievers.simple_retriever import SimpleRetriever
from airbyte_cdk.sources.declarative.schema.json_schema import JsonSchema
Expand All @@ -25,17 +29,22 @@
from airbyte_cdk.sources.declarative.states.state import State
from airbyte_cdk.sources.declarative.stream_slicers.single_slice import SingleSlice
from airbyte_cdk.sources.declarative.stream_slicers.stream_slicer import StreamSlicer
from airbyte_cdk.sources.streams.core import Stream

DEFAULT_IMPLEMENTATIONS_REGISTRY: Mapping[Type, Type] = {
Requester: HttpRequester,
Retriever: SimpleRetriever,
SchemaLoader: JsonSchema,
HttpSelector: RecordSelector,
ConnectionChecker: CheckStream,
Retrier: DefaultRetrier,
ErrorHandler: DefaultErrorHandler,
Decoder: JsonDecoder,
JelloExtractor: JelloExtractor,
State: DictState,
StreamSlicer: SingleSlice,
Paginator: NoPagination,
HttpResponseFilter: HttpResponseFilter,
Stream: DeclarativeStream,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no more class_name!! 🎉

MinMaxDatetime: MinMaxDatetime,
InterpolatedString: InterpolatedString,
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ def create_component(self, component_definition: Mapping[str, Any], config: Conf
:return: the object to create
"""
kwargs = copy.deepcopy(component_definition)
class_name = kwargs.pop("class_name")
if "class_name" in kwargs:
class_name = kwargs.pop("class_name")
elif "type" in kwargs:
class_name = CLASS_TYPES_REGISTRY[kwargs.pop("type")]
else:
raise ValueError(f"Failed to create component because it has no class_name or type. Definition: {component_definition}")
return self.build(class_name, config, **kwargs)

def build(self, class_or_class_name: Union[str, Type], config, **kwargs):
Expand Down Expand Up @@ -92,7 +97,13 @@ def _create_subcomponent(self, key, definition, kwargs, config, parent_class):
for sub in definition
]
else:
return definition
expected_type = self.get_default_type(key, parent_class)
if expected_type and not isinstance(definition, expected_type):
# call __init__(definition) if definition is not a dict and is not of the expected type
# for instance, to turn a string into an InterpolatedString
return expected_type(definition)
else:
return definition

@staticmethod
def is_object_definition_with_class_name(definition):
Expand All @@ -106,13 +117,16 @@ def is_object_definition_with_type(definition):
def get_default_type(parameter_name, parent_class):
type_hints = get_type_hints(parent_class.__init__)
interface = type_hints.get(parameter_name)
origin = get_origin(interface)
if origin == Union:
# Handling Optional, which are implement as a Union[T, None]
# the interface we're looking for being the first type argument
args = get_args(interface)
interface = args[0]

while True:
origin = get_origin(interface)
if origin:
# Unnest types until we reach the raw type
# List[T] -> T
# Optional[List[T]] -> T
args = get_args(interface)
interface = args[0]
else:
break
expected_type = DEFAULT_IMPLEMENTATIONS_REGISTRY.get(interface)
return expected_type

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,21 @@ def preprocess(self, value, evaluated_config, path):
elif isinstance(value, dict):
return self.preprocess_dict(value, evaluated_config, path)
elif type(value) == list:
evaluated_list = [self.preprocess(v, evaluated_config, path) for v in value]
evaluated_list = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we were not correctly adding a list's elements to the evaluated_config

# pass in elem's path instead of the list's path
self.preprocess(v, evaluated_config, self._get_path_for_list_item(path, index))
for index, v in enumerate(value)
]
# Add the list's element to the evaluated config so they can be referenced
for index, elem in enumerate(evaluated_list):
evaluated_config[self._get_path_for_list_item(path, index)] = elem
return evaluated_list
else:
return value

def _get_path_for_list_item(self, path, index):
# An elem's path is {path_to_list}[{index}]
if len(path) > 1:
return path[:-1], f"{path[-1]}[{index}]"
else:
return (f"{path[-1]}[{index}]",)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

from typing import Optional

import requests
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy


class ConstantBackoffStrategy(BackoffStrategy):
"""
Backoff strategy with a constant backoff interval
"""

def __init__(self, backoff_time_in_seconds: float):
"""
:param backoff_time_in_seconds: time to backoff before retrying a retryable request
"""
self._backoff_time_in_seconds = backoff_time_in_seconds

def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
return self._backoff_time_in_seconds
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

from typing import Optional

import requests
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy


class ExponentialBackoffStrategy(BackoffStrategy):
"""
Backoff strategy with an exponential backoff interval
"""

def __init__(self, factor: float = 5):
"""
:param factor: multiplicative factor
"""
self._factor = factor

def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
return self._factor * 2**attempt_count
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

import numbers
from re import Pattern
from typing import Optional

import requests


def get_numeric_value_from_header(response: requests.Response, header: str, regex: Optional[Pattern]) -> Optional[float]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add unit tests for this guy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""
Extract a header value from the response as a float
:param response: response the extract header value from
:param header: Header to extract
:param regex: optional regex to apply on the header to obtain the value
:return: header value as float if it's a number. None otherwise
"""
header_value = response.headers.get(header, None)
if not header_value:
return None
if isinstance(header_value, str):
if regex:
match = regex.match(header_value)
if match:
header_value = match.group()
return _as_float(header_value)
elif isinstance(header_value, numbers.Number):
return float(header_value)
else:
return None


def _as_float(s: str) -> Optional[float]:
try:
return float(s)
except ValueError:
return None
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

import re
from typing import Optional

import requests
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import get_numeric_value_from_header
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy


class WaitTimeFromHeaderBackoffStrategy(BackoffStrategy):
"""
Extract wait time from http header
"""

def __init__(self, header: str, regex: Optional[str] = None):
"""
:param header: header to read wait time from
:param regex: optional regex to apply on the header to extract its value
"""
self._header = header
self._regex = re.compile(regex) if regex else None

def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
header_value = get_numeric_value_from_header(response, self._header, self._regex)
return header_value
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

import numbers
import re
import time
from typing import Optional

import requests
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import get_numeric_value_from_header
from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy


class WaitUntilTimeFromHeaderBackoffStrategy(BackoffStrategy):
"""
Extract time at which we can retry the request from response header
and wait for the difference between now and that time
"""

def __init__(self, header: str, min_wait: Optional[float] = None, regex: Optional[str] = None):
"""

:param header: header to read wait time from
:param min_wait: minimum time to wait for safety
:param regex: optional regex to apply on the header to extract its value
"""
self._header = header
self._min_wait = min_wait
self._regex = re.compile(regex) if regex else None

def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
now = time.time()
wait_until = get_numeric_value_from_header(response, self._header, self._regex)
if wait_until is None or not wait_until:
return self._min_wait
if (isinstance(wait_until, str) and wait_until.isnumeric()) or isinstance(wait_until, numbers.Number):
wait_time = float(wait_until) - now
else:
return self._min_wait
if self._min_wait:
return max(wait_time, self._min_wait)
elif wait_time < 0:
return None
return wait_time
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

from abc import abstractmethod
from typing import Optional

import requests


class BackoffStrategy:
@abstractmethod
def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
"""
Return time to wait before retrying the request.
:param response: response received for the request to retry
:param attempt_count: number of attempts to submit the request
:return: time to wait in seconds
"""
Loading