airbyte_cdk.sources.declarative.requesters

1#
2# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3#
4
5from airbyte_cdk.sources.declarative.requesters.http_requester import HttpRequester
6from airbyte_cdk.sources.declarative.requesters.request_option import RequestOption
7from airbyte_cdk.sources.declarative.requesters.requester import Requester
8
9__all__ = ["HttpRequester", "RequestOption", "Requester"]
@dataclass
class HttpRequester(airbyte_cdk.sources.declarative.requesters.Requester):
 37@dataclass
 38class HttpRequester(Requester):
 39    """
 40    Default implementation of a Requester
 41
 42    Attributes:
 43        name (str): Name of the stream. Only used for request/response caching
 44        url_base (Union[InterpolatedString, str]): Base url to send requests to
 45        path (Union[InterpolatedString, str]): Path to send requests to
 46        http_method (Union[str, HttpMethod]): HTTP method to use when sending requests
 47        request_options_provider (Optional[InterpolatedRequestOptionsProvider]): request option provider defining the options to set on outgoing requests
 48        authenticator (DeclarativeAuthenticator): Authenticator defining how to authenticate to the source
 49        error_handler (Optional[ErrorHandler]): Error handler defining how to detect and handle errors
 50        backoff_strategies (Optional[List[BackoffStrategy]]): List of backoff strategies to use when retrying requests
 51        config (Config): The user-provided configuration as specified by the source's spec
 52        use_cache (bool): Indicates that data should be cached for this stream
 53    """
 54
 55    name: str
 56    config: Config
 57    parameters: InitVar[Mapping[str, Any]]
 58
 59    url: Optional[Union[InterpolatedString, str]] = None
 60    url_base: Optional[Union[InterpolatedString, str]] = None
 61    path: Optional[Union[InterpolatedString, str]] = None
 62    authenticator: Optional[DeclarativeAuthenticator] = None
 63    http_method: Union[str, HttpMethod] = HttpMethod.GET
 64    request_options_provider: Optional[InterpolatedRequestOptionsProvider] = None
 65    error_handler: Optional[ErrorHandler] = None
 66    api_budget: Optional[APIBudget] = None
 67    disable_retries: bool = False
 68    message_repository: MessageRepository = NoopMessageRepository()
 69    use_cache: bool = False
 70    connect_timeout_in_seconds: Optional[float] = None
 71    read_timeout_in_seconds: Optional[float] = None
 72    use_tcp_keepalive: bool = False
 73    _exit_on_rate_limit: bool = False
 74    stream_response: bool = False
 75    decoder: Decoder = field(default_factory=lambda: JsonDecoder(parameters={}))
 76
 77    def __post_init__(self, parameters: Mapping[str, Any]) -> None:
 78        self._url = InterpolatedString.create(
 79            self.url if self.url else EmptyString, parameters=parameters
 80        )
 81        # deprecated
 82        self._url_base = InterpolatedString.create(
 83            self.url_base if self.url_base else EmptyString, parameters=parameters
 84        )
 85        # deprecated
 86        self._path = InterpolatedString.create(
 87            self.path if self.path else EmptyString, parameters=parameters
 88        )
 89        if self.request_options_provider is None:
 90            self._request_options_provider = InterpolatedRequestOptionsProvider(
 91                config=self.config, parameters=parameters
 92            )
 93        elif isinstance(self.request_options_provider, dict):
 94            self._request_options_provider = InterpolatedRequestOptionsProvider(
 95                config=self.config, **self.request_options_provider
 96            )
 97        else:
 98            self._request_options_provider = self.request_options_provider
 99        self._authenticator = self.authenticator or NoAuth(parameters=parameters)
