ingestr 0.14.8__py3-none-any.whl → 0.14.91__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.

Potentially problematic release.


This version of ingestr might be problematic. Click here for more details.

ingestr/src/buildinfo.py CHANGED
@@ -1 +1 @@
1
- version = "v0.14.8"
1
+ version = "v0.14.91"
ingestr/src/factory.py CHANGED
@@ -64,6 +64,7 @@ from ingestr.src.sources import (
64
64
  LinearSource,
65
65
  LinkedInAdsSource,
66
66
  LocalCsvSource,
67
+ MailchimpSource,
67
68
  MixpanelSource,
68
69
  MondaySource,
69
70
  MongoDbSource,
@@ -219,6 +220,7 @@ class SourceDestinationFactory:
219
220
  "wise": WiseSource,
220
221
  "plusvibeai": PlusVibeAISource,
221
222
  "monday": MondaySource,
223
+ "mailchimp": MailchimpSource,
222
224
  }
223
225
  destinations: Dict[str, Type[DestinationProtocol]] = {
224
226
  "bigquery": BigQueryDestination,
@@ -10,6 +10,8 @@ from dlt.sources.helpers import requests
10
10
 
11
11
  from ingestr.src.errors import HTTPError
12
12
 
13
+ TICKETS_QUERY_MAX_PAGE = 10
14
+
13
15
 
14
16
  class FreshdeskClient:
15
17
  """
@@ -85,6 +87,8 @@ class FreshdeskClient:
85
87
  if query is not None:
86
88
  query = query.replace('"', "").strip()
87
89
 
90
+ is_tickets_query = query and endpoint == "tickets"
91
+
88
92
  while True:
89
93
  # Construct the URL for the specific endpoint
90
94
  url = f"{self.base_url}/{endpoint}"
@@ -99,7 +103,7 @@ class FreshdeskClient:
99
103
 
100
104
  params[param_key] = start_date.to_iso8601_string()
101
105
 
102
- if query and endpoint == "tickets":
106
+ if is_tickets_query:
103
107
  url = f"{self.base_url}/search/tickets"
104
108
  params = {
105
109
  "query": f'"{query}"',
@@ -127,3 +131,7 @@ class FreshdeskClient:
127
131
  break
128
132
  yield filtered_data
129
133
  page += 1
134
+
135
+ # https://developers.freshdesk.com/api/#filter_tickets
136
+ if is_tickets_query and page > TICKETS_QUERY_MAX_PAGE:
137
+ break
@@ -0,0 +1,126 @@
1
+ """
2
+ Mailchimp source for data extraction via REST API.
3
+
4
+ This source provides access to Mailchimp account data.
5
+ """
6
+
7
+ from typing import Any, Iterable, Iterator
8
+
9
+ import dlt
10
+ from dlt.sources import DltResource
11
+
12
+ from ingestr.src.http_client import create_client
13
+ from ingestr.src.mailchimp.helpers import (
14
+ create_merge_resource,
15
+ create_nested_resource,
16
+ create_replace_resource,
17
+ )
18
+ from ingestr.src.mailchimp.settings import (
19
+ MERGE_ENDPOINTS,
20
+ NESTED_ENDPOINTS,
21
+ REPLACE_ENDPOINTS,
22
+ )
23
+
24
+
25
+ @dlt.source(max_table_nesting=0, name="mailchimp_source")
26
+ def mailchimp_source(
27
+ api_key: str,
28
+ server: str,
29
+ ) -> Iterable[DltResource]:
30
+ """
31
+ Mailchimp data source.
32
+
33
+ Args:
34
+ api_key: Mailchimp API key for authentication
35
+ server: Server prefix (e.g., 'us10')
36
+
37
+ Yields:
38
+ DltResource: Data resources for Mailchimp data
39
+ """
40
+ base_url = f"https://{server}.api.mailchimp.com/3.0"
41
+ session = create_client()
42
+ auth = ("anystring", api_key)
43
+
44
+ @dlt.resource(
45
+ name="account",
46
+ write_disposition="replace",
47
+ )
48
+ def fetch_account() -> Iterator[dict[str, Any]]:
49
+ """
50
+ Fetch account information from Mailchimp.
51
+
52
+ Table format: account (no parameters needed)
53
+ """
54
+ response = session.get(f"{base_url}/", auth=auth)
55
+ response.raise_for_status()
56
+ data = response.json()
57
+ yield data
58
+
59
+ # Create resources dynamically
60
+ resources = [fetch_account]
61
+
62
+ # Create merge resources (with incremental loading)
63
+ for (
64
+ resource_name,
65
+ endpoint_path,
66
+ data_key,
67
+ primary_key,
68
+ incremental_key,
69
+ ) in MERGE_ENDPOINTS:
70
+ resources.append(
71
+ create_merge_resource(
72
+ base_url,
73
+ session,
74
+ auth,
75
+ resource_name,
76
+ endpoint_path,
77
+ data_key,
78
+ primary_key,
79
+ incremental_key,
80
+ )
81
+ )
82
+
83
+ # Create replace resources (without incremental loading)
84
+ for replace_endpoint in REPLACE_ENDPOINTS:
85
+ resource_name, endpoint_path, data_key, pk = replace_endpoint
86
+ resources.append(
87
+ create_replace_resource(
88
+ base_url,
89
+ session,
90
+ auth,
91
+ resource_name,
92
+ endpoint_path,
93
+ data_key,
94
+ pk,
95
+ )
96
+ )
97
+
98
+ # Create nested resources (depend on parent resources)
99
+ for nested_endpoint in NESTED_ENDPOINTS:
100
+ (
101
+ parent_name,
102
+ parent_path,
103
+ parent_key,
104
+ parent_id_field,
105
+ nested_name,
106
+ nested_path,
107
+ nested_key,
108
+ pk,
109
+ ) = nested_endpoint
110
+ resources.append(
111
+ create_nested_resource(
112
+ base_url,
113
+ session,
114
+ auth,
115
+ parent_name,
116
+ parent_path,
117
+ parent_key,
118
+ parent_id_field,
119
+ nested_name,
120
+ nested_path,
121
+ nested_key,
122
+ pk,
123
+ )
124
+ )
125
+
126
+ return tuple(resources)
@@ -0,0 +1,226 @@
1
+ """
2
+ Helper functions for Mailchimp source.
3
+ """
4
+
5
+ from typing import Any, Iterator
6
+
7
+ import dlt
8
+
9
+
10
+ def fetch_paginated(
11
+ session,
12
+ url: str,
13
+ auth: tuple,
14
+ data_key: str | None = None,
15
+ ) -> Iterator[dict[str, Any]]:
16
+ """
17
+ Helper function to fetch paginated data from Mailchimp API.
18
+
19
+ Args:
20
+ session: HTTP session
21
+ url: API endpoint URL
22
+ auth: Authentication tuple
23
+ data_key: Key in response containing the data array (if None, return whole response)
24
+
25
+ Yields:
26
+ Individual items from the paginated response
27
+ """
28
+ offset = 0
29
+ count = 1000 # Maximum allowed by Mailchimp
30
+
31
+ while True:
32
+ params = {"count": count, "offset": offset}
33
+ response = session.get(url, auth=auth, params=params)
34
+ response.raise_for_status()
35
+ data = response.json()
36
+
37
+ # Extract items from response
38
+ if data_key and data_key in data:
39
+ items = data[data_key]
40
+ elif isinstance(data, list):
41
+ items = data
42
+ else:
43
+ # If no data_key specified and response is dict, yield the whole response
44
+ yield data
45
+ break
46
+
47
+ if not items:
48
+ break
49
+
50
+ yield from items
51
+
52
+ # Check if we've received fewer items than requested (last page)
53
+ if len(items) < count:
54
+ break
55
+
56
+ offset += count
57
+
58
+
59
+ def create_merge_resource(
60
+ base_url: str,
61
+ session,
62
+ auth: tuple,
63
+ name: str,
64
+ path: str,
65
+ key: str,
66
+ pk: str,
67
+ ik: str,
68
+ ):
69
+ """
70
+ Create a DLT resource with merge disposition for incremental loading.
71
+
72
+ Args:
73
+ base_url: Base API URL
74
+ session: HTTP session
75
+ auth: Authentication tuple
76
+ name: Resource name
77
+ path: API endpoint path
78
+ key: Data key in response
79
+ pk: Primary key field
80
+ ik: Incremental key field
81
+
82
+ Returns:
83
+ DLT resource function
84
+ """
85
+
86
+ @dlt.resource(
87
+ name=name,
88
+ write_disposition="merge",
89
+ primary_key=pk,
90
+ )
91
+ def fetch_data(
92
+ updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
93
+ ik, initial_value=None
94
+ ),
95
+ ) -> Iterator[dict[str, Any]]:
96
+ url = f"{base_url}/{path}"
97
+ yield from fetch_paginated(session, url, auth, data_key=key)
98
+
99
+ return fetch_data
100
+
101
+
102
+ def create_replace_resource(
103
+ base_url: str,
104
+ session,
105
+ auth: tuple,
106
+ name: str,
107
+ path: str,
108
+ key: str,
109
+ pk: str | None,
110
+ ):
111
+ """
112
+ Create a DLT resource with replace disposition.
113
+
114
+ Args:
115
+ base_url: Base API URL
116
+ session: HTTP session
117
+ auth: Authentication tuple
118
+ name: Resource name
119
+ path: API endpoint path
120
+ key: Data key in response
121
+ pk: Primary key field (optional)
122
+
123
+ Returns:
124
+ DLT resource function
125
+ """
126
+
127
+ def fetch_data() -> Iterator[dict[str, Any]]:
128
+ url = f"{base_url}/{path}"
129
+ yield from fetch_paginated(session, url, auth, data_key=key)
130
+
131
+ # Apply the resource decorator with conditional primary_key
132
+ if pk is not None:
133
+ return dlt.resource(
134
+ fetch_data,
135
+ name=name,
136
+ write_disposition="replace",
137
+ primary_key=pk,
138
+ )
139
+ else:
140
+ return dlt.resource(
141
+ fetch_data,
142
+ name=name,
143
+ write_disposition="replace",
144
+ )
145
+
146
+
147
+ def create_nested_resource(
148
+ base_url: str,
149
+ session,
150
+ auth: tuple,
151
+ parent_resource_name: str,
152
+ parent_path: str,
153
+ parent_key: str,
154
+ parent_id_field: str,
155
+ nested_name: str,
156
+ nested_path: str,
157
+ nested_key: str | None,
158
+ pk: str | None,
159
+ ):
160
+ """
161
+ Create a nested DLT resource that depends on a parent resource.
162
+
163
+ Args:
164
+ base_url: Base API URL
165
+ session: HTTP session
166
+ auth: Authentication tuple
167
+ parent_resource_name: Name of the parent resource
168
+ parent_path: Parent API endpoint path
169
+ parent_key: Data key in parent response
170
+ parent_id_field: Field name for parent ID
171
+ nested_name: Nested resource name
172
+ nested_path: Nested API endpoint path (with {id} placeholder)
173
+ nested_key: Data key in nested response (None to return whole response)
174
+ pk: Primary key field (optional)
175
+
176
+ Returns:
177
+ DLT resource function
178
+ """
179
+
180
+ def fetch_nested_data() -> Iterator[dict[str, Any]]:
181
+ # First, fetch parent items
182
+ parent_url = f"{base_url}/{parent_path}"
183
+ parent_items = fetch_paginated(session, parent_url, auth, data_key=parent_key)
184
+
185
+ # For each parent item, fetch nested data
186
+ for parent_item in parent_items:
187
+ parent_id = parent_item.get(parent_id_field)
188
+ if parent_id:
189
+ # Build nested URL with parent ID
190
+ nested_url = f"{base_url}/{nested_path.format(id=parent_id)}"
191
+
192
+ # Fetch nested data
193
+ response = session.get(nested_url, auth=auth)
194
+ response.raise_for_status()
195
+ data = response.json()
196
+
197
+ # Extract nested items or return whole response
198
+ if nested_key and nested_key in data:
199
+ items = data[nested_key]
200
+ if isinstance(items, list):
201
+ for item in items:
202
+ # Add parent reference
203
+ item[f"{parent_resource_name}_id"] = parent_id
204
+ yield item
205
+ else:
206
+ items[f"{parent_resource_name}_id"] = parent_id
207
+ yield items
208
+ else:
209
+ # Return whole response with parent reference
210
+ data[f"{parent_resource_name}_id"] = parent_id
211
+ yield data
212
+
213
+ # Apply the resource decorator with conditional primary_key
214
+ if pk is not None:
215
+ return dlt.resource(
216
+ fetch_nested_data,
217
+ name=nested_name,
218
+ write_disposition="replace",
219
+ primary_key=pk,
220
+ )
221
+ else:
222
+ return dlt.resource(
223
+ fetch_nested_data,
224
+ name=nested_name,
225
+ write_disposition="replace",
226
+ )
@@ -0,0 +1,164 @@
1
+ """
2
+ Mailchimp API endpoint configurations.
3
+ """
4
+
5
+ # Endpoints with merge disposition (have both primary_key and incremental_key)
6
+ # Format: (resource_name, endpoint_path, data_key, primary_key, incremental_key)
7
+ MERGE_ENDPOINTS = [
8
+ ("audiences", "lists", "lists", "id", "date_created"),
9
+ ("automations", "automations", "automations", "id", "create_time"),
10
+ ("campaigns", "campaigns", "campaigns", "id", "create_time"),
11
+ ("connected_sites", "connected-sites", "sites", "id", "updated_at"),
12
+ ("conversations", "conversations", "conversations", "id", "last_message.timestamp"),
13
+ ("ecommerce_stores", "ecommerce/stores", "stores", "id", "updated_at"),
14
+ ("facebook_ads", "facebook-ads", "facebook_ads", "id", "updated_at"),
15
+ ("landing_pages", "landing-pages", "landing_pages", "id", "updated_at"),
16
+ ("reports", "reports", "reports", "id", "send_time"),
17
+ ]
18
+
19
+ # Endpoints with replace disposition
20
+ # Format: (resource_name, endpoint_path, data_key, primary_key)
21
+ REPLACE_ENDPOINTS: list[tuple[str, str, str, str | None]] = [
22
+ ("account_exports", "account-exports", "exports", None),
23
+ ("authorized_apps", "authorized-apps", "apps", "id"),
24
+ ("batches", "batches", "batches", None),
25
+ ("campaign_folders", "campaign-folders", "folders", "id"),
26
+ ("chimp_chatter", "activity-feed/chimp-chatter", "chimp_chatter", None),
27
+ ]
28
+
29
+ # Nested endpoints (depend on parent resources)
30
+ # Format: (parent_name, parent_path, parent_key, parent_id_field, nested_name, nested_path, nested_key, pk)
31
+ NESTED_ENDPOINTS: list[tuple[str, str, str, str, str, str, str | None, str | None]] = [
32
+ # Reports nested endpoints
33
+ (
34
+ "reports",
35
+ "reports",
36
+ "reports",
37
+ "id",
38
+ "reports_advice",
39
+ "reports/{id}/advice",
40
+ None,
41
+ None,
42
+ ),
43
+ (
44
+ "reports",
45
+ "reports",
46
+ "reports",
47
+ "id",
48
+ "reports_domain_performance",
49
+ "reports/{id}/domain-performance",
50
+ "domains",
51
+ None,
52
+ ),
53
+ (
54
+ "reports",
55
+ "reports",
56
+ "reports",
57
+ "id",
58
+ "reports_locations",
59
+ "reports/{id}/locations",
60
+ "locations",
61
+ None,
62
+ ),
63
+ (
64
+ "reports",
65
+ "reports",
66
+ "reports",
67
+ "id",
68
+ "reports_sent_to",
69
+ "reports/{id}/sent-to",
70
+ "sent_to",
71
+ None,
72
+ ),
73
+ (
74
+ "reports",
75
+ "reports",
76
+ "reports",
77
+ "id",
78
+ "reports_sub_reports",
79
+ "reports/{id}/sub-reports",
80
+ None,
81
+ None,
82
+ ),
83
+ (
84
+ "reports",
85
+ "reports",
86
+ "reports",
87
+ "id",
88
+ "reports_unsubscribed",
89
+ "reports/{id}/unsubscribed",
90
+ "unsubscribes",
91
+ None,
92
+ ),
93
+ # Lists/Audiences nested endpoints
94
+ (
95
+ "audiences",
96
+ "lists",
97
+ "lists",
98
+ "id",
99
+ "lists_activity",
100
+ "lists/{id}/activity",
101
+ "activity",
102
+ None,
103
+ ),
104
+ (
105
+ "audiences",
106
+ "lists",
107
+ "lists",
108
+ "id",
109
+ "lists_clients",
110
+ "lists/{id}/clients",
111
+ "clients",
112
+ None,
113
+ ),
114
+ (
115
+ "audiences",
116
+ "lists",
117
+ "lists",
118
+ "id",
119
+ "lists_growth_history",
120
+ "lists/{id}/growth-history",
121
+ "history",
122
+ None,
123
+ ),
124
+ (
125
+ "audiences",
126
+ "lists",
127
+ "lists",
128
+ "id",
129
+ "lists_interest_categories",
130
+ "lists/{id}/interest-categories",
131
+ "categories",
132
+ None,
133
+ ),
134
+ (
135
+ "audiences",
136
+ "lists",
137
+ "lists",
138
+ "id",
139
+ "lists_locations",
140
+ "lists/{id}/locations",
141
+ "locations",
142
+ None,
143
+ ),
144
+ (
145
+ "audiences",
146
+ "lists",
147
+ "lists",
148
+ "id",
149
+ "lists_merge_fields",
150
+ "lists/{id}/merge-fields",
151
+ "merge_fields",
152
+ None,
153
+ ),
154
+ (
155
+ "audiences",
156
+ "lists",
157
+ "lists",
158
+ "id",
159
+ "lists_segments",
160
+ "lists/{id}/segments",
161
+ "segments",
162
+ None,
163
+ ),
164
+ ]
ingestr/src/sources.py CHANGED
@@ -3971,3 +3971,29 @@ class MondaySource:
3971
3971
  ).with_resources(table_name)
3972
3972
  except ResourcesNotFoundError:
3973
3973
  raise UnsupportedResourceError(table_name, "Monday")
3974
+
3975
+
3976
+ class MailchimpSource:
3977
+ def handles_incrementality(self) -> bool:
3978
+ return False
3979
+
3980
+ def dlt_source(self, uri: str, table: str, **kwargs):
3981
+ parsed_uri = urlparse(uri)
3982
+ query_params = parse_qs(parsed_uri.query)
3983
+ api_key = query_params.get("api_key")
3984
+ server = query_params.get("server")
3985
+
3986
+ if api_key is None:
3987
+ raise MissingValueError("api_key", "Mailchimp")
3988
+ if server is None:
3989
+ raise MissingValueError("server", "Mailchimp")
3990
+
3991
+ from ingestr.src.mailchimp import mailchimp_source
3992
+
3993
+ try:
3994
+ return mailchimp_source(
3995
+ api_key=api_key[0],
3996
+ server=server[0],
3997
+ ).with_resources(table)
3998
+ except ResourcesNotFoundError:
3999
+ raise UnsupportedResourceError(table, "Mailchimp")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ingestr
3
- Version: 0.14.8
3
+ Version: 0.14.91
4
4
  Summary: ingestr is a command-line application that ingests data from various sources and stores them in any database.
5
5
  Project-URL: Homepage, https://github.com/bruin-data/ingestr
6
6
  Project-URL: Issues, https://github.com/bruin-data/ingestr/issues
@@ -2,17 +2,17 @@ ingestr/conftest.py,sha256=OE2yxeTCosS9CUFVuqNypm-2ftYvVBeeq7egm3878cI,1981
2
2
  ingestr/main.py,sha256=qo0g3wCFl8a_1jUwXagX8L1Q8PKKQlTF7md9pfnzW0Y,27155
3
3
  ingestr/src/.gitignore,sha256=8cX1AZTSI0TcdZFGTmS_oyBjpfCzhOEt0DdAo2dFIY8,203
4
4
  ingestr/src/blob.py,sha256=UUWMjHUuoR9xP1XZQ6UANQmnMVyDx3d0X4-2FQC271I,2138
5
- ingestr/src/buildinfo.py,sha256=HwioZibn1wg3VNE_xRCjeAulmiz7PFpou8vbiY1JROU,20
5
+ ingestr/src/buildinfo.py,sha256=S1bddOwYtoZWcUt0-yZ-Xo-uAYE96jHPwDOFnYbIpyg,21
6
6
  ingestr/src/destinations.py,sha256=QtjE0AGs0WkPHaI2snWPHJ8HHi4lwXUBYLJPklz8Mvk,27772
7
7
  ingestr/src/errors.py,sha256=fhJ2BxOqOsBfOxuTDKfZblvawBrPG3x_1VikIxMZBRI,874
8
- ingestr/src/factory.py,sha256=03eGDe2rL6qyT5sGmTGZi-XIwJbbdoedE_KjW3ZF7QY,7661
8
+ ingestr/src/factory.py,sha256=z6lcaGwK8o7AcaeshX7TP4WfB_jsIi0TvarcquGYnK0,7720
9
9
  ingestr/src/filters.py,sha256=0n0sNAVG_f-B_1r7lW5iNtw9z_G1bxWzPaiL1i6tnbU,1665
10
10
  ingestr/src/http_client.py,sha256=bxqsk6nJNXCo-79gW04B53DQO-yr25vaSsqP0AKtjx4,732
11
11
  ingestr/src/loader.py,sha256=9NaWAyfkXdqAZSS-N72Iwo36Lbx4PyqIfaaH1dNdkFs,1712
12
12
  ingestr/src/masking.py,sha256=VN0LdfvExhQ1bZMRylGtaBUIoH-vjuIUmRnYKwo3yiY,11358
13
13
  ingestr/src/partition.py,sha256=BrIP6wFJvyR7Nus_3ElnfxknUXeCipK_E_bB8kZowfc,969
14
14
  ingestr/src/resource.py,sha256=ZqmZxFQVGlF8rFPhBiUB08HES0yoTj8sZ--jKfaaVps,1164
15
- ingestr/src/sources.py,sha256=bHkR8wZ46TBgvIWxpBp_xm-KHBnSEYbjVDKtAOzAB2I,138819
15
+ ingestr/src/sources.py,sha256=DSJAiwfVV0czMpHX1d93gQ7xIk_RyGPubQ31KfT5N8I,139639
16
16
  ingestr/src/table_definition.py,sha256=REbAbqdlmUMUuRh8nEQRreWjPVOQ5ZcfqGkScKdCrmk,390
17
17
  ingestr/src/time.py,sha256=H_Fk2J4ShXyUM-EMY7MqCLZQhlnZMZvO952bmZPc4yE,254
18
18
  ingestr/src/version.py,sha256=J_2xgZ0mKlvuHcjdKCx2nlioneLH0I47JiU_Slr_Nwc,189
@@ -61,7 +61,7 @@ ingestr/src/fluxx/helpers.py,sha256=zJmlQWwiv9snnLqTygiWVZy7-0rGi_K427hRUuZeHEM,
61
61
  ingestr/src/frankfurter/__init__.py,sha256=gOdL8ZqgHHYZByjtfE3WX3BTRHdYqyn9FpQwzDHSAx0,5089
62
62
  ingestr/src/frankfurter/helpers.py,sha256=SpRr992OcSf7IDI5y-ToUdO6m6sGpqFz59LTY0ojchI,1502
63
63
  ingestr/src/freshdesk/__init__.py,sha256=OIP3GikA6BMh9sruU3jih-swdFNSposr48oQhy1WGNk,3145
64
- ingestr/src/freshdesk/freshdesk_client.py,sha256=pWBJypCp8Vvgac63s_KA0I1ZcfNIHRHqEI03BZlFQ7o,4891
64
+ ingestr/src/freshdesk/freshdesk_client.py,sha256=Fp2YBG3hMtR-t4XfjRjc7-wiUMHCIe9hnFhrGNNGwoA,5122
65
65
  ingestr/src/freshdesk/settings.py,sha256=0Wr_OMnUZcTlry7BmALssLxD2yh686JW4moLNv12Jnw,409
66
66
  ingestr/src/fundraiseup/__init__.py,sha256=q3TQeP1HdbWNeXFMq-0-BdSo82Fq4Io1OEYOY1cAYcU,1743
67
67
  ingestr/src/fundraiseup/client.py,sha256=klU57l8iJ5NAS1nTb_4UyVAerbPWpTa8PMHlpp9Riz0,2453
@@ -109,6 +109,9 @@ ingestr/src/linear/helpers.py,sha256=J9lTuu8rHHM3YTA082_wfvByW6Teie4_44eYaVmDBhQ
109
109
  ingestr/src/linkedin_ads/__init__.py,sha256=CAPWFyV24loziiphbLmODxZUXZJwm4JxlFkr56q0jfo,1855
110
110
  ingestr/src/linkedin_ads/dimension_time_enum.py,sha256=EmHRdkFyTAfo4chGjThrwqffWJxmAadZMbpTvf0xkQc,198
111
111
  ingestr/src/linkedin_ads/helpers.py,sha256=eUWudRVlXl4kqIhfXQ1eVsUpZwJn7UFqKSpnbLfxzds,4498
112
+ ingestr/src/mailchimp/__init__.py,sha256=-V4IacEJ8wvngHQsSuGu_Z4xAt1XtQIHigvOoWcndw0,3175
113
+ ingestr/src/mailchimp/helpers.py,sha256=KXchokX5ApLqpjIAHzKWkI23ETGmQMlUrbOC5rm7q6k,6166
114
+ ingestr/src/mailchimp/settings.py,sha256=0x9q-8tvieuDZb0H8GNdNWnRFmVDo2jK68IoBVdKE6k,4014
112
115
  ingestr/src/mixpanel/__init__.py,sha256=s1QtqMP0BTGW6YtdCabJFWj7lEn7KujzELwGpBOQgfs,1796
113
116
  ingestr/src/mixpanel/client.py,sha256=c_reouegOVYBOwHLfgYFwpmkba0Sxro1Zkml07NCYf0,3602
114
117
  ingestr/src/monday/__init__.py,sha256=ZNdGCC_1CEYlgxAef-5QO56Drm9IMP82-rZpEvbD8aY,6918
@@ -180,8 +183,8 @@ ingestr/testdata/merge_expected.csv,sha256=DReHqWGnQMsf2PBv_Q2pfjsgvikYFnf1zYcQZ
180
183
  ingestr/testdata/merge_part1.csv,sha256=Pw8Z9IDKcNU0qQHx1z6BUf4rF_-SxKGFOvymCt4OY9I,185
181
184
  ingestr/testdata/merge_part2.csv,sha256=T_GiWxA81SN63_tMOIuemcvboEFeAmbKc7xRXvL9esw,287
182
185
  ingestr/tests/unit/test_smartsheets.py,sha256=zf3DXT29Y4TH2lNPBFphdjlaelUUyPJcsW2UO68RzDs,4862
183
- ingestr-0.14.8.dist-info/METADATA,sha256=1AsSAUImnAuW5zcUkenw5qLwHuPaZUPcHShFJLSQlZM,15265
184
- ingestr-0.14.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
185
- ingestr-0.14.8.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
186
- ingestr-0.14.8.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
187
- ingestr-0.14.8.dist-info/RECORD,,
186
+ ingestr-0.14.91.dist-info/METADATA,sha256=S_ZjXBHtmd92ZS7l6u_Xg6W0TpXynfhXp7AA4WggEI4,15266
187
+ ingestr-0.14.91.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
188
+ ingestr-0.14.91.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
189
+ ingestr-0.14.91.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
190
+ ingestr-0.14.91.dist-info/RECORD,,