airbyte-source-iterable 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,186 +0,0 @@
1
- {
2
- "streams": [
3
- {
4
- "stream": {
5
- "name": "campaigns",
6
- "json_schema": {},
7
- "supported_sync_modes": ["full_refresh"]
8
- },
9
- "sync_mode": "full_refresh",
10
- "destination_sync_mode": "overwrite"
11
- },
12
- {
13
- "stream": {
14
- "name": "campaigns_metrics",
15
- "json_schema": {},
16
- "supported_sync_modes": ["full_refresh"]
17
- },
18
- "sync_mode": "full_refresh",
19
- "destination_sync_mode": "overwrite"
20
- },
21
- {
22
- "stream": {
23
- "name": "channels",
24
- "json_schema": {},
25
- "supported_sync_modes": ["full_refresh"]
26
- },
27
- "sync_mode": "full_refresh",
28
- "destination_sync_mode": "overwrite"
29
- },
30
- {
31
- "stream": {
32
- "name": "email_bounce",
33
- "json_schema": {},
34
- "supported_sync_modes": ["full_refresh", "incremental"],
35
- "source_defined_cursor": true,
36
- "default_cursor_field": ["createdAt"]
37
- },
38
- "sync_mode": "incremental",
39
- "destination_sync_mode": "append"
40
- },
41
- {
42
- "stream": {
43
- "name": "email_click",
44
- "json_schema": {},
45
- "supported_sync_modes": ["full_refresh", "incremental"],
46
- "source_defined_cursor": true,
47
- "default_cursor_field": ["createdAt"]
48
- },
49
- "sync_mode": "incremental",
50
- "destination_sync_mode": "append"
51
- },
52
- {
53
- "stream": {
54
- "name": "email_complaint",
55
- "json_schema": {},
56
- "supported_sync_modes": ["full_refresh", "incremental"],
57
- "source_defined_cursor": true,
58
- "default_cursor_field": ["createdAt"]
59
- },
60
- "sync_mode": "incremental",
61
- "destination_sync_mode": "append"
62
- },
63
- {
64
- "stream": {
65
- "name": "email_open",
66
- "json_schema": {},
67
- "supported_sync_modes": ["full_refresh", "incremental"],
68
- "source_defined_cursor": true,
69
- "default_cursor_field": ["createdAt"]
70
- },
71
- "sync_mode": "incremental",
72
- "destination_sync_mode": "append"
73
- },
74
- {
75
- "stream": {
76
- "name": "email_send",
77
- "json_schema": {},
78
- "supported_sync_modes": ["full_refresh", "incremental"],
79
- "source_defined_cursor": true,
80
- "default_cursor_field": ["createdAt"]
81
- },
82
- "sync_mode": "incremental",
83
- "destination_sync_mode": "append"
84
- },
85
- {
86
- "stream": {
87
- "name": "email_send_skip",
88
- "json_schema": {},
89
- "supported_sync_modes": ["full_refresh", "incremental"],
90
- "source_defined_cursor": true,
91
- "default_cursor_field": ["createdAt"]
92
- },
93
- "sync_mode": "incremental",
94
- "destination_sync_mode": "append"
95
- },
96
- {
97
- "stream": {
98
- "name": "email_subscribe",
99
- "json_schema": {},
100
- "supported_sync_modes": ["full_refresh", "incremental"],
101
- "source_defined_cursor": true,
102
- "default_cursor_field": ["createdAt"]
103
- },
104
- "sync_mode": "incremental",
105
- "destination_sync_mode": "append"
106
- },
107
- {
108
- "stream": {
109
- "name": "email_unsubscribe",
110
- "json_schema": {},
111
- "supported_sync_modes": ["full_refresh", "incremental"],
112
- "source_defined_cursor": true,
113
- "default_cursor_field": ["createdAt"]
114
- },
115
- "sync_mode": "incremental",
116
- "destination_sync_mode": "append"
117
- },
118
- {
119
- "stream": {
120
- "name": "events",
121
- "json_schema": {},
122
- "supported_sync_modes": ["full_refresh"]
123
- },
124
- "sync_mode": "full_refresh",
125
- "destination_sync_mode": "overwrite"
126
- },
127
- {
128
- "stream": {
129
- "name": "lists",
130
- "json_schema": {},
131
- "supported_sync_modes": ["full_refresh"]
132
- },
133
- "sync_mode": "full_refresh",
134
- "destination_sync_mode": "overwrite"
135
- },
136
- {
137
- "stream": {
138
- "name": "list_users",
139
- "json_schema": {},
140
- "supported_sync_modes": ["full_refresh"]
141
- },
142
- "sync_mode": "full_refresh",
143
- "destination_sync_mode": "overwrite"
144
- },
145
- {
146
- "stream": {
147
- "name": "message_types",
148
- "json_schema": {},
149
- "supported_sync_modes": ["full_refresh"]
150
- },
151
- "sync_mode": "full_refresh",
152
- "destination_sync_mode": "overwrite"
153
- },
154
- {
155
- "stream": {
156
- "name": "metadata",
157
- "json_schema": {},
158
- "supported_sync_modes": ["full_refresh"]
159
- },
160
- "sync_mode": "full_refresh",
161
- "destination_sync_mode": "overwrite"
162
- },
163
- {
164
- "stream": {
165
- "name": "users",
166
- "json_schema": {},
167
- "supported_sync_modes": ["full_refresh", "incremental"],
168
- "source_defined_cursor": true,
169
- "default_cursor_field": ["profileUpdatedAt"]
170
- },
171
- "sync_mode": "incremental",
172
- "destination_sync_mode": "append"
173
- },
174
- {
175
- "stream": {
176
- "name": "templates",
177
- "json_schema": {},
178
- "supported_sync_modes": ["full_refresh", "incremental"],
179
- "source_defined_cursor": true,
180
- "default_cursor_field": ["createdAt"]
181
- },
182
- "sync_mode": "incremental",
183
- "destination_sync_mode": "append"
184
- }
185
- ]
186
- }
@@ -1,291 +0,0 @@
1
- {
2
- "streams": [
3
- {
4
- "stream": {
5
- "name": "push_send",
6
- "json_schema": {},
7
- "supported_sync_modes": ["full_refresh", "incremental"],
8
- "source_defined_cursor": true,
9
- "default_cursor_field": ["createdAt"]
10
- },
11
- "sync_mode": "incremental",
12
- "destination_sync_mode": "append"
13
- },
14
- {
15
- "stream": {
16
- "name": "push_send_skip",
17
- "json_schema": {},
18
- "supported_sync_modes": ["full_refresh", "incremental"],
19
- "source_defined_cursor": true,
20
- "default_cursor_field": ["createdAt"]
21
- },
22
- "sync_mode": "incremental",
23
- "destination_sync_mode": "append"
24
- },
25
- {
26
- "stream": {
27
- "name": "push_open",
28
- "json_schema": {},
29
- "supported_sync_modes": ["full_refresh", "incremental"],
30
- "source_defined_cursor": true,
31
- "default_cursor_field": ["createdAt"]
32
- },
33
- "sync_mode": "incremental",
34
- "destination_sync_mode": "append"
35
- },
36
-
37
- {
38
- "stream": {
39
- "name": "push_uninstall",
40
- "json_schema": {},
41
- "supported_sync_modes": ["full_refresh", "incremental"],
42
- "source_defined_cursor": true,
43
- "default_cursor_field": ["createdAt"]
44
- },
45
- "sync_mode": "incremental",
46
- "destination_sync_mode": "append"
47
- },
48
- {
49
- "stream": {
50
- "name": "push_bounce",
51
- "json_schema": {},
52
- "supported_sync_modes": ["full_refresh", "incremental"],
53
- "source_defined_cursor": true,
54
- "default_cursor_field": ["createdAt"]
55
- },
56
- "sync_mode": "incremental",
57
- "destination_sync_mode": "append"
58
- },
59
- {
60
- "stream": {
61
- "name": "web_push_send",
62
- "json_schema": {},
63
- "supported_sync_modes": ["full_refresh", "incremental"],
64
- "source_defined_cursor": true,
65
- "default_cursor_field": ["createdAt"]
66
- },
67
- "sync_mode": "incremental",
68
- "destination_sync_mode": "append"
69
- },
70
- {
71
- "stream": {
72
- "name": "web_push_click",
73
- "json_schema": {},
74
- "supported_sync_modes": ["full_refresh", "incremental"],
75
- "source_defined_cursor": true,
76
- "default_cursor_field": ["createdAt"]
77
- },
78
- "sync_mode": "incremental",
79
- "destination_sync_mode": "append"
80
- },
81
- {
82
- "stream": {
83
- "name": "web_push_send_skip",
84
- "json_schema": {},
85
- "supported_sync_modes": ["full_refresh", "incremental"],
86
- "source_defined_cursor": true,
87
- "default_cursor_field": ["createdAt"]
88
- },
89
- "sync_mode": "incremental",
90
- "destination_sync_mode": "append"
91
- },
92
- {
93
- "stream": {
94
- "name": "in_app_send",
95
- "json_schema": {},
96
- "supported_sync_modes": ["full_refresh", "incremental"],
97
- "source_defined_cursor": true,
98
- "default_cursor_field": ["createdAt"]
99
- },
100
- "sync_mode": "incremental",
101
- "destination_sync_mode": "append"
102
- },
103
- {
104
- "stream": {
105
- "name": "in_app_open",
106
- "json_schema": {},
107
- "supported_sync_modes": ["full_refresh", "incremental"],
108
- "source_defined_cursor": true,
109
- "default_cursor_field": ["createdAt"]
110
- },
111
- "sync_mode": "incremental",
112
- "destination_sync_mode": "append"
113
- },
114
- {
115
- "stream": {
116
- "name": "in_app_click",
117
- "json_schema": {},
118
- "supported_sync_modes": ["full_refresh", "incremental"],
119
- "source_defined_cursor": true,
120
- "default_cursor_field": ["createdAt"]
121
- },
122
- "sync_mode": "incremental",
123
- "destination_sync_mode": "append"
124
- },
125
- {
126
- "stream": {
127
- "name": "in_app_close",
128
- "json_schema": {},
129
- "supported_sync_modes": ["full_refresh", "incremental"],
130
- "source_defined_cursor": true,
131
- "default_cursor_field": ["createdAt"]
132
- },
133
- "sync_mode": "incremental",
134
- "destination_sync_mode": "append"
135
- },
136
- {
137
- "stream": {
138
- "name": "in_app_delete",
139
- "json_schema": {},
140
- "supported_sync_modes": ["full_refresh", "incremental"],
141
- "source_defined_cursor": true,
142
- "default_cursor_field": ["createdAt"]
143
- },
144
- "sync_mode": "incremental",
145
- "destination_sync_mode": "append"
146
- },
147
- {
148
- "stream": {
149
- "name": "in_app_delivery",
150
- "json_schema": {},
151
- "supported_sync_modes": ["full_refresh", "incremental"],
152
- "source_defined_cursor": true,
153
- "default_cursor_field": ["createdAt"]
154
- },
155
- "sync_mode": "incremental",
156
- "destination_sync_mode": "append"
157
- },
158
- {
159
- "stream": {
160
- "name": "in_app_send_skip",
161
- "json_schema": {},
162
- "supported_sync_modes": ["full_refresh", "incremental"],
163
- "source_defined_cursor": true,
164
- "default_cursor_field": ["createdAt"]
165
- },
166
- "sync_mode": "incremental",
167
- "destination_sync_mode": "append"
168
- },
169
- {
170
- "stream": {
171
- "name": "inbox_session",
172
- "json_schema": {},
173
- "supported_sync_modes": ["full_refresh", "incremental"],
174
- "source_defined_cursor": true,
175
- "default_cursor_field": ["createdAt"]
176
- },
177
- "sync_mode": "incremental",
178
- "destination_sync_mode": "append"
179
- },
180
- {
181
- "stream": {
182
- "name": "inbox_message_impression",
183
- "json_schema": {},
184
- "supported_sync_modes": ["full_refresh", "incremental"],
185
- "source_defined_cursor": true,
186
- "default_cursor_field": ["createdAt"]
187
- },
188
- "sync_mode": "incremental",
189
- "destination_sync_mode": "append"
190
- },
191
- {
192
- "stream": {
193
- "name": "sms_send",
194
- "json_schema": {},
195
- "supported_sync_modes": ["full_refresh", "incremental"],
196
- "source_defined_cursor": true,
197
- "default_cursor_field": ["createdAt"]
198
- },
199
- "sync_mode": "incremental",
200
- "destination_sync_mode": "append"
201
- },
202
- {
203
- "stream": {
204
- "name": "sms_bounce",
205
- "json_schema": {},
206
- "supported_sync_modes": ["full_refresh", "incremental"],
207
- "source_defined_cursor": true,
208
- "default_cursor_field": ["createdAt"]
209
- },
210
- "sync_mode": "incremental",
211
- "destination_sync_mode": "append"
212
- },
213
- {
214
- "stream": {
215
- "name": "sms_click",
216
- "json_schema": {},
217
- "supported_sync_modes": ["full_refresh", "incremental"],
218
- "source_defined_cursor": true,
219
- "default_cursor_field": ["createdAt"]
220
- },
221
- "sync_mode": "incremental",
222
- "destination_sync_mode": "append"
223
- },
224
- {
225
- "stream": {
226
- "name": "sms_received",
227
- "json_schema": {},
228
- "supported_sync_modes": ["full_refresh", "incremental"],
229
- "source_defined_cursor": true,
230
- "default_cursor_field": ["createdAt"]
231
- },
232
- "sync_mode": "incremental",
233
- "destination_sync_mode": "append"
234
- },
235
- {
236
- "stream": {
237
- "name": "sms_send_skip",
238
- "json_schema": {},
239
- "supported_sync_modes": ["full_refresh", "incremental"],
240
- "source_defined_cursor": true,
241
- "default_cursor_field": ["createdAt"]
242
- },
243
- "sync_mode": "incremental",
244
- "destination_sync_mode": "append"
245
- },
246
- {
247
- "stream": {
248
- "name": "sms_usage_info",
249
- "json_schema": {},
250
- "supported_sync_modes": ["full_refresh", "incremental"],
251
- "source_defined_cursor": true,
252
- "default_cursor_field": ["createdAt"]
253
- },
254
- "sync_mode": "incremental",
255
- "destination_sync_mode": "append"
256
- },
257
- {
258
- "stream": {
259
- "name": "purchase",
260
- "json_schema": {},
261
- "supported_sync_modes": ["full_refresh", "incremental"],
262
- "source_defined_cursor": true,
263
- "default_cursor_field": ["createdAt"]
264
- },
265
- "sync_mode": "incremental",
266
- "destination_sync_mode": "append"
267
- },
268
- {
269
- "stream": {
270
- "name": "custom_event",
271
- "json_schema": {},
272
- "supported_sync_modes": ["full_refresh", "incremental"],
273
- "source_defined_cursor": true,
274
- "default_cursor_field": ["createdAt"]
275
- },
276
- "sync_mode": "incremental",
277
- "destination_sync_mode": "append"
278
- },
279
- {
280
- "stream": {
281
- "name": "hosted_unsubscribe_click",
282
- "json_schema": {},
283
- "supported_sync_modes": ["full_refresh", "incremental"],
284
- "source_defined_cursor": true,
285
- "default_cursor_field": ["createdAt"]
286
- },
287
- "sync_mode": "incremental",
288
- "destination_sync_mode": "append"
289
- }
290
- ]
291
- }
@@ -1,4 +0,0 @@
1
- {
2
- "api_key": "test-api-key",
3
- "start_date": "2020-12-12T00:00:00Z"
4
- }
unit_tests/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- #
2
- # Copyright (c) 2021 Airbyte, Inc., all rights reserved.
3
- #
unit_tests/conftest.py DELETED
@@ -1,49 +0,0 @@
1
- #
2
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
- #
4
-
5
- import pytest
6
- from airbyte_cdk.models import AirbyteStream, ConfiguredAirbyteCatalog, ConfiguredAirbyteStream
7
-
8
-
9
- @pytest.fixture(autouse=True)
10
- def disable_cache(mocker):
11
- mocker.patch("source_iterable.streams.ListUsers.use_cache", False)
12
-
13
-
14
- @pytest.fixture
15
- def catalog(request):
16
- return ConfiguredAirbyteCatalog(
17
- streams=[
18
- ConfiguredAirbyteStream(
19
- stream=AirbyteStream(name=request.param, json_schema={}, supported_sync_modes=["full_refresh"]),
20
- sync_mode="full_refresh",
21
- destination_sync_mode="append",
22
- )
23
- ]
24
- )
25
-
26
-
27
- @pytest.fixture(name="config")
28
- def config_fixture():
29
- return {"api_key": 123, "start_date": "2019-10-10T00:00:00"}
30
-
31
-
32
- @pytest.fixture()
33
- def mock_lists_resp(mocker):
34
- mocker.patch("source_iterable.streams.Lists.read_records", return_value=iter([{"id": 1}, {"id": 2}]))
35
-
36
-
37
- @pytest.fixture(name="lists_stream")
38
- def lists_stream():
39
- # local imports
40
- from source_iterable.streams import Lists
41
-
42
- # return the instance of the stream so we could make global tests on it,
43
- # to cover the different `should_retry` logic
44
- return Lists(authenticator=None)
45
-
46
-
47
- @pytest.fixture(autouse=True)
48
- def mock_sleep(mocker):
49
- mocker.patch("time.sleep")
@@ -1,117 +0,0 @@
1
- #
2
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
- #
4
-
5
- import datetime
6
- import json
7
- import urllib.parse
8
- from typing import List
9
- from unittest import mock
10
-
11
- import freezegun
12
- import pendulum
13
- import pytest
14
- import responses
15
- from airbyte_cdk.models import Type as MessageType
16
- from requests.exceptions import ChunkedEncodingError
17
- from source_iterable.slice_generators import AdjustableSliceGenerator
18
- from source_iterable.source import SourceIterable
19
-
20
- TEST_START_DATE = "2020"
21
-
22
-
23
- @pytest.fixture
24
- def time_mock(request):
25
- with freezegun.freeze_time() as time_mock:
26
- yield time_mock
27
-
28
-
29
- def get_range_days_from_request(request):
30
- query = urllib.parse.urlsplit(request.url).query
31
- query = urllib.parse.parse_qs(query)
32
- return (pendulum.parse(query["endDateTime"][0]) - pendulum.parse(query["startDateTime"][0])).days
33
-
34
-
35
- @mock.patch("logging.getLogger", mock.MagicMock())
36
- def read_from_source(catalog):
37
- return list(
38
- SourceIterable().read(
39
- mock.MagicMock(),
40
- {"start_date": TEST_START_DATE, "api_key": "api_key"},
41
- catalog,
42
- None,
43
- )
44
- )
45
-
46
-
47
- @responses.activate
48
- @pytest.mark.parametrize("catalog", (["email_send"]), indirect=True)
49
- def test_email_stream(mock_lists_resp, catalog, time_mock):
50
- DAYS_DURATION = 100
51
- DAYS_PER_MINUTE_RATE = 8
52
-
53
- time_mock.move_to(pendulum.parse(TEST_START_DATE) + pendulum.Duration(days=DAYS_DURATION))
54
-
55
- ranges: List[int] = []
56
-
57
- def response_cb(req):
58
- days = get_range_days_from_request(req)
59
- ranges.append(days)
60
- time_mock.tick(delta=datetime.timedelta(minutes=days / DAYS_PER_MINUTE_RATE))
61
- return (200, {}, json.dumps({"createdAt": "2020"}))
62
-
63
- responses.add(responses.GET, "https://api.iterable.com/api/lists/getUsers?listId=1", json={"lists": [{"id": 1}]}, status=200)
64
- responses.add_callback("GET", "https://api.iterable.com/api/export/data.json", callback=response_cb)
65
-
66
- records = read_from_source(catalog)
67
- assert records
68
- assert sum(ranges) == DAYS_DURATION
69
- # since read is called on source instance, under the hood .streams() is called which triggers one more http call
70
- assert len(responses.calls) == len(ranges) + 1
71
- assert ranges == [
72
- AdjustableSliceGenerator.INITIAL_RANGE_DAYS,
73
- *([int(DAYS_PER_MINUTE_RATE / AdjustableSliceGenerator.REQUEST_PER_MINUTE_LIMIT)] * 35),
74
- ]
75
-
76
-
77
- @responses.activate
78
- @pytest.mark.parametrize(
79
- "catalog, days_duration, days_per_minute_rate",
80
- [
81
- ("email_send", 10, 200),
82
- ("email_send", 100, 200000),
83
- ("email_send", 10000, 200000),
84
- ("email_click", 1000, 20),
85
- ("email_open", 1000, 1),
86
- ("email_open", 1, 1000),
87
- ("email_open", 0, 1000000),
88
- ],
89
- indirect=["catalog"],
90
- )
91
- def test_email_stream_chunked_encoding(mocker, mock_lists_resp, catalog, days_duration, days_per_minute_rate, time_mock):
92
- mocker.patch("time.sleep")
93
- time_mock.move_to(pendulum.parse(TEST_START_DATE) + pendulum.Duration(days=days_duration))
94
-
95
- ranges: List[int] = []
96
- encoding_throw = 0
97
-
98
- def response_cb(req):
99
- nonlocal encoding_throw
100
- # Every request fails with 2 ChunkedEncodingError exception but works well on third time.
101
- if encoding_throw < 2:
102
- encoding_throw += 1
103
- raise ChunkedEncodingError()
104
- encoding_throw = 0
105
- days = get_range_days_from_request(req)
106
- ranges.append(days)
107
- time_mock.tick(delta=datetime.timedelta(minutes=days / days_per_minute_rate))
108
- return (200, {}, json.dumps({"createdAt": "2020"}))
109
-
110
- responses.add(responses.GET, "https://api.iterable.com/api/lists/getUsers?listId=1", json={"lists": [{"id": 1}]}, status=200)
111
- responses.add_callback("GET", "https://api.iterable.com/api/export/data.json", callback=response_cb)
112
- # added condition because read_from_source also returns LOG messages
113
- records = [record for record in read_from_source(catalog) if record.type == MessageType.RECORD]
114
- assert sum(ranges) == days_duration
115
- assert len(ranges) == len(records)
116
- # since read is called on source instance, under the hood .streams() is called which triggers one more http call
117
- assert len(responses.calls) == 3 * len(ranges) + 1
@@ -1,35 +0,0 @@
1
- #
2
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
- #
4
-
5
- import json
6
- from unittest import mock
7
-
8
- import pendulum
9
- import pytest
10
- import responses
11
- from airbyte_cdk.models import SyncMode
12
- from source_iterable.slice_generators import StreamSlice
13
- from source_iterable.streams import Users
14
-
15
-
16
- @pytest.fixture
17
- def session_mock():
18
- with mock.patch("airbyte_cdk.sources.streams.http.http.requests") as requests_mock:
19
- session_mock = mock.MagicMock()
20
- response_mock = mock.MagicMock()
21
- requests_mock.Session.return_value = session_mock
22
- session_mock.send.return_value = response_mock
23
- response_mock.status_code = 200
24
- yield session_mock
25
-
26
- @responses.activate
27
- def test_stream_correct():
28
- stream_slice = StreamSlice(start_date=pendulum.parse("2020"), end_date=pendulum.parse("2021"))
29
- record_js = {"profileUpdatedAt": "2020"}
30
- NUMBER_OF_RECORDS = 10**2
31
- resp_body = "\n".join([json.dumps(record_js)] * NUMBER_OF_RECORDS)
32
- responses.add("GET", "https://api.iterable.com/api/export/data.json", body=resp_body)
33
- stream = Users(start_date="2020", authenticator=None)
34
- records = list(stream.read_records(sync_mode=SyncMode.full_refresh, cursor_field=None, stream_slice=stream_slice, stream_state={}))
35
- assert len(records) == NUMBER_OF_RECORDS