100        self._http_method = (
101            HttpMethod[self.http_method] if isinstance(self.http_method, str) else self.http_method
102        )
103        self.error_handler = self.error_handler
104        self._parameters = parameters
105
106        if self.error_handler is not None and hasattr(self.error_handler, "backoff_strategies"):
107            backoff_strategies = self.error_handler.backoff_strategies  # type: ignore
108        else:
109            backoff_strategies = None
110
111        self._http_client = HttpClient(
112            name=self.name,
113            logger=self.logger,
114            error_handler=self.error_handler,
115            api_budget=self.api_budget,
116            authenticator=self._authenticator,
117            use_cache=self.use_cache,
118            backoff_strategy=backoff_strategies,
119            disable_retries=self.disable_retries,
120            message_repository=self.message_repository,
121            request_timeout=(
122                (self.connect_timeout_in_seconds, self.read_timeout_in_seconds)
123                if self.connect_timeout_in_seconds is not None
124                or self.read_timeout_in_seconds is not None
125                else None
126            ),
127            use_tcp_keepalive=self.use_tcp_keepalive,
128        )
129
130    @property
131    def exit_on_rate_limit(self) -> bool:
132        return self._exit_on_rate_limit
133
134    @exit_on_rate_limit.setter
135    def exit_on_rate_limit(self, value: bool) -> None:
136        self._exit_on_rate_limit = value
137
138    def get_authenticator(self) -> DeclarativeAuthenticator:
139        return self._authenticator
140
141    def get_url(
142        self,
143        *,
144        stream_state: Optional[StreamState] = None,
145        stream_slice: Optional[StreamSlice] = None,
146        next_page_token: Optional[Mapping[str, Any]] = None,
147    ) -> str:
148        interpolation_context = get_interpolation_context(
149            stream_state=stream_state,
150            stream_slice=stream_slice,
151            next_page_token=next_page_token,
152        )
153
154        return str(self._url.eval(self.config, **interpolation_context))
155
156    def _get_url(
157        self,
158        *,
159        path: Optional[str] = None,
160        stream_state: Optional[StreamState] = None,
161        stream_slice: Optional[StreamSlice] = None,
162        next_page_token: Optional[Mapping[str, Any]] = None,
163    ) -> str:
164        url = self.get_url(
165            stream_state=stream_state,
166            stream_slice=stream_slice,
167            next_page_token=next_page_token,
168        )
169
170        url_base = self.get_url_base(
171            stream_state=stream_state,
172            stream_slice=stream_slice,
173            next_page_token=next_page_token,
174        )
175
176        path = path or self.get_path(
177            stream_state=stream_state,
178            stream_slice=stream_slice,
179            next_page_token=next_page_token,
180        )
181
182        full_url = (
183            self._join_url(url_base, path)
184            if url_base
185            else self._join_url(url, path)
186            if path
187            else url
188        )
189
190        return full_url
191
192    def get_url_base(
193        self,
194        *,
195        stream_state: Optional[StreamState] = None,
196        stream_slice: Optional[StreamSlice] = None,
197        next_page_token: Optional[Mapping[str, Any]] = None,
198    ) -> str:
199        interpolation_context = get_interpolation_context(
200            stream_state=stream_state,
201            stream_slice=stream_slice,
202            next_page_token=next_page_token,
203        )
204        return str(self._url_base.eval(self.config, **interpolation_context))
205
206    def get_path(
207        self,
208        *,
209        stream_state: Optional[StreamState] = None,
210        stream_slice: Optional[StreamSlice] = None,
211        next_page_token: Optional[Mapping[str, Any]] = None,
212    ) -> str:
213        interpolation_context = get_interpolation_context(
214            stream_state=stream_state,
215            stream_slice=stream_slice,
216            next_page_token=next_page_token,
217        )
218        path = str(self._path.eval(self.config, **interpolation_context))
219        return path.lstrip("/")
220
221    def get_method(self) -> HttpMethod:
222        return self._http_method
223
224    def get_request_params(
225        self,
226        *,
227        stream_state: Optional[StreamState] = None,
228        stream_slice: Optional[StreamSlice] = None,
229        next_page_token: Optional[Mapping[str, Any]] = None,
230    ) -> MutableMapping[str, Any]:
231        return self._request_options_provider.get_request_params(
232            stream_state=stream_state,
233            stream_slice=stream_slice,
234            next_page_token=next_page_token,
235        )
236
237    def get_request_headers(
238        self,
239        *,
240        stream_state: Optional[StreamState] = None,
241        stream_slice: Optional[StreamSlice] = None,
242        next_page_token: Optional[Mapping[str, Any]] = None,
243    ) -> Mapping[str, Any]:
244        return self._request_options_provider.get_request_headers(
245            stream_state=stream_state,
246            stream_slice=stream_slice,
247            next_page_token=next_page_token,
248        )
249
250    # fixing request options provider types has a lot of dependencies
251    def get_request_body_data(  # type: ignore
252        self,
253        *,
254        stream_state: Optional[StreamState] = None,
255        stream_slice: Optional[StreamSlice] = None,
256        next_page_token: Optional[Mapping[str, Any]] = None,
257    ) -> Union[Mapping[str, Any], str]:
258        return (
259            self._request_options_provider.get_request_body_data(
260                stream_state=stream_state,
261                stream_slice=stream_slice,
262                next_page_token=next_page_token,
263            )
264            or {}
265        )
266
267    # fixing request options provider types has a lot of dependencies
268    def get_request_body_json(  # type: ignore
269        self,
270        *,
271        stream_state: Optional[StreamState] = None,
272        stream_slice: Optional[StreamSlice] = None,
273        next_page_token: Optional[Mapping[str, Any]] = None,
274    ) -> Optional[Mapping[str, Any]]:
275        return self._request_options_provider.get_request_body_json(
276            stream_state=stream_state,
277            stream_slice=stream_slice,
278            next_page_token=next_page_token,
279        )
280
281    @property
282    def logger(self) -> logging.Logger:
283        return logging.getLogger(f"airbyte.HttpRequester.{self.name}")
284
285    def _get_request_options(
286        self,
287        stream_state: Optional[StreamState],
288        stream_slice: Optional[StreamSlice],
289        next_page_token: Optional[Mapping[str, Any]],
290        requester_method: Callable[..., Optional[Union[Mapping[str, Any], str]]],
291        auth_options_method: Callable[..., Optional[Union[Mapping[str, Any], str]]],
292        extra_options: Optional[Union[Mapping[str, Any], str]] = None,
293    ) -> Union[Mapping[str, Any], str]:
294        """
295        Get the request_option from the requester, the authenticator and extra_options passed in.
296        Raise a ValueError if there's a key collision
297        Returned merged mapping otherwise
298        """
299
300        is_body_json = requester_method.__name__ == "get_request_body_json"
301
302        return combine_mappings(
303            [
304                requester_method(
305                    stream_state=stream_state,
306                    stream_slice=stream_slice,
307                    next_page_token=next_page_token,
308                ),
309                auth_options_method(),
310                extra_options,
311            ],
312            allow_same_value_merge=is_body_json,
313        )
314
315    def _request_headers(
316        self,
317        stream_state: Optional[StreamState] = None,
318        stream_slice: Optional[StreamSlice] = None,
319        next_page_token: Optional[Mapping[str, Any]] = None,
320        extra_headers: Optional[Mapping[str, Any]] = None,
321    ) -> Mapping[str, Any]:
322        """
323        Specifies request headers.
324        Authentication headers will overwrite any overlapping headers returned from this method.
325        """
326        headers = self._get_request_options(
327            stream_state,
328            stream_slice,
329            next_page_token,
330            self.get_request_headers,
331            self.get_authenticator().get_auth_header,
332            extra_headers,
333        )
334        if isinstance(headers, str):
335            raise ValueError("Request headers cannot be a string")
336        return {str(k): str(v) for k, v in headers.items()}
337
338    def _request_params(
339        self,
340        stream_state: Optional[StreamState],
341        stream_slice: Optional[StreamSlice],
342        next_page_token: Optional[Mapping[str, Any]],
343        extra_params: Optional[Mapping[str, Any]] = None,
344    ) -> Mapping[str, Any]:
345        """
346        Specifies the query parameters that should be set on an outgoing HTTP request given the inputs.
347
348        E.g: you might want to define query parameters for paging if next_page_token is not None.
349        """
350        options = self._get_request_options(
351            stream_state,
352            stream_slice,
353            next_page_token,
354            self.get_request_params,
355            self.get_authenticator().get_request_params,
356            extra_params,
357        )
358        if isinstance(options, str):
359            raise ValueError("Request params cannot be a string")
360
361        for k, v in options.items():
362            if isinstance(v, (dict,)):
363                raise ValueError(
364                    f"Invalid value for `{k}` parameter. The values of request params cannot be an object."
365                )
366
367        return options
368
369    def _request_body_data(
370        self,
371        stream_state: Optional[StreamState],
372        stream_slice: Optional[StreamSlice],
373        next_page_token: Optional[Mapping[str, Any]],
374        extra_body_data: Optional[Union[Mapping[str, Any], str]] = None,
375    ) -> Optional[Union[Mapping[str, Any], str]]:
376        """
377        Specifies how to populate the body of the request with a non-JSON payload.
378
379        If returns a ready text that it will be sent as is.
380        If returns a dict that it will be converted to a urlencoded form.
381        E.g. {"key1": "value1", "key2": "value2"} => "key1=value1&key2=value2"
382
383        At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.
384        """
385        # Warning: use self.state instead of the stream_state passed as argument!
386        return self._get_request_options(
387            stream_state,
388            stream_slice,
389            next_page_token,
390            self.get_request_body_data,
391            self.get_authenticator().get_request_body_data,
392            extra_body_data,
393        )
394
395    def _request_body_json(
396        self,
397        stream_state: Optional[StreamState],
398        stream_slice: Optional[StreamSlice],
399        next_page_token: Optional[Mapping[str, Any]],
400        extra_body_json: Optional[Mapping[str, Any]] = None,
401    ) -> Optional[Mapping[str, Any]]:
402        """
403        Specifies how to populate the body of the request with a JSON payload.
404
405        At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.
406        """
407        # Warning: use self.state instead of the stream_state passed as argument!
408        options = self._get_request_options(
409            stream_state,
410            stream_slice,
411            next_page_token,
412            self.get_request_body_json,
413            self.get_authenticator().get_request_body_json,
414            extra_body_json,
415        )
416        if isinstance(options, str):
417            raise ValueError("Request body json cannot be a string")
418        return options
419
420    @classmethod
421    def _join_url(cls, url_base: str, path: Optional[str] = None) -> str:
422        """
423        Joins a base URL with a given path and returns the resulting URL with any trailing slash removed.
424
425        This method ensures that there are no duplicate slashes when concatenating the base URL and the path,
426        which is useful when the full URL is provided from an interpolation context.
427
428        Args:
429            url_base (str): The base URL to which the path will be appended.
430            path (Optional[str]): The path to join with the base URL.
431
432        Returns:
433            str: The resulting joined URL.
434
435        Note:
436            Related issue: https://github.com/airbytehq/airbyte-internal-issues/issues/11869
437            - If the path is an empty string or None, the method returns the base URL with any trailing slash removed.
438
439        Example:
440            1) _join_url("https://example.com/api/", "endpoint") >> 'https://example.com/api/endpoint'
441            2) _join_url("https://example.com/api", "/endpoint") >> 'https://example.com/api/endpoint'
442            3) _join_url("https://example.com/api/", "") >> 'https://example.com/api/'
443            4) _join_url("https://example.com/api", None) >> 'https://example.com/api'
444        """
445
446        # return a full-url if provided directly from interpolation context
447        if path == EmptyString or path is None:
448            return url_base
449        else:
450            # since we didn't provide a full-url, the url_base might not have a trailing slash
451            # so we join the url_base and path correctly
452            if not url_base.endswith("/"):
453                url_base += "/"
454
455        return urljoin(url_base, path)
456
457    def send_request(
458        self,
459        stream_state: Optional[StreamState] = None,
460        stream_slice: Optional[StreamSlice] = None,
461        next_page_token: Optional[Mapping[str, Any]] = None,
462        path: Optional[str] = None,
463        request_headers: Optional[Mapping[str, Any]] = None,
464        request_params: Optional[Mapping[str, Any]] = None,
465        request_body_data: Optional[Union[Mapping[str, Any], str]] = None,
466        request_body_json: Optional[Mapping[str, Any]] = None,
467        log_formatter: Optional[Callable[[requests.Response], Any]] = None,
468    ) -> Optional[requests.Response]:
469        request, response = self._http_client.send_request(
470            http_method=self.get_method().value,
471            url=self._get_url(
472                path=path,
473                stream_state=stream_state,
474                stream_slice=stream_slice,
475                next_page_token=next_page_token,
476            ),
477            request_kwargs={"stream": self.stream_response},
478            headers=self._request_headers(
479                stream_state, stream_slice, next_page_token, request_headers
480            ),
481            params=self._request_params(
482                stream_state, stream_slice, next_page_token, request_params
483            ),
484            json=self._request_body_json(
485                stream_state, stream_slice, next_page_token, request_body_json
486            ),
487            data=self._request_body_data(
488                stream_state, stream_slice, next_page_token, request_body_data
489            ),
490            dedupe_query_params=True,
491            log_formatter=log_formatter,
492            exit_on_rate_limit=self._exit_on_rate_limit,
493        )
494
495        return response

