From 3110caac4e308226741fa411e5c10fb0fb4a56a0 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 22 Mar 2024 13:58:09 -0700 Subject: [PATCH 01/27] yield from generators --- .../declarative/extractors/dpath_extractor.py | 10 +++--- .../declarative/extractors/http_selector.py | 4 +-- .../extractors/record_extractor.py | 4 +-- .../declarative/extractors/record_filter.py | 10 +++--- .../declarative/extractors/record_selector.py | 32 ++++++++++++------- .../paginators/default_paginator.py | 4 +-- .../paginators/strategies/offset_increment.py | 6 ++-- .../retrievers/simple_retriever.py | 14 +++++--- 8 files changed, 49 insertions(+), 35 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py index 99fe9378b6fe0..26ce85af49065 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Union +from typing import Any, Iterable, List, Mapping, Union import dpath.util import requests @@ -63,7 +63,7 @@ def __post_init__(self, parameters: Mapping[str, Any]): if isinstance(self.field_path[path_index], str): self.field_path[path_index] = InterpolatedString.create(self.field_path[path_index], parameters=parameters) - def extract_records(self, response: requests.Response) -> List[Mapping[str, Any]]: + def extract_records(self, response: requests.Response) -> Iterable[Mapping[str, Any]]: response_body = self.decoder.decode(response) if len(self.field_path) == 0: extracted = response_body @@ -74,8 +74,8 @@ def extract_records(self, response: requests.Response) -> List[Mapping[str, Any] else: extracted = dpath.util.get(response_body, path, default=[]) if isinstance(extracted, list): - return extracted + yield from extracted elif extracted: - return [extracted] + yield extracted else: - return [] + yield from [] diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py index 0da7125868f19..b867bcb49ceaa 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py @@ -4,7 +4,7 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any, List, Mapping, Optional +from typing import Any, Iterable, List, Mapping, Optional import requests from airbyte_cdk.sources.declarative.types import Record, StreamSlice, StreamState @@ -25,7 +25,7 @@ def select_records( records_schema: Mapping[str, Any], stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, - ) -> List[Record]: + ) -> Iterable[Record]: """ Selects records from the response :param response: The response to select the records from diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py index 792499c6dac61..fce9e1c783241 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py @@ -4,7 +4,7 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any, List, Mapping +from typing import Any, Iterable, List, Mapping import requests @@ -19,7 +19,7 @@ class RecordExtractor: def extract_records( self, response: requests.Response, - ) -> List[Mapping[str, Any]]: + ) -> Iterable[Mapping[str, Any]]: """ Selects records from the response :param response: The response to extract the records from diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py index 6bd46113e3ba9..eb92ec1474ad2 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Optional +from typing import Any, Iterable, List, Mapping, Optional from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean from airbyte_cdk.sources.declarative.types import Config, StreamSlice, StreamState @@ -27,10 +27,12 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None: def filter_records( self, - records: List[Mapping[str, Any]], + records: Iterable[Mapping[str, Any]], stream_state: StreamState, stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, - ) -> List[Mapping[str, Any]]: + ) -> Iterable[Mapping[str, Any]]: kwargs = {"stream_state": stream_state, "stream_slice": stream_slice, "next_page_token": next_page_token} - return [record for record in records if self._filter_interpolator.eval(self.config, record=record, **kwargs)] + for record in records: + if self._filter_interpolator.eval(self.config, record=record, **kwargs): + yield record diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py index 33ad173d54840..799871cb9ba0a 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass, field -from typing import Any, List, Mapping, Optional +from typing import Any, Iterable, List, Mapping, Optional import requests from airbyte_cdk.sources.declarative.extractors.http_selector import HttpSelector @@ -50,7 +50,7 @@ def select_records( records_schema: Mapping[str, Any], stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, - ) -> List[Record]: + ) -> Iterable[Record]: """ Selects records from the response :param response: The response to select the records from @@ -62,32 +62,39 @@ def select_records( """ all_data = self.extractor.extract_records(response) filtered_data = self._filter(all_data, stream_state, stream_slice, next_page_token) - self._transform(filtered_data, stream_state, stream_slice) - self._normalize_by_schema(filtered_data, schema=records_schema) - return [Record(data, stream_slice) for data in filtered_data] + transformed_data = self._transform(filtered_data, stream_state, stream_slice) + normalized_data = self._normalize_by_schema(transformed_data, schema=records_schema) + for data in normalized_data: + yield Record(data, stream_slice) - def _normalize_by_schema(self, records: List[Mapping[str, Any]], schema: Optional[Mapping[str, Any]]) -> List[Mapping[str, Any]]: + def _normalize_by_schema( + self, records: Iterable[Mapping[str, Any]], schema: Optional[Mapping[str, Any]] + ) -> Iterable[Mapping[str, Any]]: if schema: # record has type Mapping[str, Any], but dict[str, Any] expected - return [self.schema_normalization.transform(record, schema) for record in records] # type: ignore - return records + for record in records: + self.schema_normalization.transform(record, schema) + yield record + else: + yield from records def _filter( self, - records: List[Mapping[str, Any]], + records: Iterable[Mapping[str, Any]], stream_state: StreamState, stream_slice: Optional[StreamSlice], next_page_token: Optional[Mapping[str, Any]], ) -> List[Mapping[str, Any]]: if self.record_filter: - return self.record_filter.filter_records( + yield from self.record_filter.filter_records( records, stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token ) - return records + else: + yield from records def _transform( self, - records: List[Mapping[str, Any]], + records: Iterable[Mapping[str, Any]], stream_state: StreamState, stream_slice: Optional[StreamSlice] = None, ) -> None: @@ -95,3 +102,4 @@ def _transform( for transformation in self.transformations: # record has type Mapping[str, Any], but Record expected transformation.transform(record, config=self.config, stream_state=stream_state, stream_slice=stream_slice) # type: ignore + yield record diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py index 824efe9aed395..dade12a0b6ffc 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py @@ -101,8 +101,8 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None: self.url_base = InterpolatedString(string=self.url_base, parameters=parameters) self._token = self.pagination_strategy.initial_token - def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Optional[Mapping[str, Any]]: - self._token = self.pagination_strategy.next_page_token(response, last_records) + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Record) -> Optional[Mapping[str, Any]]: + self._token = self.pagination_strategy.next_page_token(response, last_page_size, last_record) if self._token: return {"next_page_token": self._token} else: diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py index 4a3224a4b4278..a6ef3eb20bc97 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py @@ -56,14 +56,14 @@ def initial_token(self) -> Optional[Any]: return self._offset return None - def next_page_token(self, response: requests.Response, last_records: List[Mapping[str, Any]]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Mapping[str, Any]) -> Optional[Any]: decoded_response = self.decoder.decode(response) # Stop paginating when there are fewer records than the page size or the current page has no records - if (self._page_size and len(last_records) < self._page_size.eval(self.config, response=decoded_response)) or len(last_records) == 0: + if (self._page_size and last_page_size < self._page_size.eval(self.config, response=decoded_response)) or last_page_size == 0: return None else: - self._offset += len(last_records) + self._offset += last_page_size return self._offset def reset(self): diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py index 258206678f9f9..8421beb630fd1 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py @@ -64,7 +64,8 @@ class SimpleRetriever(Retriever): def __post_init__(self, parameters: Mapping[str, Any]) -> None: self._paginator = self.paginator or NoPagination(parameters=parameters) self._last_response: Optional[requests.Response] = None - self._records_from_last_response: List[Record] = [] + self._last_page_size: int = 0 + self._last_record: Mapping[str, Any] = {} self._parameters = parameters self._name = InterpolatedString(self._name, parameters=parameters) if isinstance(self._name, str) else self._name @@ -227,15 +228,18 @@ def _parse_response( return [] self._last_response = response - records = self.record_selector.select_records( + record_generator = self.record_selector.select_records( response=response, stream_state=stream_state, records_schema=records_schema, stream_slice=stream_slice, next_page_token=next_page_token, ) - self._records_from_last_response = records - return records + self._last_page_size = 0 + for record in record_generator: + self._last_page_size += 1 + self._last_record = record + yield record @property # type: ignore def primary_key(self) -> Optional[Union[str, List[str], List[List[str]]]]: @@ -255,7 +259,7 @@ def _next_page_token(self, response: requests.Response) -> Optional[Mapping[str, :return: The token for the next page from the input response object. Returning None means there are no more pages to read in this response. """ - return self._paginator.next_page_token(response, self._records_from_last_response) + return self._paginator.next_page_token(response, self._last_page_size, self._last_record) def _fetch_next_page( self, stream_state: Mapping[str, Any], stream_slice: StreamSlice, next_page_token: Optional[Mapping[str, Any]] = None From 10640ab463cad0a0bf2b15d616433da1e7752cc5 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 22 Mar 2024 14:44:16 -0700 Subject: [PATCH 02/27] update interfaces and fix the tests --- .../paginators/default_paginator.py | 10 ++-- .../requesters/paginators/no_pagination.py | 2 +- .../requesters/paginators/paginator.py | 7 ++- .../strategies/cursor_pagination_strategy.py | 6 +-- .../paginators/strategies/offset_increment.py | 4 +- .../paginators/strategies/page_increment.py | 4 +- .../strategies/pagination_strategy.py | 5 +- .../paginators/strategies/stop_condition.py | 6 +-- .../retrievers/simple_retriever.py | 2 +- .../extractors/test_dpath_extractor.py | 2 +- .../extractors/test_record_filter.py | 4 +- .../extractors/test_record_selector.py | 8 ++-- .../test_cursor_pagination_strategy.py | 6 +-- .../paginators/test_default_paginator.py | 28 +++++------ .../paginators/test_no_paginator.py | 2 +- .../paginators/test_offset_increment.py | 16 +++---- .../paginators/test_page_increment.py | 20 ++++---- .../paginators/test_stop_condition.py | 22 ++++----- .../retrievers/test_simple_retriever.py | 46 ++----------------- 19 files changed, 83 insertions(+), 117 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py index dade12a0b6ffc..af95d79524dfd 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py @@ -101,7 +101,9 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None: self.url_base = InterpolatedString(string=self.url_base, parameters=parameters) self._token = self.pagination_strategy.initial_token - def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Record) -> Optional[Mapping[str, Any]]: + def next_page_token( + self, response: requests.Response, last_page_size: int, last_record: Optional[Record] + ) -> Optional[Mapping[str, Any]]: self._token = self.pagination_strategy.next_page_token(response, last_page_size, last_record) if self._token: return {"next_page_token": self._token} @@ -185,12 +187,14 @@ def __init__(self, decorated: Paginator, maximum_number_of_pages: int = 5) -> No self._decorated = decorated self._page_count = self._PAGE_COUNT_BEFORE_FIRST_NEXT_CALL - def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Optional[Mapping[str, Any]]: + def next_page_token( + self, response: requests.Response, last_page_size: int, last_record: Optional[Record] + ) -> Optional[Mapping[str, Any]]: if self._page_count >= self._maximum_number_of_pages: return None self._page_count += 1 - return self._decorated.next_page_token(response, last_records) + return self._decorated.next_page_token(response, last_page_size, last_record) def path(self) -> Optional[str]: return self._decorated.path() diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py index 683508c761aa5..6d615fb76a20b 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py @@ -57,7 +57,7 @@ def get_request_body_json( ) -> Mapping[str, Any]: return {} - def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Mapping[str, Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Record]) -> Mapping[str, Any]: return {} def reset(self) -> None: diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py index 2138712875dc1..344fae310a17f 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py @@ -27,12 +27,15 @@ def reset(self) -> None: """ @abstractmethod - def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Optional[Mapping[str, Any]]: + def next_page_token( + self, response: requests.Response, last_page_size: int, last_record: Optional[Record] + ) -> Optional[Mapping[str, Any]]: """ Returns the next_page_token to use to fetch the next page of records. :param response: the response to process - :param last_records: the records extracted from the response + :param last_page_size: the number of records read from the response + :param last_record: the last record extracted from the response :return: A mapping {"next_page_token": } for the next page from the input response object. Returning None means there are no more pages to read in this response. """ pass diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index bea36fc8e3230..8370758d37713 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -44,7 +44,7 @@ def __post_init__(self, parameters: Mapping[str, Any]): def initial_token(self) -> Optional[Any]: return None - def next_page_token(self, response: requests.Response, last_records: List[Mapping[str, Any]]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Mapping[str, Any]]) -> Optional[Any]: decoded_response = self.decoder.decode(response) # The default way that link is presented in requests.Response is a string of various links (last, next, etc). This @@ -53,10 +53,10 @@ def next_page_token(self, response: requests.Response, last_records: List[Mappin headers["link"] = response.links if self.stop_condition: - should_stop = self.stop_condition.eval(self.config, response=decoded_response, headers=headers, last_records=last_records) + should_stop = self.stop_condition.eval(self.config, response=decoded_response, headers=headers, last_record=last_record) if should_stop: return None - token = self.cursor_value.eval(config=self.config, last_records=last_records, response=decoded_response, headers=headers) + token = self.cursor_value.eval(config=self.config, last_record=last_record, response=decoded_response, headers=headers) return token if token else None def reset(self): diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py index a6ef3eb20bc97..c4bdcba6e7345 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py @@ -9,7 +9,7 @@ from airbyte_cdk.sources.declarative.decoders import Decoder, JsonDecoder from airbyte_cdk.sources.declarative.interpolation import InterpolatedString from airbyte_cdk.sources.declarative.requesters.paginators.strategies.pagination_strategy import PaginationStrategy -from airbyte_cdk.sources.declarative.types import Config +from airbyte_cdk.sources.declarative.types import Config, Record @dataclass @@ -56,7 +56,7 @@ def initial_token(self) -> Optional[Any]: return self._offset return None - def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Mapping[str, Any]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Record]) -> Optional[Any]: decoded_response = self.decoder.decode(response) # Stop paginating when there are fewer records than the page size or the current page has no records diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py index d68f573ab1ea0..442e26c246aba 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py @@ -43,9 +43,9 @@ def initial_token(self) -> Optional[Any]: return self._page return None - def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Record]) -> Optional[Any]: # Stop paginating when there are fewer records than the page size or the current page has no records - if (self._page_size and len(last_records) < self._page_size) or len(last_records) == 0: + if (self._page_size and last_page_size < self._page_size) or last_page_size == 0: return None else: self._page += 1 diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py index 3ebe49a059b57..6bcff1e1e907e 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py @@ -24,10 +24,11 @@ def initial_token(self) -> Optional[Any]: """ @abstractmethod - def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Record]) -> Optional[Any]: """ :param response: response to process - :param last_records: records extracted from the response + :param last_page_size: the number of records read from the response + :param last_record: the last record extracted from the response :return: next page token. Returns None if there are no more pages to fetch """ pass diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py index 8732e39ffef53..db874d834fd61 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py @@ -35,12 +35,12 @@ def __init__(self, _delegate: PaginationStrategy, stop_condition: PaginationStop self._delegate = _delegate self._stop_condition = stop_condition - def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Record]) -> Optional[Any]: # We evaluate in reverse order because the assumption is that most of the APIs using data feed structure will return records in # descending order. In terms of performance/memory, we return the records lazily - if last_records and any(self._stop_condition.is_met(record) for record in reversed(last_records)): + if last_record and self._stop_condition.is_met(last_record): return None - return self._delegate.next_page_token(response, last_records) + return self._delegate.next_page_token(response, last_page_size, last_record) def reset(self) -> None: self._delegate.reset() diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py index 8421beb630fd1..980627e351f03 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py @@ -65,7 +65,7 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None: self._paginator = self.paginator or NoPagination(parameters=parameters) self._last_response: Optional[requests.Response] = None self._last_page_size: int = 0 - self._last_record: Mapping[str, Any] = {} + self._last_record: Optional[Record] = None self._parameters = parameters self._name = InterpolatedString(self._name, parameters=parameters) if isinstance(self._name, str) else self._name diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_dpath_extractor.py b/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_dpath_extractor.py index 76fad421ea766..948fb19c921ae 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_dpath_extractor.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_dpath_extractor.py @@ -44,7 +44,7 @@ def test_dpath_extractor(test_name, field_path, body, expected_records): extractor = DpathExtractor(field_path=field_path, config=config, decoder=decoder, parameters=parameters) response = create_response(body) - actual_records = extractor.extract_records(response) + actual_records = list(extractor.extract_records(response)) assert actual_records == expected_records diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_filter.py b/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_filter.py index 89d003b776527..2104e4243120a 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_filter.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_filter.py @@ -49,7 +49,7 @@ def test_record_filter(test_name, filter_template, records, expected_records): next_page_token = {"last_seen_id": 14} record_filter = RecordFilter(config=config, condition=filter_template, parameters=parameters) - actual_records = record_filter.filter_records( + actual_records = list(record_filter.filter_records( records, stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token - ) + )) assert actual_records == expected_records diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_selector.py b/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_selector.py index 5fa6af43d831f..91c1ba13fc4ea 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_selector.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/extractors/test_record_selector.py @@ -90,9 +90,9 @@ def test_record_filter(test_name, field_path, filter_template, body, expected_da schema_normalization=TypeTransformer(TransformConfig.NoTransform), ) - actual_records = record_selector.select_records( + actual_records = list(record_selector.select_records( response=response, records_schema=schema, stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token - ) + )) assert actual_records == [Record(data, stream_slice) for data in expected_data] calls = [] @@ -149,13 +149,13 @@ def test_schema_normalization(test_name, schema, schema_transformation, body, ex schema_normalization=TypeTransformer(schema_transformation), ) - actual_records = record_selector.select_records( + actual_records = list(record_selector.select_records( response=response, stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token, records_schema=schema, - ) + )) assert actual_records == [Record(data, stream_slice) for data in expected_data] diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py index ce15b586125b5..70c33bedccb56 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py @@ -17,7 +17,7 @@ ("test_static_token", "token", None, "token", None), ("test_static_token_with_page_size", "token", None, "token", 5), ("test_token_from_config", "{{ config.config_key }}", None, "config_value", None), - ("test_token_from_last_record", "{{ last_records[-1].id }}", None, 1, None), + ("test_token_from_last_record", "{{ last_record.id }}", None, 1, None), ("test_token_from_response", "{{ response._metadata.content }}", None, "content_value", None), ("test_token_from_parameters", "{{ parameters.key }}", None, "value", None), ("test_token_not_found", "{{ response.invalid_key }}", None, None, None), @@ -57,8 +57,8 @@ def test_cursor_pagination_strategy(test_name, template_string, stop_condition, response.headers = {"has_more": True, "next": "ready_to_go", "link": link_str} response_body = {"_metadata": {"content": "content_value"}, "accounts": [], "end": 99, "total": 200, "characters": {}} response._content = json.dumps(response_body).encode("utf-8") - last_records = [{"id": 0, "more_records": True}, {"id": 1, "more_records": True}] + last_record = {"id": 1, "more_records": True} - token = strategy.next_page_token(response, last_records) + token = strategy.next_page_token(response, 1, last_record) assert expected_token == token assert page_size == strategy.get_page_size() diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py index d8326d5227ecb..923083342808a 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py @@ -21,7 +21,7 @@ @pytest.mark.parametrize( - "page_token_request_option, stop_condition, expected_updated_path, expected_request_params, expected_headers, expected_body_data, expected_body_json, last_records, expected_next_page_token, limit", + "page_token_request_option, stop_condition, expected_updated_path, expected_request_params, expected_headers, expected_body_data, expected_body_json, last_record, expected_next_page_token, limit", [ ( RequestPath(parameters={}), @@ -31,7 +31,7 @@ {}, {}, {}, - [{"id": 0}, {"id": 1}], + {"id": 1}, {"next_page_token": "https://airbyte.io/next_url"}, 2, ), @@ -43,7 +43,7 @@ {}, {}, {}, - [{"id": 0}, {"id": 1}], + {"id": 1}, {"next_page_token": "https://airbyte.io/next_url"}, 2, ), @@ -55,7 +55,7 @@ {}, {}, {}, - [{"id": 0}, {"id": 1}], + {"id": 1}, None, 2, ), @@ -67,7 +67,7 @@ {"from": "https://airbyte.io/next_url"}, {}, {}, - [{"id": 0}, {"id": 1}], + {"id": 1}, {"next_page_token": "https://airbyte.io/next_url"}, 2, ), @@ -79,7 +79,7 @@ {}, {"from": "https://airbyte.io/next_url"}, {}, - [{"id": 0}, {"id": 1}], + {"id": 1}, {"next_page_token": "https://airbyte.io/next_url"}, 2, ), @@ -91,7 +91,7 @@ {}, {}, {"from": "https://airbyte.io/next_url"}, - [{"id": 0}, {"id": 1}], + {"id": 1}, {"next_page_token": "https://airbyte.io/next_url"}, 2, ), @@ -113,7 +113,7 @@ def test_default_paginator_with_cursor( expected_headers, expected_body_data, expected_body_json, - last_records, + last_record, expected_next_page_token, limit, ): @@ -146,7 +146,7 @@ def test_default_paginator_with_cursor( response_body = {"next": "https://airbyte.io/next_url"} response._content = json.dumps(response_body).encode("utf-8") - actual_next_page_token = paginator.next_page_token(response, last_records) + actual_next_page_token = paginator.next_page_token(response, 2, last_record) actual_next_path = paginator.path() actual_request_params = paginator.get_request_params() actual_headers = paginator.get_request_headers() @@ -210,8 +210,8 @@ def test_paginator_request_param_interpolation( response.headers = {"A_HEADER": "HEADER_VALUE"} response_body = {"next": "https://airbyte.io/next_url"} response._content = json.dumps(response_body).encode("utf-8") - last_records = [{"id": 0}, {"id": 1}] - paginator.next_page_token(response, last_records) + last_record = {"id": 1} + paginator.next_page_token(response, 2, last_record) actual_request_params = paginator.get_request_params() assert actual_request_params == expected_request_params @@ -255,7 +255,7 @@ def test_reset(test_name, inject_on_first_request): strategy, config, url_base, parameters={}, page_size_option=page_size_request_option, page_token_option=page_token_request_option ) initial_request_parameters = paginator.get_request_params() - paginator.next_page_token(MagicMock(), [{"first key": "first value"}, {"second key": "second value"}]) + paginator.next_page_token(MagicMock(), 2, {"a key": "a value"}) request_parameters_for_second_request = paginator.get_request_params() paginator.reset() request_parameters_after_reset = paginator.get_request_params() @@ -293,10 +293,10 @@ def test_limit_page_fetched(): ) for _ in range(number_of_next_performed): - last_token = paginator.next_page_token(MagicMock(), MagicMock()) + last_token = paginator.next_page_token(MagicMock(), 1, MagicMock()) assert last_token - assert not paginator.next_page_token(MagicMock(), MagicMock()) + assert not paginator.next_page_token(MagicMock(), 1, MagicMock()) def test_paginator_with_page_option_no_page_size(): diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py index 33910f54499c3..12b81010f43b1 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py @@ -8,5 +8,5 @@ def test(): paginator = NoPagination(parameters={}) - next_page_token = paginator.next_page_token(requests.Response(), []) + next_page_token = paginator.next_page_token(requests.Response(), 0, []) assert next_page_token == {} diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py index 37f26a2af420c..326de7b7d197f 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py @@ -11,16 +11,16 @@ @pytest.mark.parametrize( - "page_size, parameters, last_records, expected_next_page_token, expected_offset", + "page_size, parameters, last_page_size, last_record, expected_next_page_token, expected_offset", [ - pytest.param("2", {}, [{"id": 0}, {"id": 1}], 2, 2, id="test_same_page_size"), - pytest.param(2, {}, [{"id": 0}, {"id": 1}], 2, 2, id="test_same_page_size"), - pytest.param("{{ parameters['page_size'] }}", {"page_size": 3}, [{"id": 0}, {"id": 1}], None, 0, id="test_larger_page_size"), - pytest.param(None, {}, [], None, 0, id="test_stop_if_no_records"), - pytest.param("{{ response['page_metadata']['limit'] }}", {}, [{"id": 0}, {"id": 1}], None, 0, id="test_page_size_from_response"), + pytest.param("2", {}, 2, {"id": 1}, 2, 2, id="test_same_page_size"), + pytest.param(2, {}, 2, {"id": 1}, 2, 2, id="test_same_page_size"), + pytest.param("{{ parameters['page_size'] }}", {"page_size": 3}, 2, {"id": 1}, None, 0, id="test_larger_page_size"), + pytest.param(None, {}, 0, [], None, 0, id="test_stop_if_no_records"), + pytest.param("{{ response['page_metadata']['limit'] }}", {}, 2, {"id": 1}, None, 0, id="test_page_size_from_response"), ], ) -def test_offset_increment_paginator_strategy(page_size, parameters, last_records, expected_next_page_token, expected_offset): +def test_offset_increment_paginator_strategy(page_size, parameters, last_page_size, last_record, expected_next_page_token, expected_offset): paginator_strategy = OffsetIncrement(page_size=page_size, parameters=parameters, config={}) assert paginator_strategy._offset == 0 @@ -30,7 +30,7 @@ def test_offset_increment_paginator_strategy(page_size, parameters, last_records response_body = {"next": "https://airbyte.io/next_url", "page_metadata": {"limit": 5}} response._content = json.dumps(response_body).encode("utf-8") - next_page_token = paginator_strategy.next_page_token(response, last_records) + next_page_token = paginator_strategy.next_page_token(response, last_page_size, last_record) assert expected_next_page_token == next_page_token assert expected_offset == paginator_strategy._offset diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_page_increment.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_page_increment.py index 42d7995388e71..eaa2b41ec07fe 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_page_increment.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_page_increment.py @@ -11,18 +11,18 @@ @pytest.mark.parametrize( - "page_size, start_from, last_records, expected_next_page_token, expected_offset", + "page_size, start_from, last_page_size, last_record, expected_next_page_token, expected_offset", [ - pytest.param(2, 1, [{"id": 0}, {"id": 1}], 2, 2, id="test_same_page_size_start_from_0"), - pytest.param(3, 1, [{"id": 0}, {"id": 1}], None, 1, id="test_larger_page_size_start_from_0"), - pytest.param(2, 0, [{"id": 0}, {"id": 1}], 1, 1, id="test_same_page_size_start_from_1"), - pytest.param(3, 0, [{"id": 0}, {"id": 1}], None, 0, id="test_larger_page_size_start_from_0"), - pytest.param(None, 0, [], None, 0, id="test_no_page_size"), - pytest.param("2", 0, [{"id": 0}, {"id": 1}], 1, 1, id="test_page_size_from_string"), - pytest.param("{{ config['value'] }}", 0, [{"id": 0}, {"id": 1}], 1, 1, id="test_page_size_from_config"), + pytest.param(2, 1, 2, {"id": 1}, 2, 2, id="test_same_page_size_start_from_0"), + pytest.param(3, 1, 2, {"id": 1}, None, 1, id="test_larger_page_size_start_from_0"), + pytest.param(2, 0, 2, {"id": 1}, 1, 1, id="test_same_page_size_start_from_1"), + pytest.param(3, 0, 2, {"id": 1}, None, 0, id="test_larger_page_size_start_from_0"), + pytest.param(None, 0, 0, None, None, 0, id="test_no_page_size"), + pytest.param("2", 0, 2, {"id": 1}, 1, 1, id="test_page_size_from_string"), + pytest.param("{{ config['value'] }}", 0, 2, {"id": 1}, 1, 1, id="test_page_size_from_config"), ], ) -def test_page_increment_paginator_strategy(page_size, start_from, last_records, expected_next_page_token, expected_offset): +def test_page_increment_paginator_strategy(page_size, start_from, last_page_size, last_record, expected_next_page_token, expected_offset): paginator_strategy = PageIncrement(page_size=page_size, parameters={}, start_from_page=start_from, config={"value": 2}) assert paginator_strategy._page == start_from @@ -32,7 +32,7 @@ def test_page_increment_paginator_strategy(page_size, start_from, last_records, response_body = {"next": "https://airbyte.io/next_url"} response._content = json.dumps(response_body).encode("utf-8") - next_page_token = paginator_strategy.next_page_token(response, last_records) + next_page_token = paginator_strategy.next_page_token(response, last_page_size, last_record) assert expected_next_page_token == next_page_token assert expected_offset == paginator_strategy._page diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_stop_condition.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_stop_condition.py index b2aa1117362dc..46f929e328cd8 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_stop_condition.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_stop_condition.py @@ -15,7 +15,7 @@ from pytest import fixture ANY_RECORD = Mock() -NO_RECORDS = [] +NO_RECORD = None ANY_RESPONSE = Mock() @@ -45,14 +45,13 @@ def test_given_record_should_not_be_synced_when_is_met_return_true(mocked_cursor def test_given_stop_condition_is_met_when_next_page_token_then_return_none(mocked_pagination_strategy, mocked_stop_condition): - mocked_stop_condition.is_met.side_effect = [False, True] - first_record = Mock(spec=Record) + mocked_stop_condition.is_met.return_value = True last_record = Mock(spec=Record) decorator = StopConditionPaginationStrategyDecorator(mocked_pagination_strategy, mocked_stop_condition) - assert not decorator.next_page_token(ANY_RESPONSE, [first_record, last_record]) - mocked_stop_condition.is_met.assert_has_calls([call(last_record), call(first_record)]) + assert not decorator.next_page_token(ANY_RESPONSE, 2, last_record) + mocked_stop_condition.is_met.assert_has_calls([call(last_record)]) def test_given_last_record_meets_condition_when_next_page_token_then_do_not_check_for_other_records( @@ -62,7 +61,7 @@ def test_given_last_record_meets_condition_when_next_page_token_then_do_not_chec last_record = Mock(spec=Record) StopConditionPaginationStrategyDecorator(mocked_pagination_strategy, mocked_stop_condition).next_page_token( - ANY_RESPONSE, [Mock(spec=Record), last_record] + ANY_RESPONSE, 2, last_record ) mocked_stop_condition.is_met.assert_called_once_with(last_record) @@ -70,24 +69,23 @@ def test_given_last_record_meets_condition_when_next_page_token_then_do_not_chec def test_given_stop_condition_is_not_met_when_next_page_token_then_delegate(mocked_pagination_strategy, mocked_stop_condition): mocked_stop_condition.is_met.return_value = False - first_record = Mock(spec=Record) last_record = Mock(spec=Record) decorator = StopConditionPaginationStrategyDecorator(mocked_pagination_strategy, mocked_stop_condition) - next_page_token = decorator.next_page_token(ANY_RESPONSE, [first_record, last_record]) + next_page_token = decorator.next_page_token(ANY_RESPONSE, 2, last_record) assert next_page_token == mocked_pagination_strategy.next_page_token.return_value - mocked_pagination_strategy.next_page_token.assert_called_once_with(ANY_RESPONSE, [first_record, last_record]) - mocked_stop_condition.is_met.assert_has_calls([call(last_record), call(first_record)]) + mocked_pagination_strategy.next_page_token.assert_called_once_with(ANY_RESPONSE, 2, last_record) + mocked_stop_condition.is_met.assert_has_calls([call(last_record)]) def test_given_no_records_when_next_page_token_then_delegate(mocked_pagination_strategy, mocked_stop_condition): decorator = StopConditionPaginationStrategyDecorator(mocked_pagination_strategy, mocked_stop_condition) - next_page_token = decorator.next_page_token(ANY_RESPONSE, NO_RECORDS) + next_page_token = decorator.next_page_token(ANY_RESPONSE, 0, NO_RECORD) assert next_page_token == mocked_pagination_strategy.next_page_token.return_value - mocked_pagination_strategy.next_page_token.assert_called_once_with(ANY_RESPONSE, NO_RECORDS) + mocked_pagination_strategy.next_page_token.assert_called_once_with(ANY_RESPONSE, 0, NO_RECORD) def test_when_reset_then_delegate(mocked_pagination_strategy, mocked_stop_condition): diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py b/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py index 83058c195e800..06cc874061bb7 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py @@ -92,10 +92,10 @@ def test_simple_retriever_full(mock_http_stream): assert retriever.stream_slices() == stream_slices assert retriever._last_response is None - assert retriever._records_from_last_response == [] - assert retriever._parse_response(response, stream_state={}, records_schema={}) == records + assert retriever._last_record is None + assert list(retriever._parse_response(response, stream_state={}, records_schema={})) == records assert retriever._last_response == response - assert retriever._records_from_last_response == records + assert retriever._last_page_size == 2 [r for r in retriever.read_records(SyncMode.full_refresh)] paginator.reset.assert_called() @@ -137,46 +137,6 @@ def test_simple_retriever_with_request_response_logs(mock_http_stream): assert actual_messages[3] == records[1] -@patch.object(SimpleRetriever, "_read_pages", return_value=iter([])) -def test_simple_retriever_with_request_response_log_last_records(mock_http_stream): - requester = MagicMock() - paginator = MagicMock() - record_selector = MagicMock() - record_selector.select_records.return_value = request_response_logs - response = requests.Response() - response.status_code = 200 - stream_slicer = DatetimeBasedCursor( - start_datetime="", - end_datetime="", - step="P1D", - cursor_field="id", - datetime_format="", - cursor_granularity="P1D", - config={}, - parameters={}, - ) - - retriever = SimpleRetriever( - name="stream_name", - primary_key=primary_key, - requester=requester, - paginator=paginator, - record_selector=record_selector, - stream_slicer=stream_slicer, - parameters={}, - config={}, - ) - - assert retriever._last_response is None - assert retriever._records_from_last_response == [] - assert retriever._parse_response(response, stream_state={}, records_schema={}) == request_response_logs - assert retriever._last_response == response - assert retriever._records_from_last_response == request_response_logs - - [r for r in retriever.read_records(SyncMode.full_refresh)] - paginator.reset.assert_called() - - @pytest.mark.parametrize( "test_name, paginator_mapping, stream_slicer_mapping, expected_mapping", [ From 148419e93d8829684d7959fcf89fe1a5bcd26306 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 22 Mar 2024 15:12:41 -0700 Subject: [PATCH 03/27] fix some mypy issues --- .../paginators/default_paginator.py | 10 +++---- .../strategies/cursor_pagination_strategy.py | 27 ++++++++++++------- .../paginators/strategies/offset_increment.py | 11 +++----- .../retrievers/simple_retriever.py | 1 - .../test_model_to_component_factory.py | 6 ++--- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py index af95d79524dfd..f6bd1df2366e6 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py @@ -166,9 +166,9 @@ def _get_request_options(self, option_type: RequestOptionType) -> MutableMapping and isinstance(self.page_token_option, RequestOption) and self.page_token_option.inject_into == option_type ): - options[self.page_token_option.field_name.eval(config=self.config)] = self._token + options[self.page_token_option.field_name.eval(config=self.config)] = self._token # type: ignore # field_name is known to be an interpolated string if self.page_size_option and self.pagination_strategy.get_page_size() and self.page_size_option.inject_into == option_type: - options[self.page_size_option.field_name.eval(config=self.config)] = self.pagination_strategy.get_page_size() + options[self.page_size_option.field_name.eval(config=self.config)] = self.pagination_strategy.get_page_size() # type: ignore # field_name is known to be an interpolated string return options @@ -205,7 +205,7 @@ def get_request_params( stream_state: Optional[StreamState] = None, stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, - ) -> MutableMapping[str, Any]: + ) -> Mapping[str, Any]: return self._decorated.get_request_params(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token) def get_request_headers( @@ -223,7 +223,7 @@ def get_request_body_data( stream_state: Optional[StreamState] = None, stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, - ) -> Optional[Union[Mapping[str, Any], str]]: + ) -> Union[Mapping[str, Any], str]: return self._decorated.get_request_body_data(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token) def get_request_body_json( @@ -232,7 +232,7 @@ def get_request_body_json( stream_state: Optional[StreamState] = None, stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, - ) -> Optional[Mapping[str, Any]]: + ) -> Mapping[str, Any]: return self._decorated.get_request_body_json(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token) def reset(self) -> None: diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index 8370758d37713..6b1c629f3bec7 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Optional, Union +from typing import Any, List, Mapping, Optional, Union, Dict import requests from airbyte_cdk.sources.declarative.decoders.decoder import Decoder @@ -13,6 +13,8 @@ from airbyte_cdk.sources.declarative.requesters.paginators.strategies.pagination_strategy import PaginationStrategy from airbyte_cdk.sources.declarative.types import Config +from airbyte_cdk.sources.declarative.types import Record + @dataclass class CursorPaginationStrategy(PaginationStrategy): @@ -34,32 +36,37 @@ class CursorPaginationStrategy(PaginationStrategy): stop_condition: Optional[Union[InterpolatedBoolean, str]] = None decoder: Decoder = JsonDecoder(parameters={}) - def __post_init__(self, parameters: Mapping[str, Any]): + def __post_init__(self, parameters: Mapping[str, Any]) -> None: if isinstance(self.cursor_value, str): - self.cursor_value = InterpolatedString.create(self.cursor_value, parameters=parameters) + self._cursor_value = InterpolatedString.create(self.cursor_value, parameters=parameters) + else: + self._cursor_value = self.cursor_value + self._stop_condition: Optional[InterpolatedBoolean] = None if isinstance(self.stop_condition, str): - self.stop_condition = InterpolatedBoolean(condition=self.stop_condition, parameters=parameters) + self._stop_condition = InterpolatedBoolean(condition=self.stop_condition, parameters=parameters) + else: + self._stop_condition = self.stop_condition @property def initial_token(self) -> Optional[Any]: return None - def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Mapping[str, Any]]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_page_size: int, last_record: Optional[Record]) -> Optional[Any]: decoded_response = self.decoder.decode(response) # The default way that link is presented in requests.Response is a string of various links (last, next, etc). This # is not indexable or useful for parsing the cursor, so we replace it with the link dictionary from response.links - headers = response.headers + headers: Dict[str, Any] = dict(response.headers) headers["link"] = response.links - if self.stop_condition: - should_stop = self.stop_condition.eval(self.config, response=decoded_response, headers=headers, last_record=last_record) + if self._stop_condition: + should_stop = self._stop_condition.eval(self.config, response=decoded_response, headers=headers, last_record=last_record) if should_stop: return None - token = self.cursor_value.eval(config=self.config, last_record=last_record, response=decoded_response, headers=headers) + token = self._cursor_value.eval(config=self.config, last_record=last_record, response=decoded_response, headers=headers) return token if token else None - def reset(self): + def reset(self) -> None: # No state to reset pass diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py index c4bdcba6e7345..1e1ec1ea293a8 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py @@ -42,13 +42,10 @@ class OffsetIncrement(PaginationStrategy): decoder: Decoder = JsonDecoder(parameters={}) inject_on_first_request: bool = False - def __post_init__(self, parameters: Mapping[str, Any]): + def __post_init__(self, parameters: Mapping[str, Any]) -> None: self._offset = 0 page_size = str(self.page_size) if isinstance(self.page_size, int) else self.page_size - if page_size: - self._page_size = InterpolatedString(page_size, parameters=parameters) - else: - self._page_size = None + self._page_size = InterpolatedString(page_size, parameters=parameters) if page_size else None @property def initial_token(self) -> Optional[Any]: @@ -66,7 +63,7 @@ def next_page_token(self, response: requests.Response, last_page_size: int, last self._offset += last_page_size return self._offset - def reset(self): + def reset(self) -> None: self._offset = 0 def get_page_size(self) -> Optional[int]: @@ -76,4 +73,4 @@ def get_page_size(self) -> Optional[int]: raise Exception(f"{page_size} is of type {type(page_size)}. Expected {int}") return page_size else: - return self._page_size + return None diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py index 980627e351f03..5acc937868497 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py @@ -224,7 +224,6 @@ def _parse_response( ) -> Iterable[Record]: if not response: self._last_response = None - self._records_from_last_response = [] return [] self._last_response = response diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py b/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py index 9c2b536832d43..2379b777ec857 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py @@ -243,8 +243,8 @@ def test_full_config_stream(): assert isinstance(stream.retriever.paginator.pagination_strategy, CursorPaginationStrategy) assert isinstance(stream.retriever.paginator.pagination_strategy.decoder, JsonDecoder) - assert stream.retriever.paginator.pagination_strategy.cursor_value.string == "{{ response._metadata.next }}" - assert stream.retriever.paginator.pagination_strategy.cursor_value.default == "{{ response._metadata.next }}" + assert stream.retriever.paginator.pagination_strategy._cursor_value.string == "{{ response._metadata.next }}" + assert stream.retriever.paginator.pagination_strategy._cursor_value.default == "{{ response._metadata.next }}" assert stream.retriever.paginator.pagination_strategy.page_size == 10 assert isinstance(stream.retriever.requester, HttpRequester) @@ -1128,7 +1128,7 @@ def test_create_default_paginator(): assert isinstance(paginator.pagination_strategy, CursorPaginationStrategy) assert paginator.pagination_strategy.page_size == 50 - assert paginator.pagination_strategy.cursor_value.string == "{{ response._metadata.next }}" + assert paginator.pagination_strategy._cursor_value.string == "{{ response._metadata.next }}" assert isinstance(paginator.page_size_option, RequestOption) assert paginator.page_size_option.inject_into == RequestOptionType.request_parameter From 3a7a0b8b76c7d29b8933eaeb1798e6404d55111d Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 22 Mar 2024 15:23:04 -0700 Subject: [PATCH 04/27] fix the rest of mypy issues --- .../sources/declarative/extractors/dpath_extractor.py | 10 ++++------ .../sources/declarative/extractors/record_selector.py | 11 ++++++----- .../requesters/paginators/default_paginator.py | 4 ++-- .../strategies/cursor_pagination_strategy.py | 6 ++---- .../parsers/test_model_to_component_factory.py | 6 +++--- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py index 26ce85af49065..3cde05d0504ab 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py @@ -58,17 +58,15 @@ class DpathExtractor(RecordExtractor): parameters: InitVar[Mapping[str, Any]] decoder: Decoder = JsonDecoder(parameters={}) - def __post_init__(self, parameters: Mapping[str, Any]): - for path_index in range(len(self.field_path)): - if isinstance(self.field_path[path_index], str): - self.field_path[path_index] = InterpolatedString.create(self.field_path[path_index], parameters=parameters) + def __post_init__(self, parameters: Mapping[str, Any]) -> None: + self._field_path = [InterpolatedString.create(path, parameters=parameters) for path in self.field_path] def extract_records(self, response: requests.Response) -> Iterable[Mapping[str, Any]]: response_body = self.decoder.decode(response) - if len(self.field_path) == 0: + if len(self._field_path) == 0: extracted = response_body else: - path = [path.eval(self.config) for path in self.field_path] + path = [path.eval(self.config) for path in self._field_path] if "*" in path: extracted = dpath.util.values(response_body, path) else: diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py index 799871cb9ba0a..3e1f42c2d5ac7 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py @@ -60,7 +60,7 @@ def select_records( :param next_page_token: The paginator token :return: List of Records selected from the response """ - all_data = self.extractor.extract_records(response) + all_data: Iterable[Mapping[str, Any]] = self.extractor.extract_records(response) filtered_data = self._filter(all_data, stream_state, stream_slice, next_page_token) transformed_data = self._transform(filtered_data, stream_state, stream_slice) normalized_data = self._normalize_by_schema(transformed_data, schema=records_schema) @@ -73,8 +73,9 @@ def _normalize_by_schema( if schema: # record has type Mapping[str, Any], but dict[str, Any] expected for record in records: - self.schema_normalization.transform(record, schema) - yield record + normalized_record = dict(record) + self.schema_normalization.transform(normalized_record, schema) + yield normalized_record else: yield from records @@ -84,7 +85,7 @@ def _filter( stream_state: StreamState, stream_slice: Optional[StreamSlice], next_page_token: Optional[Mapping[str, Any]], - ) -> List[Mapping[str, Any]]: + ) -> Iterable[Mapping[str, Any]]: if self.record_filter: yield from self.record_filter.filter_records( records, stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token @@ -97,7 +98,7 @@ def _transform( records: Iterable[Mapping[str, Any]], stream_state: StreamState, stream_slice: Optional[StreamSlice] = None, - ) -> None: + ) -> Iterable[Mapping[str, Any]]: for record in records: for transformation in self.transformations: # record has type Mapping[str, Any], but Record expected diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py index f6bd1df2366e6..497d6257ce544 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py @@ -166,9 +166,9 @@ def _get_request_options(self, option_type: RequestOptionType) -> MutableMapping and isinstance(self.page_token_option, RequestOption) and self.page_token_option.inject_into == option_type ): - options[self.page_token_option.field_name.eval(config=self.config)] = self._token # type: ignore # field_name is known to be an interpolated string + options[self.page_token_option.field_name.eval(config=self.config)] = self._token # type: ignore # field_name is known to be an interpolated string if self.page_size_option and self.pagination_strategy.get_page_size() and self.page_size_option.inject_into == option_type: - options[self.page_size_option.field_name.eval(config=self.config)] = self.pagination_strategy.get_page_size() # type: ignore # field_name is known to be an interpolated string + options[self.page_size_option.field_name.eval(config=self.config)] = self.pagination_strategy.get_page_size() # type: ignore # field_name is known to be an interpolated string return options diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index 6b1c629f3bec7..5cbd67b7a48c5 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Optional, Union, Dict +from typing import Any, Dict, List, Mapping, Optional, Union import requests from airbyte_cdk.sources.declarative.decoders.decoder import Decoder @@ -11,9 +11,7 @@ from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString from airbyte_cdk.sources.declarative.requesters.paginators.strategies.pagination_strategy import PaginationStrategy -from airbyte_cdk.sources.declarative.types import Config - -from airbyte_cdk.sources.declarative.types import Record +from airbyte_cdk.sources.declarative.types import Config, Record @dataclass diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py b/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py index 2379b777ec857..ed7ccd8c70540 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py @@ -228,7 +228,7 @@ def test_full_config_stream(): assert isinstance(stream.retriever.record_selector.extractor, DpathExtractor) assert isinstance(stream.retriever.record_selector.extractor.decoder, JsonDecoder) - assert [fp.eval(input_config) for fp in stream.retriever.record_selector.extractor.field_path] == ["lists"] + assert [fp.eval(input_config) for fp in stream.retriever.record_selector.extractor._field_path] == ["lists"] assert isinstance(stream.retriever.record_selector.record_filter, RecordFilter) assert stream.retriever.record_selector.record_filter._filter_interpolator.condition == "{{ record['id'] > stream_state['id'] }}" @@ -759,7 +759,7 @@ def test_create_record_selector(test_name, record_selector, expected_runtime_sel assert isinstance(selector, RecordSelector) assert isinstance(selector.extractor, DpathExtractor) - assert [fp.eval(input_config) for fp in selector.extractor.field_path] == [expected_runtime_selector] + assert [fp.eval(input_config) for fp in selector.extractor._field_path] == [expected_runtime_selector] assert isinstance(selector.record_filter, RecordFilter) assert selector.record_filter.condition == "{{ record['id'] > stream_state['id'] }}" @@ -1093,7 +1093,7 @@ def test_config_with_defaults(): assert isinstance(stream.retriever.record_selector, RecordSelector) assert isinstance(stream.retriever.record_selector.extractor, DpathExtractor) - assert [fp.eval(input_config) for fp in stream.retriever.record_selector.extractor.field_path] == ["result"] + assert [fp.eval(input_config) for fp in stream.retriever.record_selector.extractor._field_path] == ["result"] assert isinstance(stream.retriever.paginator, DefaultPaginator) assert stream.retriever.paginator.url_base.string == "https://api.sendgrid.com" From 09e86966aab4bb644f28ec3db53ce3b78e5351c6 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 22 Mar 2024 15:35:26 -0700 Subject: [PATCH 05/27] update the schema --- .../declarative_component_schema.yaml | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml b/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml index d2f372beaca96..4bb1141f57f5d 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml @@ -353,7 +353,8 @@ definitions: interpolation_context: - config - headers - - last_records + - last_page_size + - last_record - response examples: - "{{ headers.link.next.cursor }}" @@ -372,7 +373,7 @@ definitions: interpolation_context: - config - headers - - last_records + - last_record - response examples: - "{{ response.data.has_more is false }}" @@ -2253,20 +2254,20 @@ interpolation: x-ratelimit-limit: "600" x-ratelimit-remaining: "598" x-ratelimit-reset: "39" - - title: last_records - description: List of records extracted from the last response received from the API. - type: list + - title: last_record + description: Last record extracted from the response received from the API. + type: object + examples: + - name: "Test List: 19" + id: 0236d6d2 + contact_count: 20 + _metadata: + self: https://api.sendgrid.com/v3/marketing/lists/0236d6d2 + - title: last_page_size + description: Number of records extracted from the last response received from the API. + type: object examples: - - - name: "Test List: 19" - id: 0236d6d2 - contact_count: 20 - _metadata: - self: https://api.sendgrid.com/v3/marketing/lists/0236d6d2 - - name: List for CI tests, number 30 - id: 041ee031 - contact_count: 0 - _metadata: - self: https://api.sendgrid.com/v3/marketing/lists/041ee031 + - 2 - title: next_page_token description: Object describing the token to fetch the next page of records. The object has a single key "next_page_token". type: object From 30ba4d4c896b0e09158d56c4a7c244d4037a8095 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 22 Mar 2024 16:13:08 -0700 Subject: [PATCH 06/27] Add last_page_size and last_record to pagination context --- .../declarative_component_schema.yaml | 33 ++++++++++--------- .../models/declarative_component_schema.py | 2 +- .../strategies/cursor_pagination_strategy.py | 24 +++++++++----- .../test_model_to_component_factory.py | 6 ++-- 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml b/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml index d2f372beaca96..538df1ca54313 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml @@ -353,11 +353,12 @@ definitions: interpolation_context: - config - headers - - last_records + - last_page_size + - last_record - response examples: - "{{ headers.link.next.cursor }}" - - "{{ last_records[-1]['key'] }}" + - "{{ last_record['key'] }}" - "{{ response['nextPage'] }}" page_size: title: Page Size @@ -372,7 +373,7 @@ definitions: interpolation_context: - config - headers - - last_records + - last_record - response examples: - "{{ response.data.has_more is false }}" @@ -2253,20 +2254,20 @@ interpolation: x-ratelimit-limit: "600" x-ratelimit-remaining: "598" x-ratelimit-reset: "39" - - title: last_records - description: List of records extracted from the last response received from the API. - type: list + - title: last_record + description: Last record extracted from the response received from the API. + type: object + examples: + - name: "Test List: 19" + id: 0236d6d2 + contact_count: 20 + _metadata: + self: https://api.sendgrid.com/v3/marketing/lists/0236d6d2 + - title: last_page_size + description: Number of records extracted from the last response received from the API. + type: object examples: - - - name: "Test List: 19" - id: 0236d6d2 - contact_count: 20 - _metadata: - self: https://api.sendgrid.com/v3/marketing/lists/0236d6d2 - - name: List for CI tests, number 30 - id: 041ee031 - contact_count: 0 - _metadata: - self: https://api.sendgrid.com/v3/marketing/lists/041ee031 + - 2 - title: next_page_token description: Object describing the token to fetch the next page of records. The object has a single key "next_page_token". type: object diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py index 576fd45fefce0..3ca3c6486ff0c 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py @@ -859,7 +859,7 @@ class CursorPagination(BaseModel): description='Value of the cursor defining the next page to fetch.', examples=[ '{{ headers.link.next.cursor }}', - "{{ last_records[-1]['key'] }}", + "{{ last_record['key'] }}", "{{ response['nextPage'] }}", ], title='Cursor Value', diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index bea36fc8e3230..c20348bf1dc71 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Optional, Union +from typing import Any, List, Mapping, Optional, Union, Dict import requests from airbyte_cdk.sources.declarative.decoders.decoder import Decoder @@ -13,6 +13,8 @@ from airbyte_cdk.sources.declarative.requesters.paginators.strategies.pagination_strategy import PaginationStrategy from airbyte_cdk.sources.declarative.types import Config +from airbyte_cdk.sources.declarative.types import Record + @dataclass class CursorPaginationStrategy(PaginationStrategy): @@ -34,32 +36,36 @@ class CursorPaginationStrategy(PaginationStrategy): stop_condition: Optional[Union[InterpolatedBoolean, str]] = None decoder: Decoder = JsonDecoder(parameters={}) - def __post_init__(self, parameters: Mapping[str, Any]): + def __post_init__(self, parameters: Mapping[str, Any]) -> None: if isinstance(self.cursor_value, str): - self.cursor_value = InterpolatedString.create(self.cursor_value, parameters=parameters) + self._cursor_value = InterpolatedString.create(self.cursor_value, parameters=parameters) + else: + self._cursor_value = self.cursor_value if isinstance(self.stop_condition, str): self.stop_condition = InterpolatedBoolean(condition=self.stop_condition, parameters=parameters) + else: + self._stop_condition = self.stop_condition @property def initial_token(self) -> Optional[Any]: return None - def next_page_token(self, response: requests.Response, last_records: List[Mapping[str, Any]]) -> Optional[Any]: + def next_page_token(self, response: requests.Response, last_records: List[Record]) -> Optional[Any]: decoded_response = self.decoder.decode(response) # The default way that link is presented in requests.Response is a string of various links (last, next, etc). This # is not indexable or useful for parsing the cursor, so we replace it with the link dictionary from response.links - headers = response.headers + headers: Dict[str, Any] = dict(response.headers) headers["link"] = response.links - if self.stop_condition: - should_stop = self.stop_condition.eval(self.config, response=decoded_response, headers=headers, last_records=last_records) + if self._stop_condition: + should_stop = self._stop_condition.eval(self.config, response=decoded_response, headers=headers, last_records=last_records, last_record=last_records[-1], last_page_size=len(last_records)) if should_stop: return None - token = self.cursor_value.eval(config=self.config, last_records=last_records, response=decoded_response, headers=headers) + token = self._cursor_value.eval(config=self.config, last_records=last_records, response=decoded_response, headers=headers, last_record=last_records[-1], last_page_size=len(last_records)) return token if token else None - def reset(self): + def reset(self) -> None: # No state to reset pass diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py b/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py index 9c2b536832d43..2379b777ec857 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py @@ -243,8 +243,8 @@ def test_full_config_stream(): assert isinstance(stream.retriever.paginator.pagination_strategy, CursorPaginationStrategy) assert isinstance(stream.retriever.paginator.pagination_strategy.decoder, JsonDecoder) - assert stream.retriever.paginator.pagination_strategy.cursor_value.string == "{{ response._metadata.next }}" - assert stream.retriever.paginator.pagination_strategy.cursor_value.default == "{{ response._metadata.next }}" + assert stream.retriever.paginator.pagination_strategy._cursor_value.string == "{{ response._metadata.next }}" + assert stream.retriever.paginator.pagination_strategy._cursor_value.default == "{{ response._metadata.next }}" assert stream.retriever.paginator.pagination_strategy.page_size == 10 assert isinstance(stream.retriever.requester, HttpRequester) @@ -1128,7 +1128,7 @@ def test_create_default_paginator(): assert isinstance(paginator.pagination_strategy, CursorPaginationStrategy) assert paginator.pagination_strategy.page_size == 50 - assert paginator.pagination_strategy.cursor_value.string == "{{ response._metadata.next }}" + assert paginator.pagination_strategy._cursor_value.string == "{{ response._metadata.next }}" assert isinstance(paginator.page_size_option, RequestOption) assert paginator.page_size_option.inject_into == RequestOptionType.request_parameter From 97449e6587c0087fc6327bd644f70754d5ae9ec6 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Mon, 25 Mar 2024 13:46:54 -0700 Subject: [PATCH 07/27] boundaries check --- .../paginators/strategies/cursor_pagination_strategy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index c20348bf1dc71..d20c0a2e6dd6f 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -58,11 +58,13 @@ def next_page_token(self, response: requests.Response, last_records: List[Record headers: Dict[str, Any] = dict(response.headers) headers["link"] = response.links + last_record = last_records[-1] if last_records else None + if self._stop_condition: - should_stop = self._stop_condition.eval(self.config, response=decoded_response, headers=headers, last_records=last_records, last_record=last_records[-1], last_page_size=len(last_records)) + should_stop = self._stop_condition.eval(self.config, response=decoded_response, headers=headers, last_records=last_records, last_record=last_record, last_page_size=len(last_records)) if should_stop: return None - token = self._cursor_value.eval(config=self.config, last_records=last_records, response=decoded_response, headers=headers, last_record=last_records[-1], last_page_size=len(last_records)) + token = self._cursor_value.eval(config=self.config, last_records=last_records, response=decoded_response, headers=headers, last_record=last_record, last_page_size=len(last_records)) return token if token else None def reset(self) -> None: From a7b57dd7195d6ac4fffe2d3fc7ea1c59c0a5c50e Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Mon, 25 Mar 2024 15:03:26 -0700 Subject: [PATCH 08/27] add a test --- .../strategies/cursor_pagination_strategy.py | 24 ++++++++++++++----- .../test_cursor_pagination_strategy.py | 13 ++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index d20c0a2e6dd6f..af3939a8d4903 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Optional, Union, Dict +from typing import Any, Dict, List, Mapping, Optional, Union import requests from airbyte_cdk.sources.declarative.decoders.decoder import Decoder @@ -11,9 +11,7 @@ from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString from airbyte_cdk.sources.declarative.requesters.paginators.strategies.pagination_strategy import PaginationStrategy -from airbyte_cdk.sources.declarative.types import Config - -from airbyte_cdk.sources.declarative.types import Record +from airbyte_cdk.sources.declarative.types import Config, Record @dataclass @@ -61,10 +59,24 @@ def next_page_token(self, response: requests.Response, last_records: List[Record last_record = last_records[-1] if last_records else None if self._stop_condition: - should_stop = self._stop_condition.eval(self.config, response=decoded_response, headers=headers, last_records=last_records, last_record=last_record, last_page_size=len(last_records)) + should_stop = self._stop_condition.eval( + self.config, + response=decoded_response, + headers=headers, + last_records=last_records, + last_record=last_record, + last_page_size=len(last_records), + ) if should_stop: return None - token = self._cursor_value.eval(config=self.config, last_records=last_records, response=decoded_response, headers=headers, last_record=last_record, last_page_size=len(last_records)) + token = self._cursor_value.eval( + config=self.config, + last_records=last_records, + response=decoded_response, + headers=headers, + last_record=last_record, + last_page_size=len(last_records), + ) return token if token else None def reset(self) -> None: diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py index ce15b586125b5..3eff1e632cfda 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py @@ -62,3 +62,16 @@ def test_cursor_pagination_strategy(test_name, template_string, stop_condition, token = strategy.next_page_token(response, last_records) assert expected_token == token assert page_size == strategy.get_page_size() + +def test_last_record_points_to_the_last_item_in_last_records_array(): + last_records = [{"id": 0, "more_records": True}, {"id": 1, "more_records": True}] + strategy = CursorPaginationStrategy( + page_size=1, + cursor_value="{{ last_record.id }}", + config={}, + parameters={}, + ) + + response = requests.Response() + next_page_token = strategy.next_page_token(response, last_records) + assert next_page_token == 1 From ea63f3616c2e408fedf475da117fa983da36f222 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Mon, 25 Mar 2024 17:17:25 -0700 Subject: [PATCH 09/27] format --- .../requesters/paginators/test_cursor_pagination_strategy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py index 3eff1e632cfda..744c76e289192 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py @@ -63,6 +63,7 @@ def test_cursor_pagination_strategy(test_name, template_string, stop_condition, assert expected_token == token assert page_size == strategy.get_page_size() + def test_last_record_points_to_the_last_item_in_last_records_array(): last_records = [{"id": 0, "more_records": True}, {"id": 1, "more_records": True}] strategy = CursorPaginationStrategy( From 2aaa8d499e63a72f8f147c7521a1db9a58806df0 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Mon, 25 Mar 2024 21:53:09 -0700 Subject: [PATCH 10/27] missing unit test --- .../paginators/test_cursor_pagination_strategy.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py index 744c76e289192..feb2a56d65890 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py @@ -76,3 +76,16 @@ def test_last_record_points_to_the_last_item_in_last_records_array(): response = requests.Response() next_page_token = strategy.next_page_token(response, last_records) assert next_page_token == 1 + +def test_last_record_is_node_if_no_records(): + last_records = [] + strategy = CursorPaginationStrategy( + page_size=1, + cursor_value="{{ last_record.id }}", + config={}, + parameters={}, + ) + + response = requests.Response() + next_page_token = strategy.next_page_token(response, last_records) + assert next_page_token is None From 7b514e0df8f978ca9efcd6dd64b59e4a5e20d40a Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Tue, 26 Mar 2024 09:18:39 -0700 Subject: [PATCH 11/27] missing newline --- .../requesters/paginators/test_cursor_pagination_strategy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py index feb2a56d65890..bb75a0201368f 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py @@ -77,6 +77,7 @@ def test_last_record_points_to_the_last_item_in_last_records_array(): next_page_token = strategy.next_page_token(response, last_records) assert next_page_token == 1 + def test_last_record_is_node_if_no_records(): last_records = [] strategy = CursorPaginationStrategy( From 834fc96733e4a7cdd362ff25c422cc82a0cd2e00 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Tue, 26 Mar 2024 11:24:49 -0700 Subject: [PATCH 12/27] fix imports --- .../sources/declarative/extractors/http_selector.py | 2 +- .../sources/declarative/extractors/record_extractor.py | 3 +-- .../sources/declarative/extractors/record_filter.py | 2 +- .../declarative/requesters/paginators/default_paginator.py | 2 +- .../sources/declarative/requesters/paginators/no_pagination.py | 2 +- .../sources/declarative/requesters/paginators/paginator.py | 2 +- .../paginators/strategies/cursor_pagination_strategy.py | 2 +- .../requesters/paginators/strategies/offset_increment.py | 2 +- .../requesters/paginators/strategies/page_increment.py | 2 +- .../requesters/paginators/strategies/pagination_strategy.py | 2 +- .../requesters/paginators/strategies/stop_condition.py | 2 +- 11 files changed, 11 insertions(+), 12 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py index b867bcb49ceaa..a406b2727a991 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/http_selector.py @@ -4,7 +4,7 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any, Iterable, List, Mapping, Optional +from typing import Any, Iterable, Mapping, Optional import requests from airbyte_cdk.sources.declarative.types import Record, StreamSlice, StreamState diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py index fce9e1c783241..5de6a84a7db7e 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_extractor.py @@ -1,10 +1,9 @@ # # Copyright (c) 2023 Airbyte, Inc., all rights reserved. # - from abc import abstractmethod from dataclasses import dataclass -from typing import Any, Iterable, List, Mapping +from typing import Any, Iterable, Mapping import requests diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py index eb92ec1474ad2..76641eed2a112 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, Iterable, List, Mapping, Optional +from typing import Any, Iterable, Mapping, Optional from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean from airbyte_cdk.sources.declarative.types import Config, StreamSlice, StreamState diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py index 497d6257ce544..05d71c38d42f0 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, MutableMapping, Optional, Union +from typing import Any, Mapping, MutableMapping, Optional, Union import requests from airbyte_cdk.sources.declarative.decoders.decoder import Decoder diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py index 6d615fb76a20b..3d49a0d5bfc9c 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, MutableMapping, Optional, Union +from typing import Any, Mapping, MutableMapping, Optional, Union import requests from airbyte_cdk.sources.declarative.requesters.paginators.paginator import Paginator diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py index 344fae310a17f..cadb2cf68e09e 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Any, List, Mapping, Optional +from typing import Any, Mapping, Optional import requests from airbyte_cdk.sources.declarative.requesters.request_options.request_options_provider import RequestOptionsProvider diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index eec4d5959e4b5..2780796a911e2 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, Dict, List, Mapping, Optional, Union +from typing import Any, Dict, Mapping, Optional, Union import requests from airbyte_cdk.sources.declarative.decoders.decoder import Decoder diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py index 1e1ec1ea293a8..790d9381a3863 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Optional, Union +from typing import Any, Mapping, Optional, Union import requests from airbyte_cdk.sources.declarative.decoders import Decoder, JsonDecoder diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py index 442e26c246aba..99fa3170faa9d 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py @@ -3,7 +3,7 @@ # from dataclasses import InitVar, dataclass -from typing import Any, List, Mapping, Optional, Union +from typing import Any, Mapping, Optional, Union import requests from airbyte_cdk.sources.declarative.interpolation import InterpolatedString diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py index 6bcff1e1e907e..a33fc503c40c1 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py @@ -4,7 +4,7 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any, List, Optional +from typing import Any, Optional import requests from airbyte_cdk.sources.declarative.types import Record diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py index db874d834fd61..f5515e8bbb1c6 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py @@ -3,7 +3,7 @@ # from abc import ABC, abstractmethod -from typing import Any, List, Optional +from typing import Any, Optional import requests from airbyte_cdk.sources.declarative.incremental import Cursor From 341d671f80acbe288f0991d8c9eb8d31bb11e0a2 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Tue, 26 Mar 2024 11:29:40 -0700 Subject: [PATCH 13/27] update unit tests --- .../requesters/paginators/test_cursor_pagination_strategy.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py index 49b6477e7c844..2843782d4eace 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py @@ -74,12 +74,11 @@ def test_last_record_points_to_the_last_item_in_last_records_array(): ) response = requests.Response() - next_page_token = strategy.next_page_token(response, last_records) + next_page_token = strategy.next_page_token(response, 2, last_records[-1]) assert next_page_token == 1 def test_last_record_is_node_if_no_records(): - last_records = [] strategy = CursorPaginationStrategy( page_size=1, cursor_value="{{ last_record.id }}", @@ -88,5 +87,5 @@ def test_last_record_is_node_if_no_records(): ) response = requests.Response() - next_page_token = strategy.next_page_token(response, last_records) + next_page_token = strategy.next_page_token(response, 0, None) assert next_page_token is None From 28583e523997504daba9a197090d1550baaf4c89 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Thu, 9 May 2024 14:21:59 -0700 Subject: [PATCH 14/27] fixes --- .../declarative/models/declarative_component_schema.py | 4 ---- .../paginators/strategies/cursor_pagination_strategy.py | 2 +- .../paginators/test_cursor_pagination_strategy.py | 8 ++++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py index 7e545d496da1c..7302ba28e135f 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py @@ -874,15 +874,11 @@ class CursorPagination(BaseModel): cursor_value: str = Field( ..., description='Value of the cursor defining the next page to fetch.', -<<<<<<< HEAD examples=[ '{{ headers.link.next.cursor }}', "{{ last_record['key'] }}", "{{ response['nextPage'] }}", ], -======= - examples=['{{ headers.link.next.cursor }}', "{{ last_record['key'] }}", "{{ response['nextPage'] }}"], ->>>>>>> master title='Cursor Value', ) page_size: Optional[int] = Field(None, description='The number of records to include in each pages.', examples=[100], title='Page Size') diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py index 0b7b640f261b8..fbbcfae61ec6c 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py @@ -40,7 +40,7 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None: else: self._cursor_value = self.cursor_value if isinstance(self.stop_condition, str): - self.stop_condition = InterpolatedBoolean(condition=self.stop_condition, parameters=parameters) + self._stop_condition = InterpolatedBoolean(condition=self.stop_condition, parameters=parameters) else: self._stop_condition = self.stop_condition diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py index 97e83cc689be0..2f469e68bfeb4 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py @@ -81,5 +81,13 @@ def test_last_record_points_to_the_last_item_in_last_records_array(): def test_last_record_is_node_if_no_records(): last_records = [] + strategy = CursorPaginationStrategy( + page_size=1, + cursor_value="{{ last_record.id }}", + config={}, + parameters={}, + ) + + response = requests.Response() next_page_token = strategy.next_page_token(response, 0, None) assert next_page_token is None From f75fb47f033fa3c911af241bf620524b7d4286f6 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Thu, 9 May 2024 16:00:36 -0700 Subject: [PATCH 15/27] Revert "Airbyte CDK: use pytz.utc instead of datetime.utc (#38026)" This reverts commit 5fe60b7fb87f3c99f33d1bdf79675ce43004a60e. --- .../declarative/interpolation/macros.py | 31 +++++++------------ .../declarative/interpolation/test_macros.py | 7 ----- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py index 9f3c634680f9b..c01ff081ccdb4 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py @@ -4,11 +4,9 @@ import builtins import datetime -import typing +import numbers from typing import Union -import isodate -import pytz from dateutil import parser from isodate import parse_duration @@ -17,7 +15,7 @@ """ -def now_utc() -> datetime.datetime: +def now_utc(): """ Current local date and time in UTC timezone @@ -27,7 +25,7 @@ def now_utc() -> datetime.datetime: return datetime.datetime.now(datetime.timezone.utc) -def today_utc() -> datetime.date: +def today_utc(): """ Current date in UTC timezone @@ -37,7 +35,7 @@ def today_utc() -> datetime.date: return datetime.datetime.now(datetime.timezone.utc).date() -def timestamp(dt: Union[float, str]) -> Union[int, float]: +def timestamp(dt: Union[numbers.Number, str]): """ Converts a number or a string to a timestamp @@ -50,21 +48,21 @@ def timestamp(dt: Union[float, str]) -> Union[int, float]: :param dt: datetime to convert to timestamp :return: unix timestamp """ - if isinstance(dt, (int, float)): + if isinstance(dt, numbers.Number): return int(dt) else: - return _str_to_datetime(dt).astimezone(pytz.utc).timestamp() + return _str_to_datetime(dt).astimezone(datetime.timezone.utc).timestamp() def _str_to_datetime(s: str) -> datetime.datetime: parsed_date = parser.isoparse(s) if not parsed_date.tzinfo: # Assume UTC if the input does not contain a timezone - parsed_date = parsed_date.replace(tzinfo=pytz.utc) - return parsed_date.astimezone(pytz.utc) + parsed_date = parsed_date.replace(tzinfo=datetime.timezone.utc) + return parsed_date.astimezone(datetime.timezone.utc) -def max(*args: typing.Any) -> typing.Any: +def max(*args): """ Returns biggest object of an iterable, or two or more arguments. @@ -97,7 +95,7 @@ def day_delta(num_days: int, format: str = "%Y-%m-%dT%H:%M:%S.%f%z") -> str: return (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=num_days)).strftime(format) -def duration(datestring: str) -> Union[datetime.timedelta, isodate.Duration]: +def duration(datestring: str) -> datetime.timedelta: """ Converts ISO8601 duration to datetime.timedelta @@ -113,17 +111,10 @@ def format_datetime(dt: Union[str, datetime.datetime], format: str) -> str: Usage: `"{{ format_datetime(config.start_date, '%Y-%m-%d') }}"` - - CPython Datetime package has known bug with `stfrtime` method: '%s' formatting uses locale timezone - https://github.com/python/cpython/issues/77169 - https://github.com/python/cpython/issues/56959 """ if isinstance(dt, datetime.datetime): return dt.strftime(format) - dt_datetime = _str_to_datetime(dt) - if format == "%s": - return str(int(dt_datetime.timestamp())) - return dt_datetime.strftime(format) + return _str_to_datetime(dt).strftime(format) _macros_list = [now_utc, today_utc, timestamp, max, day_delta, duration, format_datetime] diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py index 1b9bd61cec4f5..bfd1fbc137d02 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py @@ -71,10 +71,3 @@ def test_timestamp(test_name, input_value, expected_output): timestamp_function = macros["timestamp"] actual_output = timestamp_function(input_value) assert actual_output == expected_output - - -def test_utc_datetime_to_local_timestamp_conversion(): - """ - This test ensures correct timezone handling independent of the timezone of the system on which the sync is running. - """ - assert macros["format_datetime"](dt="2020-10-01T00:00:00Z", format="%s") == "1601510400" From 6f89fba6ff58c57d47d713b0edd8c64ff1990822 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Thu, 9 May 2024 17:47:25 -0700 Subject: [PATCH 16/27] bump cdk --- .../connectors/source-iterable/poetry.lock | 429 +++++++++++++++--- .../connectors/source-iterable/pyproject.toml | 2 +- 2 files changed, 357 insertions(+), 74 deletions(-) diff --git a/airbyte-integrations/connectors/source-iterable/poetry.lock b/airbyte-integrations/connectors/source-iterable/poetry.lock index 6411fa9fa5bb0..893d5ae9e7773 100644 --- a/airbyte-integrations/connectors/source-iterable/poetry.lock +++ b/airbyte-integrations/connectors/source-iterable/poetry.lock @@ -2,19 +2,20 @@ [[package]] name = "airbyte-cdk" -version = "0.80.0" +version = "0.88.1" description = "A framework for writing Airbyte Connectors." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "airbyte_cdk-0.80.0-py3-none-any.whl", hash = "sha256:060e92323a73674fa4e9e2e4a1eb312b9b9d072c9bbe5fa28f54ef21cb4974f3"}, - {file = "airbyte_cdk-0.80.0.tar.gz", hash = "sha256:1383512a83917fecca5b24cea4c72aa5c561cf96dd464485fbcefda48fe574c5"}, + {file = "airbyte_cdk-0.88.1-py3-none-any.whl", hash = "sha256:b9b6826255fb20dd85872929ecef4d01bd0808135c9f2078d07491fabcb86be2"}, + {file = "airbyte_cdk-0.88.1.tar.gz", hash = "sha256:a769dc6fa3127050ff0b73334fe2e4b16f05543254667dca96de9d816684ffd8"}, ] [package.dependencies] -airbyte-protocol-models = "0.5.1" +airbyte-protocol-models = ">=0.9.0,<1.0" backoff = "*" cachetools = "*" +cryptography = ">=42.0.5,<43.0.0" Deprecated = ">=1.2,<1.3" dpath = ">=2.0.1,<2.1.0" genson = "1.2.2" @@ -22,8 +23,10 @@ isodate = ">=0.6.1,<0.7.0" Jinja2 = ">=3.1.2,<3.2.0" jsonref = ">=0.2,<0.3" jsonschema = ">=3.2.0,<3.3.0" +langchain_core = "0.1.42" pendulum = "<3.0.0" pydantic = ">=1.10.8,<2.0.0" +pyjwt = ">=2.8.0,<3.0.0" pyrate-limiter = ">=3.1.0,<3.2.0" python-dateutil = "*" PyYAML = ">=6.0.1,<7.0.0" @@ -34,17 +37,17 @@ wcmatch = "8.4" [package.extras] file-based = ["avro (>=1.11.2,<1.12.0)", "fastavro (>=1.8.0,<1.9.0)", "markdown", "pdf2image (==1.16.3)", "pdfminer.six (==20221105)", "pyarrow (>=15.0.0,<15.1.0)", "pytesseract (==0.3.10)", "unstructured.pytesseract (>=0.3.12)", "unstructured[docx,pptx] (==0.10.27)"] sphinx-docs = ["Sphinx (>=4.2,<4.3)", "sphinx-rtd-theme (>=1.0,<1.1)"] -vector-db-based = ["cohere (==4.21)", "langchain (==0.0.271)", "openai[embeddings] (==0.27.9)", "tiktoken (==0.4.0)"] +vector-db-based = ["cohere (==4.21)", "langchain (==0.1.16)", "openai[embeddings] (==0.27.9)", "tiktoken (==0.4.0)"] [[package]] name = "airbyte-protocol-models" -version = "0.5.1" +version = "0.9.0" description = "Declares the Airbyte Protocol." optional = false python-versions = ">=3.8" files = [ - {file = "airbyte_protocol_models-0.5.1-py3-none-any.whl", hash = "sha256:dfe84e130e51ce2ae81a06d5aa36f6c5ce3152b9e36e6f0195fad6c3dab0927e"}, - {file = "airbyte_protocol_models-0.5.1.tar.gz", hash = "sha256:7c8b16c7c1c7956b1996052e40585a3a93b1e44cb509c4e97c1ee4fe507ea086"}, + {file = "airbyte_protocol_models-0.9.0-py3-none-any.whl", hash = "sha256:e972e140b5efd1edad5a338bcae8fdee9fc12545caf2c321e0f61b151c163a9b"}, + {file = "airbyte_protocol_models-0.9.0.tar.gz", hash = "sha256:40b69c33df23fe82d7078e84beb123bd604480e4d73cb277a890fcc92aedc8d2"}, ] [package.dependencies] @@ -148,6 +151,70 @@ files = [ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -258,6 +325,60 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "cryptography" +version = "42.0.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, + {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, + {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, + {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, + {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, + {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, + {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "deprecated" version = "1.2.14" @@ -288,13 +409,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -326,13 +447,13 @@ files = [ [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -362,13 +483,13 @@ six = "*" [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -377,6 +498,31 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + [[package]] name = "jsonref" version = "0.2" @@ -409,6 +555,44 @@ six = ">=1.11.0" format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] +[[package]] +name = "langchain-core" +version = "0.1.42" +description = "Building applications with LLMs through composability" +optional = false +python-versions = "<4.0,>=3.8.1" +files = [ + {file = "langchain_core-0.1.42-py3-none-any.whl", hash = "sha256:c5653ffa08a44f740295c157a24c0def4a753333f6a2c41f76bf431cd00be8b5"}, + {file = "langchain_core-0.1.42.tar.gz", hash = "sha256:40751bf60ea5d8e2b2efe65290db434717ee3834870c002e40e2811f09d814e6"}, +] + +[package.dependencies] +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.1.0,<0.2.0" +packaging = ">=23.2,<24.0" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +extended-testing = ["jinja2 (>=3,<4)"] + +[[package]] +name = "langsmith" +version = "0.1.56" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = "<4.0,>=3.8.1" +files = [ + {file = "langsmith-0.1.56-py3-none-any.whl", hash = "sha256:2f930e054ea8eccd8ff99f0f129ae7d2513973b2e706d5483f44ea9951a1dca0"}, + {file = "langsmith-0.1.56.tar.gz", hash = "sha256:ff645b5bf16e2566740218ed6c048a1f8edbbedb4480a0d305a837ec71303fbf"}, +] + +[package.dependencies] +orjson = ">=3.9.14,<4.0.0" +pydantic = ">=1,<3" +requests = ">=2,<3" + [[package]] name = "markupsafe" version = "2.1.5" @@ -478,15 +662,70 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "orjson" +version = "3.10.3" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"}, + {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"}, + {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"}, + {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"}, + {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"}, + {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"}, + {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"}, + {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"}, + {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"}, + {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"}, + {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"}, + {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"}, + {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"}, + {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"}, + {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"}, + {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"}, + {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"}, + {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"}, + {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"}, + {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"}, + {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"}, + {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"}, + {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"}, + {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"}, + {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"}, + {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"}, + {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"}, + {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"}, + {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"}, + {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"}, + {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"}, + {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"}, + {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"}, + {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"}, + {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"}, + {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"}, + {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"}, + {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"}, + {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"}, + {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"}, + {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"}, + {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"}, + {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"}, + {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"}, + {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"}, + {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"}, +] + [[package]] name = "packaging" -version = "24.0" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -525,28 +764,29 @@ pytzdata = ">=2020.1" [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -564,49 +804,60 @@ files = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydantic" -version = "1.10.14" +version = "1.10.15" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, - {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, - {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, - {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, - {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, - {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, - {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, - {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, - {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, - {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, + {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, + {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, + {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, + {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, + {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, + {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, + {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, + {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, ] [package.dependencies] @@ -616,6 +867,23 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pyrate-limiter" version = "3.1.1" @@ -888,18 +1156,18 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy [[package]] name = "setuptools" -version = "69.2.0" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -913,6 +1181,21 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "tenacity" +version = "8.3.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tenacity-8.3.0-py3-none-any.whl", hash = "sha256:3649f6443dbc0d9b01b9d8020a9c4ec7a1ff5f6f3c6c8a036ef371f573fe9185"}, + {file = "tenacity-8.3.0.tar.gz", hash = "sha256:953d4e6ad24357bceffbc9707bc74349aca9d245f68eb65419cf0c249a1949a2"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + [[package]] name = "toml" version = "0.10.2" @@ -937,13 +1220,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -1073,4 +1356,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9,<3.12" -content-hash = "480cfebc1b43626a8f6d5b2f931da7b88f9c70f6f1d11b1b09bb6e1ae76be699" +content-hash = "b06225eeb7981ecc720d1cdc04fb86e951c6ad9fcebee3de06ad0e74015044d8" diff --git a/airbyte-integrations/connectors/source-iterable/pyproject.toml b/airbyte-integrations/connectors/source-iterable/pyproject.toml index ea41b71b60629..db5b8efd59ac7 100644 --- a/airbyte-integrations/connectors/source-iterable/pyproject.toml +++ b/airbyte-integrations/connectors/source-iterable/pyproject.toml @@ -18,7 +18,7 @@ include = "source_iterable" [tool.poetry.dependencies] python = "^3.9,<3.12" pendulum = "==2.1.2" -airbyte-cdk = "0.80.0" +airbyte-cdk = "0.88.1" requests = "==2.31.0" python-dateutil = "==2.8.2" From 0aa79ec2bd41314002ca04c5c3ac29b49f37aab8 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Thu, 9 May 2024 18:39:18 -0700 Subject: [PATCH 17/27] dont keep records in memory --- .../source_iterable/components.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/airbyte-integrations/connectors/source-iterable/source_iterable/components.py b/airbyte-integrations/connectors/source-iterable/source_iterable/components.py index 323fc1450b347..ad0dc0194c5f3 100644 --- a/airbyte-integrations/connectors/source-iterable/source_iterable/components.py +++ b/airbyte-integrations/connectors/source-iterable/source_iterable/components.py @@ -7,6 +7,7 @@ from io import StringIO import requests +from typing import Iterable from airbyte_cdk.sources.declarative.extractors.dpath_extractor import DpathExtractor from airbyte_cdk.sources.declarative.types import Config, Record, StreamSlice, StreamState @@ -14,28 +15,26 @@ @dataclass class XJsonRecordExtractor(DpathExtractor): def extract_records(self, response: requests.Response) -> list[Record]: - return [json.loads(record) for record in response.iter_lines()] + for record in response.iter_lines(): + yield json.loads(record) @dataclass class ListUsersRecordExtractor(DpathExtractor): - def extract_records(self, response: requests.Response) -> list[Record]: - return [{"email": record.decode()} for record in response.iter_lines()] + def extract_records(self, response: requests.Response) -> Iterable[Record]: + for record in response.iter_lines(): + yield {"email": record.decode()} @dataclass class EventsRecordExtractor(DpathExtractor): common_fields = ("itblInternal", "_type", "createdAt", "email") - def extract_records(self, response: requests.Response) -> list[Record]: + def extract_records(self, response: requests.Response) -> Iterable[Record]: jsonl_records = StringIO(response.text) - records = [] for record in jsonl_records: record_dict = json.loads(record) record_dict_common_fields = {} for field in self.common_fields: record_dict_common_fields[field] = record_dict.pop(field, None) - - records.append({**record_dict_common_fields, "data": record_dict}) - - return records + yield {**record_dict_common_fields, "data": record_dict} From f1d1ed4171f8cd297ccdd4676f9d567cf5c8f37a Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Thu, 9 May 2024 19:09:15 -0700 Subject: [PATCH 18/27] Revert "Revert "Airbyte CDK: use pytz.utc instead of datetime.utc (#38026)"" This reverts commit f75fb47f033fa3c911af241bf620524b7d4286f6. --- .../declarative/interpolation/macros.py | 31 ++++++++++++------- .../declarative/interpolation/test_macros.py | 7 +++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py index c01ff081ccdb4..9f3c634680f9b 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py @@ -4,9 +4,11 @@ import builtins import datetime -import numbers +import typing from typing import Union +import isodate +import pytz from dateutil import parser from isodate import parse_duration @@ -15,7 +17,7 @@ """ -def now_utc(): +def now_utc() -> datetime.datetime: """ Current local date and time in UTC timezone @@ -25,7 +27,7 @@ def now_utc(): return datetime.datetime.now(datetime.timezone.utc) -def today_utc(): +def today_utc() -> datetime.date: """ Current date in UTC timezone @@ -35,7 +37,7 @@ def today_utc(): return datetime.datetime.now(datetime.timezone.utc).date() -def timestamp(dt: Union[numbers.Number, str]): +def timestamp(dt: Union[float, str]) -> Union[int, float]: """ Converts a number or a string to a timestamp @@ -48,21 +50,21 @@ def timestamp(dt: Union[numbers.Number, str]): :param dt: datetime to convert to timestamp :return: unix timestamp """ - if isinstance(dt, numbers.Number): + if isinstance(dt, (int, float)): return int(dt) else: - return _str_to_datetime(dt).astimezone(datetime.timezone.utc).timestamp() + return _str_to_datetime(dt).astimezone(pytz.utc).timestamp() def _str_to_datetime(s: str) -> datetime.datetime: parsed_date = parser.isoparse(s) if not parsed_date.tzinfo: # Assume UTC if the input does not contain a timezone - parsed_date = parsed_date.replace(tzinfo=datetime.timezone.utc) - return parsed_date.astimezone(datetime.timezone.utc) + parsed_date = parsed_date.replace(tzinfo=pytz.utc) + return parsed_date.astimezone(pytz.utc) -def max(*args): +def max(*args: typing.Any) -> typing.Any: """ Returns biggest object of an iterable, or two or more arguments. @@ -95,7 +97,7 @@ def day_delta(num_days: int, format: str = "%Y-%m-%dT%H:%M:%S.%f%z") -> str: return (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=num_days)).strftime(format) -def duration(datestring: str) -> datetime.timedelta: +def duration(datestring: str) -> Union[datetime.timedelta, isodate.Duration]: """ Converts ISO8601 duration to datetime.timedelta @@ -111,10 +113,17 @@ def format_datetime(dt: Union[str, datetime.datetime], format: str) -> str: Usage: `"{{ format_datetime(config.start_date, '%Y-%m-%d') }}"` + + CPython Datetime package has known bug with `stfrtime` method: '%s' formatting uses locale timezone + https://github.com/python/cpython/issues/77169 + https://github.com/python/cpython/issues/56959 """ if isinstance(dt, datetime.datetime): return dt.strftime(format) - return _str_to_datetime(dt).strftime(format) + dt_datetime = _str_to_datetime(dt) + if format == "%s": + return str(int(dt_datetime.timestamp())) + return dt_datetime.strftime(format) _macros_list = [now_utc, today_utc, timestamp, max, day_delta, duration, format_datetime] diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py index bfd1fbc137d02..1b9bd61cec4f5 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py @@ -71,3 +71,10 @@ def test_timestamp(test_name, input_value, expected_output): timestamp_function = macros["timestamp"] actual_output = timestamp_function(input_value) assert actual_output == expected_output + + +def test_utc_datetime_to_local_timestamp_conversion(): + """ + This test ensures correct timezone handling independent of the timezone of the system on which the sync is running. + """ + assert macros["format_datetime"](dt="2020-10-01T00:00:00Z", format="%s") == "1601510400" From d213f20d345d15580e44e0283da7885aecd05b23 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Thu, 9 May 2024 19:09:39 -0700 Subject: [PATCH 19/27] Revert "Revert "Revert "Airbyte CDK: use pytz.utc instead of datetime.utc (#38026)""" This reverts commit f1d1ed4171f8cd297ccdd4676f9d567cf5c8f37a. --- .../declarative/interpolation/macros.py | 31 +++++++------------ .../declarative/interpolation/test_macros.py | 7 ----- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py index 9f3c634680f9b..c01ff081ccdb4 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py @@ -4,11 +4,9 @@ import builtins import datetime -import typing +import numbers from typing import Union -import isodate -import pytz from dateutil import parser from isodate import parse_duration @@ -17,7 +15,7 @@ """ -def now_utc() -> datetime.datetime: +def now_utc(): """ Current local date and time in UTC timezone @@ -27,7 +25,7 @@ def now_utc() -> datetime.datetime: return datetime.datetime.now(datetime.timezone.utc) -def today_utc() -> datetime.date: +def today_utc(): """ Current date in UTC timezone @@ -37,7 +35,7 @@ def today_utc() -> datetime.date: return datetime.datetime.now(datetime.timezone.utc).date() -def timestamp(dt: Union[float, str]) -> Union[int, float]: +def timestamp(dt: Union[numbers.Number, str]): """ Converts a number or a string to a timestamp @@ -50,21 +48,21 @@ def timestamp(dt: Union[float, str]) -> Union[int, float]: :param dt: datetime to convert to timestamp :return: unix timestamp """ - if isinstance(dt, (int, float)): + if isinstance(dt, numbers.Number): return int(dt) else: - return _str_to_datetime(dt).astimezone(pytz.utc).timestamp() + return _str_to_datetime(dt).astimezone(datetime.timezone.utc).timestamp() def _str_to_datetime(s: str) -> datetime.datetime: parsed_date = parser.isoparse(s) if not parsed_date.tzinfo: # Assume UTC if the input does not contain a timezone - parsed_date = parsed_date.replace(tzinfo=pytz.utc) - return parsed_date.astimezone(pytz.utc) + parsed_date = parsed_date.replace(tzinfo=datetime.timezone.utc) + return parsed_date.astimezone(datetime.timezone.utc) -def max(*args: typing.Any) -> typing.Any: +def max(*args): """ Returns biggest object of an iterable, or two or more arguments. @@ -97,7 +95,7 @@ def day_delta(num_days: int, format: str = "%Y-%m-%dT%H:%M:%S.%f%z") -> str: return (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=num_days)).strftime(format) -def duration(datestring: str) -> Union[datetime.timedelta, isodate.Duration]: +def duration(datestring: str) -> datetime.timedelta: """ Converts ISO8601 duration to datetime.timedelta @@ -113,17 +111,10 @@ def format_datetime(dt: Union[str, datetime.datetime], format: str) -> str: Usage: `"{{ format_datetime(config.start_date, '%Y-%m-%d') }}"` - - CPython Datetime package has known bug with `stfrtime` method: '%s' formatting uses locale timezone - https://github.com/python/cpython/issues/77169 - https://github.com/python/cpython/issues/56959 """ if isinstance(dt, datetime.datetime): return dt.strftime(format) - dt_datetime = _str_to_datetime(dt) - if format == "%s": - return str(int(dt_datetime.timestamp())) - return dt_datetime.strftime(format) + return _str_to_datetime(dt).strftime(format) _macros_list = [now_utc, today_utc, timestamp, max, day_delta, duration, format_datetime] diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py index 1b9bd61cec4f5..bfd1fbc137d02 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py @@ -71,10 +71,3 @@ def test_timestamp(test_name, input_value, expected_output): timestamp_function = macros["timestamp"] actual_output = timestamp_function(input_value) assert actual_output == expected_output - - -def test_utc_datetime_to_local_timestamp_conversion(): - """ - This test ensures correct timezone handling independent of the timezone of the system on which the sync is running. - """ - assert macros["format_datetime"](dt="2020-10-01T00:00:00Z", format="%s") == "1601510400" From c67f331210ea5ea83ee5520b86308226e9d94230 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Thu, 9 May 2024 19:53:11 -0700 Subject: [PATCH 20/27] lower to 30 days --- .../connectors/source-iterable/source_iterable/manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml b/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml index ff8f3411d23e8..81301e523531a 100644 --- a/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml +++ b/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml @@ -290,7 +290,7 @@ streams: partition_router: [] primary_key: [] incremental_sync: - step: P90D + step: P30D type: DatetimeBasedCursor cursor_field: profileUpdatedAt end_datetime: From 36e5fa41f8bf642713ee17b5ab9478df84e6c514 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 17 May 2024 16:07:53 -0700 Subject: [PATCH 21/27] bump to cdk 0.90.0 --- .../connectors/source-iterable/poetry.lock | 38 ++++++++++++------- .../connectors/source-iterable/pyproject.toml | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/airbyte-integrations/connectors/source-iterable/poetry.lock b/airbyte-integrations/connectors/source-iterable/poetry.lock index 893d5ae9e7773..c3ce27bac30d7 100644 --- a/airbyte-integrations/connectors/source-iterable/poetry.lock +++ b/airbyte-integrations/connectors/source-iterable/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "airbyte-cdk" -version = "0.88.1" +version = "0.90.0" description = "A framework for writing Airbyte Connectors." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "airbyte_cdk-0.88.1-py3-none-any.whl", hash = "sha256:b9b6826255fb20dd85872929ecef4d01bd0808135c9f2078d07491fabcb86be2"}, - {file = "airbyte_cdk-0.88.1.tar.gz", hash = "sha256:a769dc6fa3127050ff0b73334fe2e4b16f05543254667dca96de9d816684ffd8"}, + {file = "airbyte_cdk-0.90.0-py3-none-any.whl", hash = "sha256:bd0aa5843cdc4901f2e482f0e86695ca4e6db83b65c5017799255dd20535cf56"}, + {file = "airbyte_cdk-0.90.0.tar.gz", hash = "sha256:25cefc010718bada5cce3f87e7ae93068630732c0d34ce5145f8ddf7457d4d3c"}, ] [package.dependencies] @@ -29,6 +29,7 @@ pydantic = ">=1.10.8,<2.0.0" pyjwt = ">=2.8.0,<3.0.0" pyrate-limiter = ">=3.1.0,<3.2.0" python-dateutil = "*" +pytz = "2024.1" PyYAML = ">=6.0.1,<7.0.0" requests = "*" requests_cache = "*" @@ -41,13 +42,13 @@ vector-db-based = ["cohere (==4.21)", "langchain (==0.1.16)", "openai[embeddings [[package]] name = "airbyte-protocol-models" -version = "0.9.0" +version = "0.11.0" description = "Declares the Airbyte Protocol." optional = false python-versions = ">=3.8" files = [ - {file = "airbyte_protocol_models-0.9.0-py3-none-any.whl", hash = "sha256:e972e140b5efd1edad5a338bcae8fdee9fc12545caf2c321e0f61b151c163a9b"}, - {file = "airbyte_protocol_models-0.9.0.tar.gz", hash = "sha256:40b69c33df23fe82d7078e84beb123bd604480e4d73cb277a890fcc92aedc8d2"}, + {file = "airbyte_protocol_models-0.11.0-py3-none-any.whl", hash = "sha256:2157757c1af8c13e471ab6a0304fd2f9a2a6af8cc9173937be1348a9553f7c32"}, + {file = "airbyte_protocol_models-0.11.0.tar.gz", hash = "sha256:1c7e46251b0d5a292b4aa382df24f415ac2a2a2b4719361b3c0f76368a043c23"}, ] [package.dependencies] @@ -579,13 +580,13 @@ extended-testing = ["jinja2 (>=3,<4)"] [[package]] name = "langsmith" -version = "0.1.56" +version = "0.1.59" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langsmith-0.1.56-py3-none-any.whl", hash = "sha256:2f930e054ea8eccd8ff99f0f129ae7d2513973b2e706d5483f44ea9951a1dca0"}, - {file = "langsmith-0.1.56.tar.gz", hash = "sha256:ff645b5bf16e2566740218ed6c048a1f8edbbedb4480a0d305a837ec71303fbf"}, + {file = "langsmith-0.1.59-py3-none-any.whl", hash = "sha256:445e3bc1d3baa1e5340cd979907a19483b9763a2ed37b863a01113d406f69345"}, + {file = "langsmith-0.1.59.tar.gz", hash = "sha256:e748a89f4dd6aa441349143e49e546c03b5dfb43376a25bfef6a5ca792fe1437"}, ] [package.dependencies] @@ -764,13 +765,13 @@ pytzdata = ">=2020.1" [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -995,6 +996,17 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + [[package]] name = "pytzdata" version = "2020.1" @@ -1356,4 +1368,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9,<3.12" -content-hash = "b06225eeb7981ecc720d1cdc04fb86e951c6ad9fcebee3de06ad0e74015044d8" +content-hash = "d3db6c4de172bf22d4901c3f2029d6cf9d22bd2c4cacb4d8a62ed9879f538ad5" diff --git a/airbyte-integrations/connectors/source-iterable/pyproject.toml b/airbyte-integrations/connectors/source-iterable/pyproject.toml index db5b8efd59ac7..326c1eda05cd9 100644 --- a/airbyte-integrations/connectors/source-iterable/pyproject.toml +++ b/airbyte-integrations/connectors/source-iterable/pyproject.toml @@ -18,7 +18,7 @@ include = "source_iterable" [tool.poetry.dependencies] python = "^3.9,<3.12" pendulum = "==2.1.2" -airbyte-cdk = "0.88.1" +airbyte-cdk = "0.90.0" requests = "==2.31.0" python-dateutil = "==2.8.2" From c2353600a3c0e5a59ae753edaee511eedf5f62a6 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 17 May 2024 16:08:29 -0700 Subject: [PATCH 22/27] reset --- .../declarative/interpolation/macros.py | 31 ++++++++++++------- .../declarative/interpolation/test_macros.py | 7 +++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py index 8b8c007a8c5e0..9fb5fe99a5a65 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py @@ -4,9 +4,11 @@ import builtins import datetime -import numbers +import typing from typing import Union +import isodate +import pytz from dateutil import parser from isodate import parse_duration @@ -15,7 +17,7 @@ """ -def now_utc(): +def now_utc() -> datetime.datetime: """ Current local date and time in UTC timezone @@ -25,7 +27,7 @@ def now_utc(): return datetime.datetime.now(datetime.timezone.utc) -def today_utc(): +def today_utc() -> datetime.date: """ Current date in UTC timezone @@ -35,7 +37,7 @@ def today_utc(): return datetime.datetime.now(datetime.timezone.utc).date() -def timestamp(dt: Union[numbers.Number, str]): +def timestamp(dt: Union[float, str]) -> Union[int, float]: """ Converts a number or a string to a timestamp @@ -48,21 +50,21 @@ def timestamp(dt: Union[numbers.Number, str]): :param dt: datetime to convert to timestamp :return: unix timestamp """ - if isinstance(dt, numbers.Number): + if isinstance(dt, (int, float)): return int(dt) else: - return _str_to_datetime(dt).astimezone(datetime.timezone.utc).timestamp() + return _str_to_datetime(dt).astimezone(pytz.utc).timestamp() def _str_to_datetime(s: str) -> datetime.datetime: parsed_date = parser.isoparse(s) if not parsed_date.tzinfo: # Assume UTC if the input does not contain a timezone - parsed_date = parsed_date.replace(tzinfo=datetime.timezone.utc) - return parsed_date.astimezone(datetime.timezone.utc) + parsed_date = parsed_date.replace(tzinfo=pytz.utc) + return parsed_date.astimezone(pytz.utc) -def max(*args): +def max(*args: typing.Any) -> typing.Any: """ Returns biggest object of an iterable, or two or more arguments. @@ -95,7 +97,7 @@ def day_delta(num_days: int, format: str = "%Y-%m-%dT%H:%M:%S.%f%z") -> str: return (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=num_days)).strftime(format) -def duration(datestring: str) -> datetime.timedelta: +def duration(datestring: str) -> Union[datetime.timedelta, isodate.Duration]: """ Converts ISO8601 duration to datetime.timedelta @@ -111,10 +113,17 @@ def format_datetime(dt: Union[str, datetime.datetime], format: str) -> str: Usage: `"{{ format_datetime(config.start_date, '%Y-%m-%d') }}"` + + CPython Datetime package has known bug with `stfrtime` method: '%s' formatting uses locale timezone + https://github.com/python/cpython/issues/77169 + https://github.com/python/cpython/issues/56959 """ if isinstance(dt, datetime.datetime): return dt.strftime(format) - return _str_to_datetime(dt).strftime(format) + dt_datetime = _str_to_datetime(dt) + if format == "%s": + return str(int(dt_datetime.timestamp())) + return dt_datetime.strftime(format) _macros_list = [now_utc, today_utc, timestamp, max, day_delta, duration, format_datetime] diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py index bfd1fbc137d02..1b9bd61cec4f5 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_macros.py @@ -71,3 +71,10 @@ def test_timestamp(test_name, input_value, expected_output): timestamp_function = macros["timestamp"] actual_output = timestamp_function(input_value) assert actual_output == expected_output + + +def test_utc_datetime_to_local_timestamp_conversion(): + """ + This test ensures correct timezone handling independent of the timezone of the system on which the sync is running. + """ + assert macros["format_datetime"](dt="2020-10-01T00:00:00Z", format="%s") == "1601510400" From c43415d380cdacae5540b241be8bfba74b005dcd Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Sat, 18 May 2024 12:21:07 -0700 Subject: [PATCH 23/27] stream response --- .../requesters/error_handlers/http_response_filter.py | 6 +++++- .../sources/declarative/requesters/http_requester.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py index 670e653389441..33682a0199cfc 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py @@ -42,8 +42,10 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None: if isinstance(self.action, str): self.action = ResponseAction[self.action] self.http_codes = self.http_codes or set() - if isinstance(self.predicate, str): + if self.predicate and isinstance(self.predicate, str): self.predicate = InterpolatedBoolean(condition=self.predicate, parameters=parameters) + else: + self.predicate = None self.error_message = InterpolatedString.create(string_or_interpolated=self.error_message, parameters=parameters) def matches(self, response: requests.Response, backoff_time: Optional[float] = None) -> Optional[ResponseStatus]: @@ -91,6 +93,8 @@ def _create_error_message(self, response: requests.Response) -> str: return self.error_message.eval(self.config, response=self._safe_response_json(response), headers=response.headers) # type: ignore # error_message is always cast to an interpolated string def _response_matches_predicate(self, response: requests.Response) -> bool: + if not self.predicate: + return False return bool(self.predicate and self.predicate.eval(None, response=self._safe_response_json(response), headers=response.headers)) # type: ignore # predicate is always cast to an interpolated string def _response_contains_error_message(self, response: requests.Response) -> bool: diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py index 81cd406d482f3..f08298f7fe1b0 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py @@ -531,8 +531,8 @@ def _send( self.logger.debug( "Making outbound API request", extra={"headers": request.headers, "url": request.url, "request_body": request.body} ) - response: requests.Response = self._session.send(request) - self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) + response: requests.Response = self._session.send(request, stream=True) + #self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) if log_formatter: formatter = log_formatter self.message_repository.log_message( From 4a9470e47824557b79e6477c047155804d3d3c9c Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Sat, 18 May 2024 13:46:45 -0700 Subject: [PATCH 24/27] reset to master --- .../sources/declarative/requesters/http_requester.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py index f08298f7fe1b0..81cd406d482f3 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py @@ -531,8 +531,8 @@ def _send( self.logger.debug( "Making outbound API request", extra={"headers": request.headers, "url": request.url, "request_body": request.body} ) - response: requests.Response = self._session.send(request, stream=True) - #self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) + response: requests.Response = self._session.send(request) + self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) if log_formatter: formatter = log_formatter self.message_repository.log_message( From 10caba876e142d49d1634da951a10186cf5155ec Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Wed, 22 May 2024 16:46:55 -0700 Subject: [PATCH 25/27] Revert "reset to master" This reverts commit 4a9470e47824557b79e6477c047155804d3d3c9c. --- .../sources/declarative/requesters/http_requester.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py index 81cd406d482f3..f08298f7fe1b0 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py @@ -531,8 +531,8 @@ def _send( self.logger.debug( "Making outbound API request", extra={"headers": request.headers, "url": request.url, "request_body": request.body} ) - response: requests.Response = self._session.send(request) - self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) + response: requests.Response = self._session.send(request, stream=True) + #self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) if log_formatter: formatter = log_formatter self.message_repository.log_message( From 47e01bf0835e68c6716de1e9c20ca9920b0a3592 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 24 May 2024 12:46:53 -0700 Subject: [PATCH 26/27] wip --- .../declarative_component_schema.yaml | 27 ++- .../sources/declarative/decoders/__init__.py | 4 +- .../declarative/decoders/json_decoder.py | 18 ++ .../declarative/extractors/dpath_extractor.py | 28 +-- .../declarative/extractors/record_selector.py | 3 + .../models/declarative_component_schema.py | 32 ++- .../parsers/model_to_component_factory.py | 37 +++- .../declarative/requesters/http_requester.py | 20 +- .../integration_tests/configured_catalog.json | 171 ---------------- .../connectors/source-iterable/poetry.lock | 189 +++--------------- .../connectors/source-iterable/pyproject.toml | 2 +- .../source_iterable/manifest.yaml | 6 +- 12 files changed, 150 insertions(+), 387 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml b/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml index 9e908839904a2..28166317e2e76 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/declarative_component_schema.yaml @@ -383,7 +383,7 @@ definitions: decoder: title: Decoder description: Component decoding the response so records can be extracted. - "$ref": "#/definitions/JsonDecoder" + "$ref": "#/definitions/Decoder" $parameters: type: object additionalProperties: true @@ -1247,7 +1247,9 @@ definitions: decoder: title: Decoder description: Component decoding the response so records can be extracted. - "$ref": "#/definitions/JsonDecoder" + anyOf: + - "$ref": "#/definitions/JsonDecoder" + - "$ref": "#/definitions/JsonlDecoder" page_size_option: "$ref": "#/definitions/RequestOption" page_token_option: @@ -1284,7 +1286,9 @@ definitions: decoder: title: Decoder description: Component decoding the response so records can be extracted. - "$ref": "#/definitions/JsonDecoder" + anyOf: + - "$ref": "#/definitions/JsonDecoder" + - "$ref": "#/definitions/JsonlDecoder" $parameters: type: object additionalProperties: true @@ -1645,6 +1649,17 @@ definitions: type: type: string enum: [JsonDecoder] + JsonlDecoder: + title: Jsonl Decoder + type: object + required: + - type + properties: + type: + type: string + enum: [JsonlDecoder] + field: + type: string ListPartitionRouter: title: List Partition Router description: A Partition router that specifies a list of attributes where each attribute describes a portion of the complete data set for a stream. During a sync, each value is iterated over and can be used as input to outbound API requests. @@ -2257,6 +2272,12 @@ definitions: anyOf: - "$ref": "#/definitions/DefaultPaginator" - "$ref": "#/definitions/NoPagination" + decoder: + title: Decoder + description: Component decoding the response so records can be extracted. + anyOf: + - "$ref": "#/definitions/JsonDecoder" + - "$ref": "#/definitions/JsonlDecoder" ignore_stream_slicer_parameters_on_paginated_requests: description: If true, the partition router and incremental request options will be ignored when paginating requests. Request options set directly on the requester will not be ignored. type: boolean diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/__init__.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/__init__.py index a7784d7a1cf9c..5ad92512ed029 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/__init__.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/__init__.py @@ -3,6 +3,6 @@ # from airbyte_cdk.sources.declarative.decoders.decoder import Decoder -from airbyte_cdk.sources.declarative.decoders.json_decoder import JsonDecoder +from airbyte_cdk.sources.declarative.decoders.json_decoder import JsonDecoder, JsonlDecoder -__all__ = ["Decoder", "JsonDecoder"] +__all__ = ["Decoder", "JsonDecoder", "JsonlDecoder"] diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/json_decoder.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/json_decoder.py index 8d5ad54fd7d16..ab48f186dee78 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/json_decoder.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/decoders/json_decoder.py @@ -5,6 +5,7 @@ from dataclasses import InitVar, dataclass from typing import Any, List, Mapping, Union +import json import requests from airbyte_cdk.sources.declarative.decoders.decoder import Decoder @@ -22,3 +23,20 @@ def decode(self, response: requests.Response) -> Union[Mapping[str, Any], List[A return response.json() except requests.exceptions.JSONDecodeError: return {} + + +@dataclass +class JsonlDecoder(Decoder): + """ + """ + + parameters: InitVar[Mapping[str, Any]] + + def decode(self, response: requests.Response) -> Union[Mapping[str, Any], List[Any], Any]: + print(f"jsonldecoder") + # j = response.json() + # print(f"j: {j}") + for record in response.iter_lines(): + print(f"record: {record}") + #yield record + yield from [{}] \ No newline at end of file diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py index cfbe271364517..b90b5d2523c74 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py @@ -65,18 +65,18 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None: self._field_path[path_index] = InterpolatedString.create(self.field_path[path_index], parameters=parameters) def extract_records(self, response: requests.Response) -> Iterable[Mapping[str, Any]]: - response_body = self.decoder.decode(response) - if len(self._field_path) == 0: - extracted = response_body - else: - path = [path.eval(self.config) for path in self._field_path] - if "*" in path: - extracted = dpath.util.values(response_body, path) + for body in self.decoder.decode(response): + if len(self._field_path) == 0: + extracted = body else: - extracted = dpath.util.get(response_body, path, default=[]) - if isinstance(extracted, list): - yield from extracted - elif extracted: - yield extracted - else: - yield from [] + path = [path.eval(self.config) for path in self._field_path] + if "*" in path: + extracted = dpath.util.values(body, path) + else: + extracted = dpath.util.get(body, path, default=[]) + if isinstance(extracted, list): + yield from extracted + elif extracted: + yield extracted + else: + yield from [] diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py index 6f9cc40478383..be797dcf3025c 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py @@ -60,10 +60,13 @@ def select_records( :param next_page_token: The paginator token :return: List of Records selected from the response """ + print(f"RecordSelector: select_records: {self.extractor.decoder}") + print(f"recordextractor: {self.extractor.__class__}") all_data: Iterable[Mapping[str, Any]] = self.extractor.extract_records(response) filtered_data = self._filter(all_data, stream_state, stream_slice, next_page_token) transformed_data = self._transform(filtered_data, stream_state, stream_slice) normalized_data = self._normalize_by_schema(transformed_data, schema=records_schema) + print("normalized") for data in normalized_data: yield Record(data, stream_slice) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py index 7302ba28e135f..74558eaa4fdce 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/models/declarative_component_schema.py @@ -497,7 +497,11 @@ class HttpResponseFilter(BaseModel): title='Error Message Substring', ) http_codes: Optional[List[int]] = Field( - None, description='Match the response if its HTTP code is included in this list.', examples=[[420, 429], [500]], title='HTTP Codes' + None, + description='Match the response if its HTTP code is included in this list.', + examples=[[420, 429], [500]], + title='HTTP Codes', + unique_items=True, ) predicate: Optional[str] = Field( None, @@ -536,6 +540,11 @@ class JsonDecoder(BaseModel): type: Literal['JsonDecoder'] +class JsonlDecoder(BaseModel): + type: Literal['JsonlDecoder'] + field: Optional[str] = None + + class MinMaxDatetime(BaseModel): type: Literal['MinMaxDatetime'] datetime: str = Field( @@ -801,6 +810,10 @@ class WaitUntilTimeFromHeader(BaseModel): parameters: Optional[Dict[str, Any]] = Field(None, alias='$parameters') +class Decoder(BaseModel): + __root__: Any + + class AddedFieldDefinition(BaseModel): type: Literal['AddedFieldDefinition'] path: List[str] = Field( @@ -874,11 +887,7 @@ class CursorPagination(BaseModel): cursor_value: str = Field( ..., description='Value of the cursor defining the next page to fetch.', - examples=[ - '{{ headers.link.next.cursor }}', - "{{ last_record['key'] }}", - "{{ response['nextPage'] }}", - ], + examples=['{{ headers.link.next.cursor }}', "{{ last_record['key'] }}", "{{ response['nextPage'] }}"], title='Cursor Value', ) page_size: Optional[int] = Field(None, description='The number of records to include in each pages.', examples=[100], title='Page Size') @@ -888,9 +897,7 @@ class CursorPagination(BaseModel): examples=['{{ response.data.has_more is false }}', "{{ 'next' not in headers['link'] }}"], title='Stop Condition', ) - decoder: Optional[JsonDecoder] = Field( - None, description='Component decoding the response so records can be extracted.', title='Decoder' - ) + decoder: Optional[Decoder] = Field(None, description='Component decoding the response so records can be extracted.', title='Decoder') parameters: Optional[Dict[str, Any]] = Field(None, alias='$parameters') @@ -995,7 +1002,7 @@ class DefaultPaginator(BaseModel): pagination_strategy: Union[CursorPagination, CustomPaginationStrategy, OffsetIncrement, PageIncrement] = Field( ..., description='Strategy defining how records are paginated.', title='Pagination Strategy' ) - decoder: Optional[JsonDecoder] = Field( + decoder: Optional[Union[JsonDecoder, JsonlDecoder]] = Field( None, description='Component decoding the response so records can be extracted.', title='Decoder' ) page_size_option: Optional[RequestOption] = None @@ -1011,7 +1018,7 @@ class DpathExtractor(BaseModel): examples=[['data'], ['data', 'records'], ['data', '{{ parameters.name }}'], ['data', '*', 'record']], title='Field Path', ) - decoder: Optional[JsonDecoder] = Field( + decoder: Optional[Union[JsonDecoder, JsonlDecoder]] = Field( None, description='Component decoding the response so records can be extracted.', title='Decoder' ) parameters: Optional[Dict[str, Any]] = Field(None, alias='$parameters') @@ -1317,6 +1324,9 @@ class SimpleRetriever(BaseModel): paginator: Optional[Union[DefaultPaginator, NoPagination]] = Field( None, description="Paginator component that describes how to navigate through the API's pages." ) + decoder: Optional[Union[JsonDecoder, JsonlDecoder]] = Field( + None, description='Component decoding the response so records can be extracted.', title='Decoder' + ) ignore_stream_slicer_parameters_on_paginated_requests: Optional[bool] = Field( False, description='If true, the partition router and incremental request options will be ignored when paginating requests. Request options set directly on the requester will not be ignored.', diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py index 5940673de5905..e0becd8dbc5f1 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py @@ -25,7 +25,7 @@ from airbyte_cdk.sources.declarative.checks import CheckStream from airbyte_cdk.sources.declarative.datetime import MinMaxDatetime from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream -from airbyte_cdk.sources.declarative.decoders import JsonDecoder +from airbyte_cdk.sources.declarative.decoders import Decoder, JsonDecoder, JsonlDecoder from airbyte_cdk.sources.declarative.extractors import DpathExtractor, RecordFilter, RecordSelector from airbyte_cdk.sources.declarative.extractors.record_selector import SCHEMA_TRANSFORMER_TYPE_MAPPING from airbyte_cdk.sources.declarative.incremental import ( @@ -72,6 +72,7 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import HttpResponseFilter as HttpResponseFilterModel from airbyte_cdk.sources.declarative.models.declarative_component_schema import InlineSchemaLoader as InlineSchemaLoaderModel from airbyte_cdk.sources.declarative.models.declarative_component_schema import JsonDecoder as JsonDecoderModel +from airbyte_cdk.sources.declarative.models.declarative_component_schema import JsonlDecoder as JsonlDecoderModel from airbyte_cdk.sources.declarative.models.declarative_component_schema import JsonFileSchemaLoader as JsonFileSchemaLoaderModel from airbyte_cdk.sources.declarative.models.declarative_component_schema import JwtAuthenticator as JwtAuthenticatorModel from airbyte_cdk.sources.declarative.models.declarative_component_schema import JwtHeaders as JwtHeadersModel @@ -197,6 +198,7 @@ def _init_mappings(self) -> None: HttpResponseFilterModel: self.create_http_response_filter, InlineSchemaLoaderModel: self.create_inline_schema_loader, JsonDecoderModel: self.create_json_decoder, + JsonlDecoderModel: self.create_jsonl_decoder, JsonFileSchemaLoaderModel: self.create_json_file_schema_loader, JwtAuthenticatorModel: self.create_jwt_authenticator, LegacyToPerPartitionStateMigrationModel: self.create_legacy_to_per_partition_state_migration, @@ -239,7 +241,6 @@ def create_component( :return: The declarative component to be used at runtime """ - component_type = component_definition.get("type") if component_definition.get("type") != model_type.__name__: raise ValueError(f"Expected manifest component of type {model_type.__name__}, but received {component_type} instead") @@ -722,6 +723,7 @@ def create_default_paginator( url_base: str, cursor_used_for_stop_condition: Optional[DeclarativeCursor] = None, ) -> Union[DefaultPaginator, PaginatorTestReadDecorator]: + # Fixme need to get the parent decoder decoder = self._create_component_from_model(model=model.decoder, config=config) if model.decoder else JsonDecoder(parameters={}) page_size_option = ( self._create_component_from_model(model=model.page_size_option, config=config) if model.page_size_option else None @@ -748,16 +750,21 @@ def create_default_paginator( return PaginatorTestReadDecorator(paginator, self._limit_pages_fetched_per_slice) return paginator - def create_dpath_extractor(self, model: DpathExtractorModel, config: Config, **kwargs: Any) -> DpathExtractor: - decoder = self._create_component_from_model(model.decoder, config=config) if model.decoder else JsonDecoder(parameters={}) + def create_dpath_extractor(self, model: DpathExtractorModel, decoder: Optional[Decoder], config: Config, **kwargs: Any) -> DpathExtractor: + if model.decoder: + decoder_to_use = self._create_component_from_model(model=model.decoder, config=config) + elif decoder: + decoder_to_use = decoder + else: + decoder_to_use = JsonDecoder(parameters={}) model_field_path: List[Union[InterpolatedString, str]] = [x for x in model.field_path] - return DpathExtractor(decoder=decoder, field_path=model_field_path, config=config, parameters=model.parameters or {}) + return DpathExtractor(decoder=decoder_to_use, field_path=model_field_path, config=config, parameters=model.parameters or {}) @staticmethod def create_exponential_backoff_strategy(model: ExponentialBackoffStrategyModel, config: Config) -> ExponentialBackoffStrategy: return ExponentialBackoffStrategy(factor=model.factor or 5, parameters=model.parameters or {}, config=config) - def create_http_requester(self, model: HttpRequesterModel, config: Config, *, name: str) -> HttpRequester: + def create_http_requester(self, model: HttpRequesterModel, decoder: Optional[Decoder], config: Config, *, name: str) -> HttpRequester: authenticator = ( self._create_component_from_model(model=model.authenticator, config=config, url_base=model.url_base, name=name) if model.authenticator @@ -783,6 +790,11 @@ def create_http_requester(self, model: HttpRequesterModel, config: Config, *, na assert model.use_cache is not None # for mypy + if isinstance(decoder, JsonlDecoder): + stream_response_line_by_line = True + else: + stream_response_line_by_line = False + return HttpRequester( name=name, url_base=model.url_base, @@ -796,6 +808,7 @@ def create_http_requester(self, model: HttpRequesterModel, config: Config, *, na parameters=model.parameters or {}, message_repository=self._message_repository, use_cache=model.use_cache, + stream_response=stream_response_line_by_line, ) @staticmethod @@ -823,6 +836,10 @@ def create_inline_schema_loader(model: InlineSchemaLoaderModel, config: Config, def create_json_decoder(model: JsonDecoderModel, config: Config, **kwargs: Any) -> JsonDecoder: return JsonDecoder(parameters={}) + @staticmethod + def create_jsonl_decoder(model: JsonlDecoderModel, config: Config, **kwargs: Any) -> JsonlDecoder: + return JsonlDecoder(parameters={}) + @staticmethod def create_json_file_schema_loader(model: JsonFileSchemaLoaderModel, config: Config, **kwargs: Any) -> JsonFileSchemaLoader: return JsonFileSchemaLoader(file_path=model.file_path or "", config=config, parameters=model.parameters or {}) @@ -980,12 +997,13 @@ def create_record_selector( self, model: RecordSelectorModel, config: Config, + decoder: Optional[Decoder], *, transformations: List[RecordTransformation], **kwargs: Any, ) -> RecordSelector: assert model.schema_normalization is not None # for mypy - extractor = self._create_component_from_model(model=model.extractor, config=config) + extractor = self._create_component_from_model(model=model.extractor, decoder=decoder, config=config) record_filter = self._create_component_from_model(model.record_filter, config=config) if model.record_filter else None schema_normalization = TypeTransformer(SCHEMA_TRANSFORMER_TYPE_MAPPING[model.schema_normalization]) @@ -1040,8 +1058,9 @@ def create_simple_retriever( stop_condition_on_cursor: bool = False, transformations: List[RecordTransformation], ) -> SimpleRetriever: - requester = self._create_component_from_model(model=model.requester, config=config, name=name) - record_selector = self._create_component_from_model(model=model.record_selector, config=config, transformations=transformations) + decoder = self._create_component_from_model(model=model.decoder, config=config) if model.decoder else None + requester = self._create_component_from_model(model=model.requester, decoder=decoder, config=config, name=name) + record_selector = self._create_component_from_model(model=model.record_selector, config=config, decoder=decoder, transformations=transformations) url_base = model.requester.url_base if hasattr(model.requester, "url_base") else requester.get_url_base() stream_slicer = stream_slicer or SinglePartitionRouter(parameters={}) cursor = stream_slicer if isinstance(stream_slicer, DeclarativeCursor) else None diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py index f08298f7fe1b0..07b11ebf84a3d 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py @@ -54,21 +54,12 @@ class HttpRequester(Requester): """ name: str - url_base: Union[InterpolatedString, str] - path: Union[InterpolatedString, str] config: Config parameters: InitVar[Mapping[str, Any]] - authenticator: Optional[DeclarativeAuthenticator] = None - http_method: Union[str, HttpMethod] = HttpMethod.GET request_options_provider: Optional[InterpolatedRequestOptionsProvider] = None - error_handler: Optional[ErrorHandler] = None - disable_retries: bool = False + error_handler: Optional[PatrickErrorHandler] = None message_repository: MessageRepository = NoopMessageRepository() - use_cache: bool = False - - _DEFAULT_MAX_RETRY = 5 - _DEFAULT_RETRY_FACTOR = 5 - _DEFAULT_MAX_TIME = 60 * 10 + client: PatrickClient def __post_init__(self, parameters: Mapping[str, Any]) -> None: self._url_base = InterpolatedString.create(self.url_base, parameters=parameters) @@ -531,8 +522,11 @@ def _send( self.logger.debug( "Making outbound API request", extra={"headers": request.headers, "url": request.url, "request_body": request.body} ) - response: requests.Response = self._session.send(request, stream=True) - #self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) + response: requests.Response = self._session.send(request, stream=self.stream_response) + if self.stream_response: + self.logger.debug("Receiving response, but not logging it as it is a stream response", extra={"headers": response.headers, "status": response.status_code}) + else: + self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) if log_formatter: formatter = log_formatter self.message_repository.log_message( diff --git a/airbyte-integrations/connectors/source-iterable/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-iterable/integration_tests/configured_catalog.json index e4a8426cc2d24..b4603a2648206 100644 --- a/airbyte-integrations/connectors/source-iterable/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-iterable/integration_tests/configured_catalog.json @@ -1,165 +1,5 @@ { "streams": [ - { - "stream": { - "name": "campaigns", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "campaigns_metrics", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "channels", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "email_bounce", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "email_click", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "email_complaint", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "email_open", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "email_send", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "email_send_skip", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "email_subscribe", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "email_unsubscribe", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "events", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "lists", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "list_users", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "message_types", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "metadata", - "json_schema": {}, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, { "stream": { "name": "users", @@ -170,17 +10,6 @@ }, "sync_mode": "incremental", "destination_sync_mode": "append" - }, - { - "stream": { - "name": "templates", - "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["createdAt"] - }, - "sync_mode": "incremental", - "destination_sync_mode": "append" } ] } diff --git a/airbyte-integrations/connectors/source-iterable/poetry.lock b/airbyte-integrations/connectors/source-iterable/poetry.lock index c3ce27bac30d7..6367d34c026bd 100644 --- a/airbyte-integrations/connectors/source-iterable/poetry.lock +++ b/airbyte-integrations/connectors/source-iterable/poetry.lock @@ -5,32 +5,29 @@ name = "airbyte-cdk" version = "0.90.0" description = "A framework for writing Airbyte Connectors." optional = false -python-versions = "<4.0,>=3.9" -files = [ - {file = "airbyte_cdk-0.90.0-py3-none-any.whl", hash = "sha256:bd0aa5843cdc4901f2e482f0e86695ca4e6db83b65c5017799255dd20535cf56"}, - {file = "airbyte_cdk-0.90.0.tar.gz", hash = "sha256:25cefc010718bada5cce3f87e7ae93068630732c0d34ce5145f8ddf7457d4d3c"}, -] +python-versions = "^3.9" +files = [] +develop = true [package.dependencies] -airbyte-protocol-models = ">=0.9.0,<1.0" +airbyte-protocol-models = ">=0.9.0, <1.0" backoff = "*" cachetools = "*" -cryptography = ">=42.0.5,<43.0.0" -Deprecated = ">=1.2,<1.3" -dpath = ">=2.0.1,<2.1.0" +cryptography = "^42.0.5" +Deprecated = "~1.2" +dpath = "~2.0.1" genson = "1.2.2" -isodate = ">=0.6.1,<0.7.0" -Jinja2 = ">=3.1.2,<3.2.0" -jsonref = ">=0.2,<0.3" -jsonschema = ">=3.2.0,<3.3.0" -langchain_core = "0.1.42" +isodate = "~0.6.1" +Jinja2 = "~3.1.2" +jsonref = "~0.2" +jsonschema = "~3.2.0" pendulum = "<3.0.0" -pydantic = ">=1.10.8,<2.0.0" -pyjwt = ">=2.8.0,<3.0.0" -pyrate-limiter = ">=3.1.0,<3.2.0" +pydantic = "^1.10.8" +pyjwt = "^2.8.0" +pyrate-limiter = "~3.1.0" python-dateutil = "*" pytz = "2024.1" -PyYAML = ">=6.0.1,<7.0.0" +PyYAML = "^6.0.1" requests = "*" requests_cache = "*" wcmatch = "8.4" @@ -40,6 +37,10 @@ file-based = ["avro (>=1.11.2,<1.12.0)", "fastavro (>=1.8.0,<1.9.0)", "markdown" sphinx-docs = ["Sphinx (>=4.2,<4.3)", "sphinx-rtd-theme (>=1.0,<1.1)"] vector-db-based = ["cohere (==4.21)", "langchain (==0.1.16)", "openai[embeddings] (==0.27.9)", "tiktoken (==0.4.0)"] +[package.source] +type = "directory" +url = "../../../airbyte-cdk/python" + [[package]] name = "airbyte-protocol-models" version = "0.11.0" @@ -499,31 +500,6 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "2.4" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, - {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, -] - [[package]] name = "jsonref" version = "0.2" @@ -556,44 +532,6 @@ six = ">=1.11.0" format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] -[[package]] -name = "langchain-core" -version = "0.1.42" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langchain_core-0.1.42-py3-none-any.whl", hash = "sha256:c5653ffa08a44f740295c157a24c0def4a753333f6a2c41f76bf431cd00be8b5"}, - {file = "langchain_core-0.1.42.tar.gz", hash = "sha256:40751bf60ea5d8e2b2efe65290db434717ee3834870c002e40e2811f09d814e6"}, -] - -[package.dependencies] -jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.0,<0.2.0" -packaging = ">=23.2,<24.0" -pydantic = ">=1,<3" -PyYAML = ">=5.3" -tenacity = ">=8.1.0,<9.0.0" - -[package.extras] -extended-testing = ["jinja2 (>=3,<4)"] - -[[package]] -name = "langsmith" -version = "0.1.59" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langsmith-0.1.59-py3-none-any.whl", hash = "sha256:445e3bc1d3baa1e5340cd979907a19483b9763a2ed37b863a01113d406f69345"}, - {file = "langsmith-0.1.59.tar.gz", hash = "sha256:e748a89f4dd6aa441349143e49e546c03b5dfb43376a25bfef6a5ca792fe1437"}, -] - -[package.dependencies] -orjson = ">=3.9.14,<4.0.0" -pydantic = ">=1,<3" -requests = ">=2,<3" - [[package]] name = "markupsafe" version = "2.1.5" @@ -663,70 +601,15 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] -[[package]] -name = "orjson" -version = "3.10.3" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"}, - {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"}, - {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"}, - {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"}, - {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"}, - {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"}, - {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"}, - {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"}, - {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"}, - {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"}, - {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"}, - {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"}, - {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"}, - {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"}, - {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"}, - {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"}, -] - [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -1168,19 +1051,18 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy [[package]] name = "setuptools" -version = "69.5.1" +version = "70.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, + {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, + {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1193,21 +1075,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "tenacity" -version = "8.3.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tenacity-8.3.0-py3-none-any.whl", hash = "sha256:3649f6443dbc0d9b01b9d8020a9c4ec7a1ff5f6f3c6c8a036ef371f573fe9185"}, - {file = "tenacity-8.3.0.tar.gz", hash = "sha256:953d4e6ad24357bceffbc9707bc74349aca9d245f68eb65419cf0c249a1949a2"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - [[package]] name = "toml" version = "0.10.2" @@ -1368,4 +1235,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9,<3.12" -content-hash = "d3db6c4de172bf22d4901c3f2029d6cf9d22bd2c4cacb4d8a62ed9879f538ad5" +content-hash = "b7eb89b7d5870de3a5244c04ef1a7b457132637eac81021aac1ad8d9dec663b0" diff --git a/airbyte-integrations/connectors/source-iterable/pyproject.toml b/airbyte-integrations/connectors/source-iterable/pyproject.toml index 326c1eda05cd9..8e7fab163f8ab 100644 --- a/airbyte-integrations/connectors/source-iterable/pyproject.toml +++ b/airbyte-integrations/connectors/source-iterable/pyproject.toml @@ -18,7 +18,7 @@ include = "source_iterable" [tool.poetry.dependencies] python = "^3.9,<3.12" pendulum = "==2.1.2" -airbyte-cdk = "0.90.0" +airbyte-cdk = {path = "../../../airbyte-cdk/python/", develop = true} requests = "==2.31.0" python-dateutil = "==2.8.2" diff --git a/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml b/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml index 81301e523531a..8600184f20327 100644 --- a/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml +++ b/airbyte-integrations/connectors/source-iterable/source_iterable/manifest.yaml @@ -262,6 +262,8 @@ streams: type: DeclarativeStream retriever: type: SimpleRetriever + decoder: + type: JsonlDecoder paginator: type: NoPagination requester: @@ -284,13 +286,13 @@ streams: record_selector: type: RecordSelector extractor: - class_name: source_iterable.components.XJsonRecordExtractor + type: DpathExtractor field_path: - users partition_router: [] primary_key: [] incremental_sync: - step: P30D + step: P300D type: DatetimeBasedCursor cursor_field: profileUpdatedAt end_datetime: From 4ee47c19b5a08bcc45dcaae0b7dafcecf90507ed Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Sun, 26 May 2024 14:38:02 -0700 Subject: [PATCH 27/27] update --- .../declarative/requesters/http_requester.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py index 07b11ebf84a3d..4cd3a28aefa53 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py @@ -54,12 +54,22 @@ class HttpRequester(Requester): """ name: str + url_base: Union[InterpolatedString, str] + path: Union[InterpolatedString, str] config: Config parameters: InitVar[Mapping[str, Any]] + authenticator: Optional[DeclarativeAuthenticator] = None + http_method: Union[str, HttpMethod] = HttpMethod.GET request_options_provider: Optional[InterpolatedRequestOptionsProvider] = None - error_handler: Optional[PatrickErrorHandler] = None + error_handler: Optional[ErrorHandler] = None + disable_retries: bool = False message_repository: MessageRepository = NoopMessageRepository() - client: PatrickClient + use_cache: bool = False + stream_response: bool = False + + _DEFAULT_MAX_RETRY = 5 + _DEFAULT_RETRY_FACTOR = 5 + _DEFAULT_MAX_TIME = 60 * 10 def __post_init__(self, parameters: Mapping[str, Any]) -> None: self._url_base = InterpolatedString.create(self.url_base, parameters=parameters) @@ -527,6 +537,7 @@ def _send( self.logger.debug("Receiving response, but not logging it as it is a stream response", extra={"headers": response.headers, "status": response.status_code}) else: self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) + self.logger.debug("Receiving response", extra={"headers": response.headers, "status": response.status_code, "body": response.text}) if log_formatter: formatter = log_formatter self.message_repository.log_message(