diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index ee2395a9c285..2938c31fa517 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -944,7 +944,7 @@ - name: Stripe sourceDefinitionId: e094cb9a-26de-4645-8761-65c0c425d1de dockerRepository: airbyte/source-stripe - dockerImageTag: 0.1.35 + dockerImageTag: 0.1.36 documentationUrl: https://docs.airbyte.io/integrations/sources/stripe icon: stripe.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 4c45cc95ad2c..7feb0c732640 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9345,7 +9345,7 @@ type: "string" path_in_connector_config: - "client_secret" -- dockerImage: "airbyte/source-stripe:0.1.35" +- dockerImage: "airbyte/source-stripe:0.1.36" spec: documentationUrl: "https://docs.airbyte.io/integrations/sources/stripe" connectionSpecification: @@ -9389,6 +9389,23 @@ \ is frequently updated after creation. More info here" order: 3 + slice_range: + type: "integer" + title: "Data request time increment in days (Optional)" + default: 365 + minimum: 1 + examples: + - 1 + - 3 + - 10 + - 30 + - 180 + - 360 + description: "The time increment used by the connector when requesting data\ + \ from the Stripe API. The bigger the value is, the less requests will\ + \ be made and faster the sync will be. On the other hand, the more seldom\ + \ the state is persisted." + order: 4 supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] diff --git a/airbyte-integrations/connectors/source-stripe/Dockerfile b/airbyte-integrations/connectors/source-stripe/Dockerfile index 0dcc64e056e4..11d615ac8c77 100644 --- a/airbyte-integrations/connectors/source-stripe/Dockerfile +++ b/airbyte-integrations/connectors/source-stripe/Dockerfile @@ -4,13 +4,13 @@ FROM python:3.9-slim RUN apt-get update && apt-get install -y bash && rm -rf /var/lib/apt/lists/* WORKDIR /airbyte/integration_code -COPY source_stripe ./source_stripe -COPY main.py ./ COPY setup.py ./ RUN pip install . +COPY source_stripe ./source_stripe +COPY main.py ./ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.35 +LABEL io.airbyte.version=0.1.36 LABEL io.airbyte.name=airbyte/source-stripe diff --git a/airbyte-integrations/connectors/source-stripe/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-stripe/integration_tests/abnormal_state.json index 168cd531425f..aac5fd711ab0 100644 --- a/airbyte-integrations/connectors/source-stripe/integration_tests/abnormal_state.json +++ b/airbyte-integrations/connectors/source-stripe/integration_tests/abnormal_state.json @@ -1,20 +1,20 @@ { - "charges": { "created": 161703040300 }, - "coupons": { "created": 161703040300 }, - "events": { "created": 161749384700 }, - "customers": { "created": 160083796900 }, - "plans": { "created": 159484835000 }, - "invoices": { "created": 161749017500 }, - "invoice_items": { "date": 159494698100 }, - "transfers": { "created": 161099582400 }, - "subscriptions": { "created": 159968687300 }, - "balance_transactions": { "created": 161706755600 }, - "payouts": { "created": 161706755600 }, - "disputes": { "created": 161099630500 }, - "products": { "created": 158551134100 }, - "refunds": { "created": 161959562900 }, - "payment_intents": { "created": 161959562900 }, - "promotion_codes": { "created": 163534157100 }, + "charges": { "created": 10000000000 }, + "coupons": { "created": 10000000000 }, + "events": { "created": 10000000000 }, + "customers": { "created": 10000000000 }, + "plans": { "created": 10000000000 }, + "invoices": { "created": 10000000000 }, + "invoice_items": { "date": 10000000000 }, + "transfers": { "created": 10000000000 }, + "subscriptions": { "created": 10000000000 }, + "balance_transactions": { "created": 10000000000 }, + "payouts": { "created": 10000000000 }, + "disputes": { "created": 10000000000 }, + "products": { "created": 10000000000 }, + "refunds": { "created": 10000000000 }, + "payment_intents": { "created": 10000000000 }, + "promotion_codes": { "created": 10000000000 }, "checkout_sessions": { "expires_at": 10000000000 }, "checkout_sessions_line_items": { "checkout_session_expires_at": 10000000000 } } diff --git a/airbyte-integrations/connectors/source-stripe/integration_tests/test_dummy.py b/airbyte-integrations/connectors/source-stripe/integration_tests/test_dummy.py deleted file mode 100644 index f1f977513d63..000000000000 --- a/airbyte-integrations/connectors/source-stripe/integration_tests/test_dummy.py +++ /dev/null @@ -1,10 +0,0 @@ -# -# Copyright (c) 2022 Airbyte, Inc., all rights reserved. -# - - -def test_dummy(): - """ - Dummy test to prevent gradle from failing test for this connector - """ - assert True diff --git a/airbyte-integrations/connectors/source-stripe/setup.py b/airbyte-integrations/connectors/source-stripe/setup.py index 0b8de104dc9c..492dfc4e5c92 100644 --- a/airbyte-integrations/connectors/source-stripe/setup.py +++ b/airbyte-integrations/connectors/source-stripe/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "stripe==2.56.0", "pendulum==1.2.0"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "stripe==2.56.0", "pendulum==2.1.2"] TEST_REQUIREMENTS = [ "pytest~=6.1", diff --git a/airbyte-integrations/connectors/source-stripe/source_stripe/source.py b/airbyte-integrations/connectors/source-stripe/source_stripe/source.py index a3a8835627a4..1d5dd91396b8 100644 --- a/airbyte-integrations/connectors/source-stripe/source_stripe/source.py +++ b/airbyte-integrations/connectors/source-stripe/source_stripe/source.py @@ -51,7 +51,12 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> def streams(self, config: Mapping[str, Any]) -> List[Stream]: authenticator = TokenAuthenticator(config["client_secret"]) start_date = pendulum.parse(config["start_date"]).int_timestamp - args = {"authenticator": authenticator, "account_id": config["account_id"], "start_date": start_date} + args = { + "authenticator": authenticator, + "account_id": config["account_id"], + "start_date": start_date, + "slice_range": config.get("slice_range"), + } incremental_args = {**args, "lookback_window_days": config.get("lookback_window_days")} return [ BalanceTransactions(**incremental_args), diff --git a/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml b/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml index 20c01267898d..1baa105033d2 100644 --- a/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml +++ b/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml @@ -44,3 +44,14 @@ connectionSpecification: after creation. More info here order: 3 + slice_range: + type: integer + title: Data request time increment in days (Optional) + default: 365 + minimum: 1 + examples: [1, 3, 10, 30, 180, 360] + description: >- + The time increment used by the connector when requesting data from the Stripe API. The bigger the value is, + the less requests will be made and faster the sync will be. On the other hand, the more seldom + the state is persisted. + order: 4 diff --git a/airbyte-integrations/connectors/source-stripe/source_stripe/streams.py b/airbyte-integrations/connectors/source-stripe/source_stripe/streams.py index 5fc5e1efd2c3..a6023fb37dac 100644 --- a/airbyte-integrations/connectors/source-stripe/source_stripe/streams.py +++ b/airbyte-integrations/connectors/source-stripe/source_stripe/streams.py @@ -5,7 +5,7 @@ import math from abc import ABC, abstractmethod from itertools import chain -from typing import Any, Iterable, Mapping, MutableMapping, Optional +from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Tuple import pendulum import requests @@ -16,11 +16,13 @@ class StripeStream(HttpStream, ABC): url_base = "https://api.stripe.com/v1/" primary_key = "id" + DEFAULT_SLICE_RANGE = 365 - def __init__(self, start_date: int, account_id: str, **kwargs): + def __init__(self, start_date: int, account_id: str, slice_range: int = DEFAULT_SLICE_RANGE, **kwargs): super().__init__(**kwargs) self.account_id = account_id self.start_date = start_date + self.slice_range = slice_range or self.DEFAULT_SLICE_RANGE def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: decoded_response = response.json() @@ -37,6 +39,9 @@ def request_params( # Stripe default pagination is 10, max is 100 params = {"limit": 100} + for key in ("created[gte]", "created[lte]"): + if key in stream_slice: + params[key] = stream_slice[key] # Handle pagination by inserting the next page's token in the request parameters if next_page_token: @@ -54,6 +59,28 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp response_json = response.json() yield from response_json.get("data", []) # Stripe puts records in a container array "data" + def chunk_dates(self, start_date_ts: int) -> Iterable[Tuple[int, int]]: + now = pendulum.now().int_timestamp + step = int(pendulum.duration(days=self.slice_range).total_seconds()) + after_ts = start_date_ts + while after_ts < now: + before_ts = min(now, after_ts + step) + yield after_ts, before_ts + after_ts = before_ts + 1 + + def stream_slices( + self, *, sync_mode: SyncMode, cursor_field: List[str] = None, stream_state: Mapping[str, Any] = None + ) -> Iterable[Optional[Mapping[str, Any]]]: + for start, end in self.chunk_dates(self.start_date): + yield {"created[gte]": start, "created[lte]": end} + + +class SingleEmptySliceMixin(object): + def stream_slices( + self, *, sync_mode: SyncMode, cursor_field: List[str] = None, stream_state: Mapping[str, Any] = None + ) -> Iterable[Optional[Mapping[str, Any]]]: + return [{}] + class IncrementalStripeStream(StripeStream, ABC): # Stripe returns most recently created objects first, so we don't want to persist state until the entire stream has been read @@ -79,14 +106,27 @@ def get_updated_state(self, current_stream_state: MutableMapping[str, Any], late """ return {self.cursor_field: max(latest_record.get(self.cursor_field), current_stream_state.get(self.cursor_field, 0))} - def request_params(self, stream_state: Mapping[str, Any] = None, **kwargs): - stream_state = stream_state or {} - params = super().request_params(stream_state=stream_state, **kwargs) - - start_timestamp = self.get_start_timestamp(stream_state) - if start_timestamp: - params["created[gte]"] = start_timestamp - return params + def read_records( + self, + sync_mode: SyncMode, + cursor_field: List[str] = None, + stream_slice: Mapping[str, Any] = None, + stream_state: Mapping[str, Any] = None, + ) -> Iterable[Mapping[str, Any]]: + if stream_slice is None: + return [] + yield from super().read_records(sync_mode, cursor_field, stream_slice, stream_state) + + def stream_slices( + self, *, sync_mode: SyncMode, cursor_field: List[str] = None, stream_state: Mapping[str, Any] = None + ) -> Iterable[Optional[Mapping[str, Any]]]: + start_ts = self.get_start_timestamp(stream_state) + if start_ts >= pendulum.now().int_timestamp: + # if the state is in the future - this will produce a state message but not make an API request + yield None + else: + for start, end in self.chunk_dates(start_ts): + yield {"created[gte]": start, "created[lte]": end} def get_start_timestamp(self, stream_state) -> int: start_point = self.start_date @@ -134,7 +174,7 @@ def path(self, **kwargs) -> str: return "charges" -class CustomerBalanceTransactions(StripeStream): +class CustomerBalanceTransactions(SingleEmptySliceMixin, StripeStream): """ API docs: https://stripe.com/docs/api/customer_balance_transactions/list """ @@ -147,8 +187,10 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs): def read_records(self, stream_slice: Optional[Mapping[str, Any]] = None, **kwargs) -> Iterable[Mapping[str, Any]]: customers_stream = Customers(authenticator=self.authenticator, account_id=self.account_id, start_date=self.start_date) - for customer in customers_stream.read_records(sync_mode=SyncMode.full_refresh): - yield from super().read_records(stream_slice={"customer_id": customer["id"]}, **kwargs) + slices = customers_stream.stream_slices(sync_mode=SyncMode.full_refresh) + for _slice in slices: + for customer in customers_stream.read_records(sync_mode=SyncMode.full_refresh, stream_slice=_slice): + yield from super().read_records(stream_slice={"customer_id": customer["id"]}, **kwargs) class Coupons(IncrementalStripeStream): @@ -184,7 +226,7 @@ def path(self, **kwargs): return "events" -class StripeSubStream(StripeStream, ABC): +class StripeSubStream(SingleEmptySliceMixin, StripeStream, ABC): """ Research shows that records related to SubStream can be extracted from Parent streams which already contain 1st page of needed items. Thus, it significantly decreases a number of requests needed to get @@ -261,31 +303,32 @@ def request_params(self, stream_slice: Mapping[str, Any] = None, **kwargs): return params def read_records(self, sync_mode: SyncMode, stream_slice: Optional[Mapping[str, Any]] = None, **kwargs) -> Iterable[Mapping[str, Any]]: - parent_stream = self.parent(authenticator=self.authenticator, account_id=self.account_id, start_date=self.start_date) - for record in parent_stream.read_records(sync_mode=SyncMode.full_refresh): + slices = parent_stream.stream_slices(sync_mode=SyncMode.full_refresh) + for _slice in slices: + for record in parent_stream.read_records(sync_mode=SyncMode.full_refresh, stream_slice=_slice): - items_obj = record.get(self.sub_items_attr, {}) - if not items_obj: - continue + items_obj = record.get(self.sub_items_attr, {}) + if not items_obj: + continue - items = items_obj.get("data", []) + items = items_obj.get("data", []) - # non-generic filter, mainly for BankAccounts stream only - if self.filter: - items = [i for i in items if i.get(self.filter["attr"]) == self.filter["value"]] + # non-generic filter, mainly for BankAccounts stream only + if self.filter: + items = [i for i in items if i.get(self.filter["attr"]) == self.filter["value"]] - # get next pages - items_next_pages = [] - if items_obj.get("has_more") and items: - stream_slice = {self.parent_id: record["id"], "starting_after": items[-1]["id"]} - items_next_pages = super().read_records(sync_mode=SyncMode.full_refresh, stream_slice=stream_slice, **kwargs) + # get next pages + items_next_pages = [] + if items_obj.get("has_more") and items: + stream_slice = {self.parent_id: record["id"], "starting_after": items[-1]["id"]} + items_next_pages = super().read_records(sync_mode=SyncMode.full_refresh, stream_slice=stream_slice, **kwargs) - for item in chain(items, items_next_pages): - if self.add_parent_id: - # add reference to parent object when item doesn't have it already - item[self.parent_id] = record["id"] - yield item + for item in chain(items, items_next_pages): + if self.add_parent_id: + # add reference to parent object when item doesn't have it already + item[self.parent_id] = record["id"] + yield item class Invoices(IncrementalStripeStream): @@ -447,12 +490,12 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs): return f"customers/{stream_slice[self.parent_id]}/sources" def request_params(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> MutableMapping[str, Any]: - params = super().request_params(**kwargs) + params = super().request_params(stream_slice=stream_slice, **kwargs) params["object"] = "bank_account" return params -class CheckoutSessions(IncrementalStripeStream): +class CheckoutSessions(SingleEmptySliceMixin, IncrementalStripeStream): """ API docs: https://stripe.com/docs/api/checkout/sessions/list """ @@ -472,12 +515,6 @@ def __init__(self, **kwargs): def path(self, **kwargs): return "checkout/sessions" - def request_params(self, stream_state: Mapping[str, Any] = None, **kwargs): - params = super().request_params(stream_state=stream_state, **kwargs) - # remove odd param, not supported by checkout_sessions api - params.pop("created[gte]", None) - return params - def parse_response(self, response: requests.Response, stream_state: Mapping[str, Any] = None, **kwargs) -> Iterable[Mapping]: since_date = self.get_start_timestamp(stream_state) for item in super().parse_response(response, **kwargs): @@ -487,7 +524,7 @@ def parse_response(self, response: requests.Response, stream_state: Mapping[str, yield item -class CheckoutSessionsLineItems(IncrementalStripeStream): +class CheckoutSessionsLineItems(SingleEmptySliceMixin, IncrementalStripeStream): """ API docs: https://stripe.com/docs/api/checkout/sessions/line_items """ @@ -516,7 +553,9 @@ def read_records( if stream_state: checkout_session_state = {"expires_at": stream_state["checkout_session_expires_at"]} - for checkout_session in checkout_session_stream.read_records(sync_mode=SyncMode.full_refresh, stream_state=checkout_session_state): + for checkout_session in checkout_session_stream.read_records( + sync_mode=SyncMode.full_refresh, stream_state=checkout_session_state, stream_slice={} + ): stream_slice = { "checkout_session_id": checkout_session["id"], "expires_at": checkout_session["expires_at"], @@ -525,10 +564,6 @@ def read_records( def request_params(self, stream_slice: Mapping[str, Any] = None, **kwargs): params = super().request_params(stream_slice=stream_slice, **kwargs) - - # remove odd param, not supported by checkout_sessions api - params.pop("created[gte]", None) - params["expand[]"] = ["data.discounts", "data.taxes"] return params @@ -545,7 +580,7 @@ def parse_response(self, response: requests.Response, stream_slice: Mapping[str, response_json = response.json() data = response_json.get("data", []) if data and stream_slice: - print(f"stream_slice: {stream_slice}") + self.logger.info(f"stream_slice: {stream_slice}") cs_id = stream_slice.get("checkout_session_id", None) cs_expires_at = stream_slice.get("expires_at", None) for e in data: @@ -565,7 +600,7 @@ def path(self, **kwargs): return "promotion_codes" -class ExternalAccount(StripeStream, ABC): +class ExternalAccount(SingleEmptySliceMixin, StripeStream, ABC): """ Bank Accounts and Cards are separate streams because they have different schemas """ diff --git a/airbyte-integrations/connectors/source-stripe/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-stripe/unit_tests/test_streams.py index dfc6bacd71b8..b365b66c4f3f 100644 --- a/airbyte-integrations/connectors/source-stripe/unit_tests/test_streams.py +++ b/airbyte-integrations/connectors/source-stripe/unit_tests/test_streams.py @@ -2,6 +2,7 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +import pendulum import pytest from airbyte_cdk.models import SyncMode from source_stripe.streams import ( @@ -128,8 +129,9 @@ def test_sub_stream(requests_mock): "url": "/v1/invoices/in_1KD6OVIEn5WyEQxn9xuASHsD/lines", }, ) - - stream = InvoiceLineItems(start_date=1641008947, account_id="None") + # make start date a recent date so there's just one slice in a parent stream + start_date = pendulum.today().subtract(days=3).int_timestamp + stream = InvoiceLineItems(start_date=start_date, account_id="None") records = stream.read_records(sync_mode=SyncMode.full_refresh) assert list(records) == [ {"id": "il_1", "invoice_id": "in_1KD6OVIEn5WyEQxn9xuASHsD", "object": "line_item"}, @@ -140,7 +142,7 @@ def test_sub_stream(requests_mock): @pytest.fixture(name="config") def config_fixture(): - config = {"authenticator": "authenticator", "account_id": "", "start_date": 1652783086} + config = {"authenticator": "authenticator", "account_id": "", "start_date": 1596466368} return config @@ -184,16 +186,32 @@ def test_path( @pytest.mark.parametrize( "stream, kwargs, expected", [ - (CustomerBalanceTransactions, {"stream_state": {}}, {"limit": 100}), - (Customers, {}, {"created[gte]": 1652783086, "limit": 100}), + ( + CustomerBalanceTransactions, + {"stream_state": {}, "stream_slice": {"created[gte]": 1596466368, "created[lte]": 1596552768}}, + {"limit": 100, "created[gte]": 1596466368, "created[lte]": 1596552768}, + ), + ( + Customers, + {"stream_state": {}, "stream_slice": {"created[gte]": 1596466368, "created[lte]": 1596552768}}, + {"created[gte]": 1596466368, "created[lte]": 1596552768, "limit": 100}, + ), (InvoiceLineItems, {"stream_state": {}, "stream_slice": {"starting_after": "2030"}}, {"limit": 100, "starting_after": "2030"}), - (Subscriptions, {}, {"created[gte]": 1652783086, "limit": 100, "status": "all"}), + ( + Subscriptions, + {"stream_slice": {"created[gte]": 1596466368, "created[lte]": 1596552768}}, + {"created[gte]": 1596466368, "limit": 100, "status": "all", "created[lte]": 1596552768}, + ), (SubscriptionItems, {"stream_state": {}, "stream_slice": {"subscription_id": "SI"}}, {"limit": 100, "subscription": "SI"}), (BankAccounts, {"stream_state": {}, "stream_slice": {"subscription_id": "SI"}}, {"limit": 100, "object": "bank_account"}), - (CheckoutSessions, {"stream_state": None}, {"limit": 100}), - (CheckoutSessionsLineItems, {"stream_state": None}, {"limit": 100, "expand[]": ["data.discounts", "data.taxes"]}), - (ExternalAccountBankAccounts, {"stream_state": None}, {"limit": 100, "object": "bank_account"}), - (ExternalAccountCards, {"stream_state": None}, {"limit": 100, "object": "card"}), + (CheckoutSessions, {"stream_state": None, "stream_slice": {}}, {"limit": 100}), + ( + CheckoutSessionsLineItems, + {"stream_state": None, "stream_slice": {}}, + {"limit": 100, "expand[]": ["data.discounts", "data.taxes"]}, + ), + (ExternalAccountBankAccounts, {"stream_state": None, "stream_slice": {}}, {"limit": 100, "object": "bank_account"}), + (ExternalAccountCards, {"stream_state": None, "stream_slice": {}}, {"limit": 100, "object": "card"}), ], ) def test_request_params( diff --git a/docs/integrations/sources/stripe.md b/docs/integrations/sources/stripe.md index c10eab38df34..db355a53458e 100644 --- a/docs/integrations/sources/stripe.md +++ b/docs/integrations/sources/stripe.md @@ -74,34 +74,35 @@ The Stripe connector should not run into Stripe API limitations under normal usa ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------| :--- |:-------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0.1.35 | 2022-07-21 | [14924](https://github.com/airbytehq/airbyte/pull/14924) | Remove `additionalProperties` field from spec and schema | -| 0.1.34 | 2022-07-01 | [14357](https://github.com/airbytehq/airbyte/pull/14357) | added external account streams | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:---------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.1.36 | 2022-08-04 | [15292](https://github.com/airbytehq/airbyte/pull/15292) | Implement slicing | +| 0.1.35 | 2022-07-21 | [14924](https://github.com/airbytehq/airbyte/pull/14924) | Remove `additionalProperties` field from spec and schema | +| 0.1.34 | 2022-07-01 | [14357](https://github.com/airbytehq/airbyte/pull/14357) | added external account streams - | | 0.1.33 | 2022-06-06 | [13449](https://github.com/airbytehq/airbyte/pull/13449) | added semi-incremental support for CheckoutSessions and CheckoutSessionsLineItems streams, fixed big in StripeSubStream, added unittests, updated docs | | 0.1.32 | 2022-04-30 | [12500](https://github.com/airbytehq/airbyte/pull/12500) | Improve input configuration copy | | 0.1.31 | 2022-04-20 | [12230](https://github.com/airbytehq/airbyte/pull/12230) | Update connector to use a `spec.yaml` | | 0.1.30 | 2022-03-21 | [11286](https://github.com/airbytehq/airbyte/pull/11286) | Minor corrections to documentation and connector specification | | 0.1.29 | 2022-03-08 | [10359](https://github.com/airbytehq/airbyte/pull/10359) | Improved performance for streams with substreams: invoice_line_items, subscription_items, bank_accounts | | 0.1.28 | 2022-02-08 | [10165](https://github.com/airbytehq/airbyte/pull/10165) | Improve 404 handling for `CheckoutSessionsLineItems` stream | -| 0.1.27 | 2021-12-28 | [9148](https://github.com/airbytehq/airbyte/pull/9148) | Fix `date`, `arrival\_date` fields | -| 0.1.26 | 2021-12-21 | [8992](https://github.com/airbytehq/airbyte/pull/8992) | Fix type `events.request` in schema | -| 0.1.25 | 2021-11-25 | [8250](https://github.com/airbytehq/airbyte/pull/8250) | Rearrange setup fields | -| 0.1.24 | 2021-11-08 | [7729](https://github.com/airbytehq/airbyte/pull/7729) | Include tax data in `checkout_sessions_line_items` stream | -| 0.1.23 | 2021-11-08 | [7729](https://github.com/airbytehq/airbyte/pull/7729) | Correct `payment_intents` schema | -| 0.1.22 | 2021-11-05 | [7345](https://github.com/airbytehq/airbyte/pull/7345) | Add 3 new streams | -| 0.1.21 | 2021-10-07 | [6841](https://github.com/airbytehq/airbyte/pull/6841) | Fix missing `start_date` argument + update json files for SAT | -| 0.1.20 | 2021-09-30 | [6017](https://github.com/airbytehq/airbyte/pull/6017) | Add lookback\_window\_days parameter | -| 0.1.19 | 2021-09-27 | [6466](https://github.com/airbytehq/airbyte/pull/6466) | Use `start_date` parameter in incremental streams | -| 0.1.18 | 2021-09-14 | [6004](https://github.com/airbytehq/airbyte/pull/6004) | Fix coupons and subscriptions stream schemas by removing incorrect timestamp formatting | -| 0.1.17 | 2021-09-14 | [6004](https://github.com/airbytehq/airbyte/pull/6004) | Add `PaymentIntents` stream | -| 0.1.16 | 2021-07-28 | [4980](https://github.com/airbytehq/airbyte/pull/4980) | Remove Updated field from schemas | -| 0.1.15 | 2021-07-21 | [4878](https://github.com/airbytehq/airbyte/pull/4878) | Fix incorrect percent\_off and discounts data filed types | -| 0.1.14 | 2021-07-09 | [4669](https://github.com/airbytehq/airbyte/pull/4669) | Subscriptions Stream now returns all kinds of subscriptions \(including expired and canceled\) | -| 0.1.13 | 2021-07-03 | [4528](https://github.com/airbytehq/airbyte/pull/4528) | Remove regex for acc validation | -| 0.1.12 | 2021-06-08 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for Kubernetes support | -| 0.1.11 | 2021-05-30 | [3744](https://github.com/airbytehq/airbyte/pull/3744) | Fix types in schema | -| 0.1.10 | 2021-05-28 | [3728](https://github.com/airbytehq/airbyte/pull/3728) | Update data types to be number instead of int | -| 0.1.9 | 2021-05-13 | [3367](https://github.com/airbytehq/airbyte/pull/3367) | Add acceptance tests for connected accounts | -| 0.1.8 | 2021-05-11 | [3566](https://github.com/airbytehq/airbyte/pull/3368) | Bump CDK connectors | +| 0.1.27 | 2021-12-28 | [9148](https://github.com/airbytehq/airbyte/pull/9148) | Fix `date`, `arrival\_date` fields | +| 0.1.26 | 2021-12-21 | [8992](https://github.com/airbytehq/airbyte/pull/8992) | Fix type `events.request` in schema | +| 0.1.25 | 2021-11-25 | [8250](https://github.com/airbytehq/airbyte/pull/8250) | Rearrange setup fields | +| 0.1.24 | 2021-11-08 | [7729](https://github.com/airbytehq/airbyte/pull/7729) | Include tax data in `checkout_sessions_line_items` stream | +| 0.1.23 | 2021-11-08 | [7729](https://github.com/airbytehq/airbyte/pull/7729) | Correct `payment_intents` schema | +| 0.1.22 | 2021-11-05 | [7345](https://github.com/airbytehq/airbyte/pull/7345) | Add 3 new streams | +| 0.1.21 | 2021-10-07 | [6841](https://github.com/airbytehq/airbyte/pull/6841) | Fix missing `start_date` argument + update json files for SAT | +| 0.1.20 | 2021-09-30 | [6017](https://github.com/airbytehq/airbyte/pull/6017) | Add lookback\_window\_days parameter | +| 0.1.19 | 2021-09-27 | [6466](https://github.com/airbytehq/airbyte/pull/6466) | Use `start_date` parameter in incremental streams | +| 0.1.18 | 2021-09-14 | [6004](https://github.com/airbytehq/airbyte/pull/6004) | Fix coupons and subscriptions stream schemas by removing incorrect timestamp formatting | +| 0.1.17 | 2021-09-14 | [6004](https://github.com/airbytehq/airbyte/pull/6004) | Add `PaymentIntents` stream | +| 0.1.16 | 2021-07-28 | [4980](https://github.com/airbytehq/airbyte/pull/4980) | Remove Updated field from schemas | +| 0.1.15 | 2021-07-21 | [4878](https://github.com/airbytehq/airbyte/pull/4878) | Fix incorrect percent\_off and discounts data filed types | +| 0.1.14 | 2021-07-09 | [4669](https://github.com/airbytehq/airbyte/pull/4669) | Subscriptions Stream now returns all kinds of subscriptions \(including expired and canceled\) | +| 0.1.13 | 2021-07-03 | [4528](https://github.com/airbytehq/airbyte/pull/4528) | Remove regex for acc validation | +| 0.1.12 | 2021-06-08 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for Kubernetes support | +| 0.1.11 | 2021-05-30 | [3744](https://github.com/airbytehq/airbyte/pull/3744) | Fix types in schema | +| 0.1.10 | 2021-05-28 | [3728](https://github.com/airbytehq/airbyte/pull/3728) | Update data types to be number instead of int | +| 0.1.9 | 2021-05-13 | [3367](https://github.com/airbytehq/airbyte/pull/3367) | Add acceptance tests for connected accounts | +| 0.1.8 | 2021-05-11 | [3566](https://github.com/airbytehq/airbyte/pull/3368) | Bump CDK connectors |