Default implementation of a Requester

Attributes:
  • name (str): Name of the stream. Only used for request/response caching
  • url_base (Union[InterpolatedString, str]): Base url to send requests to
  • path (Union[InterpolatedString, str]): Path to send requests to
  • http_method (Union[str, HttpMethod]): HTTP method to use when sending requests
  • request_options_provider (Optional[InterpolatedRequestOptionsProvider]): request option provider defining the options to set on outgoing requests
  • authenticator (DeclarativeAuthenticator): Authenticator defining how to authenticate to the source
  • error_handler (Optional[ErrorHandler]): Error handler defining how to detect and handle errors
  • backoff_strategies (Optional[List[BackoffStrategy]]): List of backoff strategies to use when retrying requests
  • config (Config): The user-provided configuration as specified by the source's spec
  • use_cache (bool): Indicates that data should be cached for this stream
HttpRequester( name: str, config: Mapping[str, Any], parameters: dataclasses.InitVar[typing.Mapping[str, typing.Any]], url: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None, url_base: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None, path: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None, authenticator: Optional[airbyte_cdk.DeclarativeAuthenticator] = None, http_method: Union[str, airbyte_cdk.HttpMethod] = <HttpMethod.GET: 'GET'>, request_options_provider: Optional[airbyte_cdk.sources.declarative.requesters.request_options.InterpolatedRequestOptionsProvider] = None, error_handler: Optional[airbyte_cdk.sources.streams.http.error_handlers.ErrorHandler] = None, api_budget: Optional[airbyte_cdk.sources.streams.call_rate.APIBudget] = None, disable_retries: bool = False, message_repository: airbyte_cdk.MessageRepository = <airbyte_cdk.sources.message.NoopMessageRepository object>, use_cache: bool = False, connect_timeout_in_seconds: Optional[float] = None, read_timeout_in_seconds: Optional[float] = None, use_tcp_keepalive: bool = False, _exit_on_rate_limit: bool = False, stream_response: bool = False, decoder: airbyte_cdk.Decoder = <factory>)
name: str
config: Mapping[str, Any]
parameters: dataclasses.InitVar[typing.Mapping[str, typing.Any]]
url: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None
url_base: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None
path: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None
authenticator: Optional[airbyte_cdk.DeclarativeAuthenticator] = None
http_method: Union[str, airbyte_cdk.HttpMethod] = <HttpMethod.GET: 'GET'>
disable_retries: bool = False
use_cache: bool = False
connect_timeout_in_seconds: Optional[float] = None
read_timeout_in_seconds: Optional[float] = None
use_tcp_keepalive: bool = False
stream_response: bool = False
exit_on_rate_limit: bool
130    @property
131    def exit_on_rate_limit(self) -> bool:
132        return self._exit_on_rate_limit
def get_authenticator( self) -> airbyte_cdk.DeclarativeAuthenticator:
138    def get_authenticator(self) -> DeclarativeAuthenticator:
139        return self._authenticator

Specifies the authenticator to use when submitting requests

def get_url( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> str:
141    def get_url(
142        self,
143        *,
144        stream_state: Optional[StreamState] = None,
145        stream_slice: Optional[StreamSlice] = None,
146        next_page_token: Optional[Mapping[str, Any]] = None,
147    ) -> str:
148        interpolation_context = get_interpolation_context(
149            stream_state=stream_state,
150            stream_slice=stream_slice,
151            next_page_token=next_page_token,
152        )
153
154        return str(self._url.eval(self.config, **interpolation_context))
Returns

URL base for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"

def get_url_base( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> str:
192    def get_url_base(
193        self,
194        *,
195        stream_state: Optional[StreamState] = None,
196        stream_slice: Optional[StreamSlice] = None,
197        next_page_token: Optional[Mapping[str, Any]] = None,
198    ) -> str:
199        interpolation_context = get_interpolation_context(
200            stream_state=stream_state,
201            stream_slice=stream_slice,
202            next_page_token=next_page_token,
203        )
204        return str(self._url_base.eval(self.config, **interpolation_context))
Returns

URL base for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"

def get_path( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> str:
206    def get_path(
207        self,
208        *,
209        stream_state: Optional[StreamState] = None,
210        stream_slice: Optional[StreamSlice] = None,
211        next_page_token: Optional[Mapping[str, Any]] = None,
212    ) -> str:
213        interpolation_context = get_interpolation_context(
214            stream_state=stream_state,
215            stream_slice=stream_slice,
216            next_page_token=next_page_token,
217        )
218        path = str(self._path.eval(self.config, **interpolation_context))
219        return path.lstrip("/")

Returns the URL path for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "some_entity"

def get_method(self) -> airbyte_cdk.HttpMethod:
221    def get_method(self) -> HttpMethod:
222        return self._http_method

Specifies the HTTP method to use

def get_request_params( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> MutableMapping[str, Any]:
224    def get_request_params(
225        self,
226        *,
227        stream_state: Optional[StreamState] = None,
228        stream_slice: Optional[StreamSlice] = None,
229        next_page_token: Optional[Mapping[str, Any]] = None,
230    ) -> MutableMapping[str, Any]:
231        return self._request_options_provider.get_request_params(
232            stream_state=stream_state,
233            stream_slice=stream_slice,
234            next_page_token=next_page_token,
235        )

Specifies the query parameters that should be set on an outgoing HTTP request given the inputs.

E.g: you might want to define query parameters for paging if next_page_token is not None.

def get_request_headers( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> Mapping[str, Any]:
237    def get_request_headers(
238        self,
239        *,
240        stream_state: Optional[StreamState] = None,
241        stream_slice: Optional[StreamSlice] = None,
242        next_page_token: Optional[Mapping[str, Any]] = None,
243    ) -> Mapping[str, Any]:
244        return self._request_options_provider.get_request_headers(
245            stream_state=stream_state,
246            stream_slice=stream_slice,
247            next_page_token=next_page_token,
248        )

Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.

def get_request_body_data( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> Union[Mapping[str, Any], str]:
251    def get_request_body_data(  # type: ignore
252        self,
253        *,
254        stream_state: Optional[StreamState] = None,
255        stream_slice: Optional[StreamSlice] = None,
256        next_page_token: Optional[Mapping[str, Any]] = None,
257    ) -> Union[Mapping[str, Any], str]:
258        return (
259            self._request_options_provider.get_request_body_data(
260                stream_state=stream_state,
261                stream_slice=stream_slice,
262                next_page_token=next_page_token,
263            )
264            or {}
265        )

Specifies how to populate the body of the request with a non-JSON payload.

If returns a ready text that it will be sent as is. If returns a dict that it will be converted to a urlencoded form. E.g. {"key1": "value1", "key2": "value2"} => "key1=value1&key2=value2"

At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.

def get_request_body_json( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> Optional[Mapping[str, Any]]:
268    def get_request_body_json(  # type: ignore
269        self,
270        *,
271        stream_state: Optional[StreamState] = None,
272        stream_slice: Optional[StreamSlice] = None,
273        next_page_token: Optional[Mapping[str, Any]] = None,
274    ) -> Optional[Mapping[str, Any]]:
275        return self._request_options_provider.get_request_body_json(
276            stream_state=stream_state,
277            stream_slice=stream_slice,
278            next_page_token=next_page_token,
279        )

Specifies how to populate the body of the request with a JSON payload.

At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.

logger: logging.Logger
281    @property
282    def logger(self) -> logging.Logger:
283        return logging.getLogger(f"airbyte.HttpRequester.{self.name}")
def send_request( self, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, path: Optional[str] = None, request_headers: Optional[Mapping[str, Any]] = None, request_params: Optional[Mapping[str, Any]] = None, request_body_data: Union[Mapping[str, Any], str, NoneType] = None, request_body_json: Optional[Mapping[str, Any]] = None, log_formatter: Optional[Callable[[requests.models.Response], Any]] = None) -> Optional[requests.models.Response]:
457    def send_request(
458        self,
459        stream_state: Optional[StreamState] = None,
460        stream_slice: Optional[StreamSlice] = None,
461        next_page_token: Optional[Mapping[str, Any]] = None,
462        path: Optional[str] = None,
463        request_headers: Optional[Mapping[str, Any]] = None,
464        request_params: Optional[Mapping[str, Any]] = None,
465        request_body_data: Optional[Union[Mapping[str, Any], str]] = None,
466        request_body_json: Optional[Mapping[str, Any]] = None,
467        log_formatter: Optional[Callable[[requests.Response], Any]] = None,
468    ) -> Optional[requests.Response]:
469        request, response = self._http_client.send_request(
470            http_method=self.get_method().value,
471            url=self._get_url(
472                path=path,
473                stream_state=stream_state,
474                stream_slice=stream_slice,
475                next_page_token=next_page_token,
476            ),
477            request_kwargs={"stream": self.stream_response},
478            headers=self._request_headers(
479                stream_state, stream_slice, next_page_token, request_headers
480            ),
481            params=self._request_params(
482                stream_state, stream_slice, next_page_token, request_params
483            ),
484            json=self._request_body_json(
485                stream_state, stream_slice, next_page_token, request_body_json
486            ),
487            data=self._request_body_data(
488                stream_state, stream_slice, next_page_token, request_body_data
489            ),
490            dedupe_query_params=True,
491            log_formatter=log_formatter,
492            exit_on_rate_limit=self._exit_on_rate_limit,
493        )
494
495        return response

Sends a request and returns the response. Might return no response if the error handler chooses to ignore the response or throw an exception in case of an error. If path is set, the path configured on the requester itself is ignored. If header, params and body are set, they are merged with the ones configured on the requester itself.

If a log formatter is provided, it's used to log the performed request and response. If it's not provided, no logging is performed.

@dataclass
class RequestOption:
 25@dataclass
 26class RequestOption:
 27    """
 28    Describes an option to set on a request
 29
 30    Attributes:
 31        field_name (str): Describes the name of the parameter to inject. Mutually exclusive with field_path.
 32        field_path (list(str)): Describes the path to a nested field as a list of field names.
 33          Only valid for body_json injection type, and mutually exclusive with field_name.
 34        inject_into (RequestOptionType): Describes where in the HTTP request to inject the parameter
 35    """
 36
 37    inject_into: RequestOptionType
 38    parameters: InitVar[Mapping[str, Any]]
 39    field_name: Optional[Union[InterpolatedString, str]] = None
 40    field_path: Optional[List[Union[InterpolatedString, str]]] = None
 41
 42    def __post_init__(self, parameters: Mapping[str, Any]) -> None:
 43        # Validate inputs. We should expect either field_name or field_path, but not both
 44        if self.field_name is None and self.field_path is None:
 45            raise ValueError("RequestOption requires either a field_name or field_path")
 46
 47        if self.field_name is not None and self.field_path is not None:
 48            raise ValueError(
 49                "Only one of field_name or field_path can be provided to RequestOption"
 50            )
 51
 52        # Nested field injection is only supported for body JSON injection
 53        if self.field_path is not None and self.inject_into != RequestOptionType.body_json:
 54            raise ValueError(
 55                "Nested field injection is only supported for body JSON injection. Please use a top-level field_name for other injection types."
 56            )
 57
 58        # Convert field_name and field_path into InterpolatedString objects if they are strings
 59        if self.field_name is not None:
 60            self.field_name = InterpolatedString.create(self.field_name, parameters=parameters)
 61        elif self.field_path is not None:
 62            self.field_path = [
 63                InterpolatedString.create(segment, parameters=parameters)
 64                for segment in self.field_path
 65            ]
 66
 67    @property
 68    def _is_field_path(self) -> bool:
 69        """Returns whether this option is a field path (ie, a nested field)"""
 70        return self.field_path is not None
 71
 72    def inject_into_request(
 73        self,
 74        target: MutableMapping[str, Any],
 75        value: Any,
 76        config: Config,
 77    ) -> None:
 78        """
 79        Inject a request option value into a target request structure using either field_name or field_path.
 80        For non-body-json injection, only top-level field names are supported.
 81        For body-json injection, both field names and nested field paths are supported.
 82
 83        Args:
 84            target: The request structure to inject the value into
 85            value: The value to inject
 86            config: The config object to use for interpolation
 87        """
 88        if self._is_field_path:
 89            if self.inject_into != RequestOptionType.body_json:
 90                raise ValueError(
 91                    "Nested field injection is only supported for body JSON injection. Please use a top-level field_name for other injection types."
 92                )
 93
 94            assert self.field_path is not None  # for type checker
 95            current = target
 96            # Convert path segments into strings, evaluating any interpolated segments
 97            # Example: ["data", "{{ config[user_type] }}", "id"] -> ["data", "admin", "id"]
 98            *path_parts, final_key = [
 99                str(
100                    segment.eval(config=config)
101                    if isinstance(segment, InterpolatedString)
102                    else segment
103                )
104                for segment in self.field_path
105            ]
106
107            # Build a nested dictionary structure and set the final value at the deepest level
108            for part in path_parts:
109                current = current.setdefault(part, {})
110            current[final_key] = value
111        else:
112            # For non-nested fields, evaluate the field name if it's an interpolated string
113            key = (
114                self.field_name.eval(config=config)
115                if isinstance(self.field_name, InterpolatedString)
116                else self.field_name
117            )
118            target[str(key)] = value

Describes an option to set on a request

Attributes:
  • field_name (str): Describes the name of the parameter to inject. Mutually exclusive with field_path.
  • field_path (list(str)): Describes the path to a nested field as a list of field names. Only valid for body_json injection type, and mutually exclusive with field_name.
  • inject_into (RequestOptionType): Describes where in the HTTP request to inject the parameter
RequestOption( inject_into: airbyte_cdk.RequestOptionType, parameters: dataclasses.InitVar[typing.Mapping[str, typing.Any]], field_name: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None, field_path: Optional[List[Union[airbyte_cdk.InterpolatedString, str]]] = None)
parameters: dataclasses.InitVar[typing.Mapping[str, typing.Any]]
field_name: Union[airbyte_cdk.InterpolatedString, str, NoneType] = None
field_path: Optional[List[Union[airbyte_cdk.InterpolatedString, str]]] = None
def inject_into_request( self, target: MutableMapping[str, Any], value: Any, config: Mapping[str, Any]) -> None:
 72    def inject_into_request(
 73        self,
 74        target: MutableMapping[str, Any],
 75        value: Any,
 76        config: Config,
 77    ) -> None:
 78        """
 79        Inject a request option value into a target request structure using either field_name or field_path.
 80        For non-body-json injection, only top-level field names are supported.
 81        For body-json injection, both field names and nested field paths are supported.
 82
 83        Args:
 84            target: The request structure to inject the value into
 85            value: The value to inject
 86            config: The config object to use for interpolation
 87        """
 88        if self._is_field_path:
 89            if self.inject_into != RequestOptionType.body_json:
 90                raise ValueError(
 91                    "Nested field injection is only supported for body JSON injection. Please use a top-level field_name for other injection types."
 92                )
 93
 94            assert self.field_path is not None  # for type checker
 95            current = target
 96            # Convert path segments into strings, evaluating any interpolated segments
 97            # Example: ["data", "{{ config[user_type] }}", "id"] -> ["data", "admin", "id"]
 98            *path_parts, final_key = [
 99                str(
100                    segment.eval(config=config)
101                    if isinstance(segment, InterpolatedString)
102                    else segment
103                )
104                for segment in self.field_path
105            ]
106
107            # Build a nested dictionary structure and set the final value at the deepest level
108            for part in path_parts:
109                current = current.setdefault(part, {})
110            current[final_key] = value
111        else:
112            # For non-nested fields, evaluate the field name if it's an interpolated string
113            key = (
114                self.field_name.eval(config=config)
115                if isinstance(self.field_name, InterpolatedString)
116                else self.field_name
117            )
118            target[str(key)] = value

Inject a request option value into a target request structure using either field_name or field_path. For non-body-json injection, only top-level field names are supported. For body-json injection, both field names and nested field paths are supported.

Arguments:
  • target: The request structure to inject the value into
  • value: The value to inject
  • config: The config object to use for interpolation
 30class Requester(RequestOptionsProvider):
 31    @abstractmethod
 32    def get_authenticator(self) -> DeclarativeAuthenticator:
 33        """
 34        Specifies the authenticator to use when submitting requests
 35        """
 36        pass
 37
 38    @abstractmethod
 39    def get_url(
 40        self,
 41        *,
 42        stream_state: Optional[StreamState],
 43        stream_slice: Optional[StreamSlice],
 44        next_page_token: Optional[Mapping[str, Any]],
 45    ) -> str:
 46        """
 47        :return: URL base for the  API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"
 48        """
 49
 50    @abstractmethod
 51    def get_url_base(
 52        self,
 53        *,
 54        stream_state: Optional[StreamState],
 55        stream_slice: Optional[StreamSlice],
 56        next_page_token: Optional[Mapping[str, Any]],
 57    ) -> str:
 58        """
 59        :return: URL base for the  API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"
 60        """
 61
 62    @abstractmethod
 63    def get_path(
 64        self,
 65        *,
 66        stream_state: Optional[StreamState],
 67        stream_slice: Optional[StreamSlice],
 68        next_page_token: Optional[Mapping[str, Any]],
 69    ) -> str:
 70        """
 71        Returns the URL path for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "some_entity"
 72        """
 73
 74    @abstractmethod
 75    def get_method(self) -> HttpMethod:
 76        """
 77        Specifies the HTTP method to use
 78        """
 79
 80    @abstractmethod
 81    def get_request_params(
 82        self,
 83        *,
 84        stream_state: Optional[StreamState] = None,
 85        stream_slice: Optional[StreamSlice] = None,
 86        next_page_token: Optional[Mapping[str, Any]] = None,
 87    ) -> MutableMapping[str, Any]:
 88        """
 89        Specifies the query parameters that should be set on an outgoing HTTP request given the inputs.
 90
 91        E.g: you might want to define query parameters for paging if next_page_token is not None.
 92        """
 93
 94    @abstractmethod
 95    def get_request_headers(
 96        self,
 97        *,
 98        stream_state: Optional[StreamState] = None,
 99        stream_slice: Optional[StreamSlice] = None,
100        next_page_token: Optional[Mapping[str, Any]] = None,
101    ) -> Mapping[str, Any]:
102        """
103        Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.
104        """
105
106    @abstractmethod
107    def get_request_body_data(
108        self,
109        *,
110        stream_state: Optional[StreamState] = None,
111        stream_slice: Optional[StreamSlice] = None,
112        next_page_token: Optional[Mapping[str, Any]] = None,
113    ) -> Union[Mapping[str, Any], str]:
114        """
115        Specifies how to populate the body of the request with a non-JSON payload.
116
117        If returns a ready text that it will be sent as is.
118        If returns a dict that it will be converted to a urlencoded form.
119        E.g. {"key1": "value1", "key2": "value2"} => "key1=value1&key2=value2"
120
121        At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.
122        """
123
124    @abstractmethod
125    def get_request_body_json(
126        self,
127        *,
128        stream_state: Optional[StreamState] = None,
129        stream_slice: Optional[StreamSlice] = None,
130        next_page_token: Optional[Mapping[str, Any]] = None,
131    ) -> Mapping[str, Any]:
132        """
133        Specifies how to populate the body of the request with a JSON payload.
134
135        At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.
136        """
137
138    @abstractmethod
139    def send_request(
140        self,
141        stream_state: Optional[StreamState] = None,
142        stream_slice: Optional[StreamSlice] = None,
143        next_page_token: Optional[Mapping[str, Any]] = None,
144        path: Optional[str] = None,
145        request_headers: Optional[Mapping[str, Any]] = None,
146        request_params: Optional[Mapping[str, Any]] = None,
147        request_body_data: Optional[Union[Mapping[str, Any], str]] = None,
148        request_body_json: Optional[Mapping[str, Any]] = None,
149        log_formatter: Optional[Callable[[requests.Response], Any]] = None,
150    ) -> Optional[requests.Response]:
151        """
152        Sends a request and returns the response. Might return no response if the error handler chooses to ignore the response or throw an exception in case of an error.
153        If path is set, the path configured on the requester itself is ignored.
154        If header, params and body are set, they are merged with the ones configured on the requester itself.
155
156        If a log formatter is provided, it's used to log the performed request and response. If it's not provided, no logging is performed.
157        """

Defines the request options to set on an outgoing HTTP request

Options can be passed by

  • request parameter
  • request headers
  • body data
  • json content
@abstractmethod
def get_authenticator( self) -> airbyte_cdk.DeclarativeAuthenticator:
31    @abstractmethod
32    def get_authenticator(self) -> DeclarativeAuthenticator:
33        """
34        Specifies the authenticator to use when submitting requests
35        """
36        pass

Specifies the authenticator to use when submitting requests

@abstractmethod
def get_url( self, *, stream_state: Optional[Mapping[str, Any]], stream_slice: Optional[airbyte_cdk.StreamSlice], next_page_token: Optional[Mapping[str, Any]]) -> str:
38    @abstractmethod
39    def get_url(
40        self,
41        *,
42        stream_state: Optional[StreamState],
43        stream_slice: Optional[StreamSlice],
44        next_page_token: Optional[Mapping[str, Any]],
45    ) -> str:
46        """
47        :return: URL base for the  API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"
48        """
Returns

URL base for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"

@abstractmethod
def get_url_base( self, *, stream_state: Optional[Mapping[str, Any]], stream_slice: Optional[airbyte_cdk.StreamSlice], next_page_token: Optional[Mapping[str, Any]]) -> str:
50    @abstractmethod
51    def get_url_base(
52        self,
53        *,
54        stream_state: Optional[StreamState],
55        stream_slice: Optional[StreamSlice],
56        next_page_token: Optional[Mapping[str, Any]],
57    ) -> str:
58        """
59        :return: URL base for the  API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"
60        """
Returns

URL base for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"

@abstractmethod
def get_path( self, *, stream_state: Optional[Mapping[str, Any]], stream_slice: Optional[airbyte_cdk.StreamSlice], next_page_token: Optional[Mapping[str, Any]]) -> str:
62    @abstractmethod
63    def get_path(
64        self,
65        *,
66        stream_state: Optional[StreamState],
67        stream_slice: Optional[StreamSlice],
68        next_page_token: Optional[Mapping[str, Any]],
69    ) -> str:
70        """
71        Returns the URL path for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "some_entity"
72        """

Returns the URL path for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "some_entity"

@abstractmethod
def get_method(self) -> airbyte_cdk.HttpMethod:
74    @abstractmethod
75    def get_method(self) -> HttpMethod:
76        """
77        Specifies the HTTP method to use
78        """

Specifies the HTTP method to use

@abstractmethod
def get_request_params( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> MutableMapping[str, Any]:
80    @abstractmethod
81    def get_request_params(
82        self,
83        *,
84        stream_state: Optional[StreamState] = None,
85        stream_slice: Optional[StreamSlice] = None,
86        next_page_token: Optional[Mapping[str, Any]] = None,
87    ) -> MutableMapping[str, Any]:
88        """
89        Specifies the query parameters that should be set on an outgoing HTTP request given the inputs.
90
91        E.g: you might want to define query parameters for paging if next_page_token is not None.
92        """

Specifies the query parameters that should be set on an outgoing HTTP request given the inputs.

E.g: you might want to define query parameters for paging if next_page_token is not None.

@abstractmethod
def get_request_headers( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> Mapping[str, Any]:
 94    @abstractmethod
 95    def get_request_headers(
 96        self,
 97        *,
 98        stream_state: Optional[StreamState] = None,
 99        stream_slice: Optional[StreamSlice] = None,
100        next_page_token: Optional[Mapping[str, Any]] = None,
101    ) -> Mapping[str, Any]:
102        """
103        Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.
104        """

Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.

@abstractmethod
def get_request_body_data( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> Union[Mapping[str, Any], str]:
106    @abstractmethod
107    def get_request_body_data(
108        self,
109        *,
110        stream_state: Optional[StreamState] = None,
111        stream_slice: Optional[StreamSlice] = None,
112        next_page_token: Optional[Mapping[str, Any]] = None,
113    ) -> Union[Mapping[str, Any], str]:
114        """
115        Specifies how to populate the body of the request with a non-JSON payload.
116
117        If returns a ready text that it will be sent as is.
118        If returns a dict that it will be converted to a urlencoded form.
119        E.g. {"key1": "value1", "key2": "value2"} => "key1=value1&key2=value2"
120
121        At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.
122        """

Specifies how to populate the body of the request with a non-JSON payload.

If returns a ready text that it will be sent as is. If returns a dict that it will be converted to a urlencoded form. E.g. {"key1": "value1", "key2": "value2"} => "key1=value1&key2=value2"

At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.

@abstractmethod
def get_request_body_json( self, *, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None) -> Mapping[str, Any]:
124    @abstractmethod
125    def get_request_body_json(
126        self,
127        *,
128        stream_state: Optional[StreamState] = None,
129        stream_slice: Optional[StreamSlice] = None,
130        next_page_token: Optional[Mapping[str, Any]] = None,
131    ) -> Mapping[str, Any]:
132        """
133        Specifies how to populate the body of the request with a JSON payload.
134
135        At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.
136        """

Specifies how to populate the body of the request with a JSON payload.

At the same time only one of the 'request_body_data' and 'request_body_json' functions can be overridden.

@abstractmethod
def send_request( self, stream_state: Optional[Mapping[str, Any]] = None, stream_slice: Optional[airbyte_cdk.StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, path: Optional[str] = None, request_headers: Optional[Mapping[str, Any]] = None, request_params: Optional[Mapping[str, Any]] = None, request_body_data: Union[Mapping[str, Any], str, NoneType] = None, request_body_json: Optional[Mapping[str, Any]] = None, log_formatter: Optional[Callable[[requests.models.Response], Any]] = None) -> Optional[requests.models.Response]:
138    @abstractmethod
139    def send_request(
140        self,
141        stream_state: Optional[StreamState] = None,
142        stream_slice: Optional[StreamSlice] = None,
143        next_page_token: Optional[Mapping[str, Any]] = None,
144        path: Optional[str] = None,
145        request_headers: Optional[Mapping[str, Any]] = None,
146        request_params: Optional[Mapping[str, Any]] = None,
147        request_body_data: Optional[Union[Mapping[str, Any], str]] = None,
148        request_body_json: Optional[Mapping[str, Any]] = None,
149        log_formatter: Optional[Callable[[requests.Response], Any]] = None,
150    ) -> Optional[requests.Response]:
151        """
152        Sends a request and returns the response. Might return no response if the error handler chooses to ignore the response or throw an exception in case of an error.
153        If path is set, the path configured on the requester itself is ignored.
154        If header, params and body are set, they are merged with the ones configured on the requester itself.
155
156        If a log formatter is provided, it's used to log the performed request and response. If it's not provided, no logging is performed.
157        """

Sends a request and returns the response. Might return no response if the error handler chooses to ignore the response or throw an exception in case of an error. If path is set, the path configured on the requester itself is ignored. If header, params and body are set, they are merged with the ones configured on the requester itself.

If a log formatter is provided, it's used to log the performed request and response. If it's not provided, no logging is performed.