ingestr 0.7.7__py3-none-any.whl → 0.8.1__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.
- ingestr/main.py +10 -0
- ingestr/src/.gitignore +10 -0
- ingestr/src/adjust/_init_.py +31 -0
- ingestr/src/adjust/helpers.py +82 -0
- ingestr/src/appsflyer/_init_.py +24 -0
- ingestr/src/appsflyer/client.py +106 -0
- ingestr/src/facebook_ads/__init__.py +197 -0
- ingestr/src/facebook_ads/exceptions.py +5 -0
- ingestr/src/facebook_ads/helpers.py +255 -0
- ingestr/src/facebook_ads/settings.py +208 -0
- ingestr/src/factory.py +15 -0
- ingestr/src/kafka/__init__.py +103 -0
- ingestr/src/kafka/helpers.py +227 -0
- ingestr/src/klaviyo/_init_.py +173 -0
- ingestr/src/klaviyo/client.py +212 -0
- ingestr/src/klaviyo/helpers.py +19 -0
- ingestr/src/shopify/__init__.py +1752 -54
- ingestr/src/shopify/helpers.py +73 -32
- ingestr/src/sources.py +230 -7
- ingestr/src/version.py +1 -1
- {ingestr-0.7.7.dist-info → ingestr-0.8.1.dist-info}/METADATA +22 -1
- {ingestr-0.7.7.dist-info → ingestr-0.8.1.dist-info}/RECORD +25 -11
- {ingestr-0.7.7.dist-info → ingestr-0.8.1.dist-info}/WHEEL +0 -0
- {ingestr-0.7.7.dist-info → ingestr-0.8.1.dist-info}/entry_points.txt +0 -0
- {ingestr-0.7.7.dist-info → ingestr-0.8.1.dist-info}/licenses/LICENSE.md +0 -0
ingestr/src/shopify/helpers.py
CHANGED
|
@@ -14,6 +14,53 @@ from .settings import DEFAULT_API_VERSION, DEFAULT_PARTNER_API_VERSION
|
|
|
14
14
|
TOrderStatus = Literal["open", "closed", "cancelled", "any"]
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def convert_datetime_fields(item: Dict[str, Any]) -> Dict[str, Any]:
|
|
18
|
+
"""Convert timestamp fields in the item to pendulum datetime objects
|
|
19
|
+
|
|
20
|
+
The item is modified in place, including nested items.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
item: The item to convert
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The same data item (for convenience)
|
|
27
|
+
"""
|
|
28
|
+
fields = ["created_at", "updated_at", "createdAt", "updatedAt"]
|
|
29
|
+
|
|
30
|
+
def convert_nested(obj: Any) -> Any:
|
|
31
|
+
if isinstance(obj, dict):
|
|
32
|
+
for key, value in obj.items():
|
|
33
|
+
if key in fields and isinstance(value, str):
|
|
34
|
+
obj[key] = ensure_pendulum_datetime(value)
|
|
35
|
+
else:
|
|
36
|
+
obj[key] = convert_nested(value)
|
|
37
|
+
elif isinstance(obj, list):
|
|
38
|
+
return [convert_nested(elem) for elem in obj]
|
|
39
|
+
return obj
|
|
40
|
+
|
|
41
|
+
return convert_nested(item)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def remove_nodes_key(item: Any) -> Any:
|
|
45
|
+
"""
|
|
46
|
+
Recursively remove the 'nodes' key from dictionaries if it's the only key and its value is an array.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
item: The item to process (can be a dict, list, or any other type)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The processed item
|
|
53
|
+
"""
|
|
54
|
+
if isinstance(item, dict):
|
|
55
|
+
if len(item) == 1 and "nodes" in item and isinstance(item["nodes"], list):
|
|
56
|
+
return [remove_nodes_key(node) for node in item["nodes"]]
|
|
57
|
+
return {k: remove_nodes_key(v) for k, v in item.items()}
|
|
58
|
+
elif isinstance(item, list):
|
|
59
|
+
return [remove_nodes_key(element) for element in item]
|
|
60
|
+
else:
|
|
61
|
+
return item
|
|
62
|
+
|
|
63
|
+
|
|
17
64
|
class ShopifyApi:
|
|
18
65
|
"""
|
|
19
66
|
A Shopify API client that can be used to get pages of data from Shopify.
|
|
@@ -50,57 +97,38 @@ class ShopifyApi:
|
|
|
50
97
|
"""
|
|
51
98
|
url = urljoin(self.shop_url, f"/admin/api/{self.api_version}/{resource}.json")
|
|
52
99
|
|
|
100
|
+
resource_last = resource.split("/")[-1]
|
|
101
|
+
|
|
53
102
|
headers = {"X-Shopify-Access-Token": self.private_app_password}
|
|
54
103
|
while url:
|
|
55
104
|
response = requests.get(url, params=params, headers=headers)
|
|
56
105
|
response.raise_for_status()
|
|
57
106
|
json = response.json()
|
|
58
|
-
|
|
59
|
-
yield [self._convert_datetime_fields(item) for item in json[resource]]
|
|
107
|
+
yield [convert_datetime_fields(item) for item in json[resource_last]]
|
|
60
108
|
url = response.links.get("next", {}).get("url")
|
|
61
109
|
# Query params are included in subsequent page URLs
|
|
62
110
|
params = None
|
|
63
111
|
|
|
64
|
-
def _convert_datetime_fields(self, item: Dict[str, Any]) -> Dict[str, Any]:
|
|
65
|
-
"""Convert timestamp fields in the item to pendulum datetime objects
|
|
66
|
-
|
|
67
|
-
The item is modified in place.
|
|
68
112
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Returns:
|
|
73
|
-
The same data item (for convenience)
|
|
74
|
-
"""
|
|
75
|
-
fields = ["created_at", "updated_at"]
|
|
76
|
-
for field in fields:
|
|
77
|
-
if field in item:
|
|
78
|
-
item[field] = ensure_pendulum_datetime(item[field])
|
|
79
|
-
return item
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class ShopifyPartnerApi:
|
|
83
|
-
"""Client for Shopify Partner grapql API"""
|
|
113
|
+
class ShopifyGraphQLApi:
|
|
114
|
+
"""Client for Shopify GraphQL API"""
|
|
84
115
|
|
|
85
116
|
def __init__(
|
|
86
117
|
self,
|
|
87
118
|
access_token: str,
|
|
88
|
-
organization_id: str,
|
|
89
119
|
api_version: str = DEFAULT_PARTNER_API_VERSION,
|
|
120
|
+
base_url: str = "partners.shopify.com",
|
|
90
121
|
) -> None:
|
|
91
|
-
"""
|
|
92
|
-
Args:
|
|
93
|
-
access_token: The access token to use
|
|
94
|
-
organization_id: The organization id to query
|
|
95
|
-
api_version: The API version to use (e.g. 2023-01)
|
|
96
|
-
"""
|
|
97
122
|
self.access_token = access_token
|
|
98
|
-
self.organization_id = organization_id
|
|
99
123
|
self.api_version = api_version
|
|
124
|
+
self.base_url = base_url
|
|
100
125
|
|
|
101
126
|
@property
|
|
102
127
|
def graphql_url(self) -> str:
|
|
103
|
-
|
|
128
|
+
if self.base_url.startswith("https://"):
|
|
129
|
+
return f"{self.base_url}/admin/api/{self.api_version}/graphql.json"
|
|
130
|
+
|
|
131
|
+
return f"https://{self.base_url}/admin/api/{self.api_version}/graphql.json"
|
|
104
132
|
|
|
105
133
|
def run_graphql_query(
|
|
106
134
|
self, query: str, variables: Optional[DictStrAny] = None
|
|
@@ -130,18 +158,31 @@ class ShopifyPartnerApi:
|
|
|
130
158
|
query: str,
|
|
131
159
|
data_items_path: jsonpath.TJsonPath,
|
|
132
160
|
pagination_cursor_path: jsonpath.TJsonPath,
|
|
161
|
+
pagination_cursor_has_next_page_path: jsonpath.TJsonPath,
|
|
133
162
|
pagination_variable_name: str,
|
|
134
163
|
variables: Optional[DictStrAny] = None,
|
|
135
164
|
) -> Iterable[TDataItems]:
|
|
136
165
|
variables = dict(variables or {})
|
|
137
166
|
while True:
|
|
138
167
|
data = self.run_graphql_query(query, variables)
|
|
139
|
-
print(data)
|
|
140
168
|
data_items = jsonpath.find_values(data_items_path, data)
|
|
169
|
+
|
|
141
170
|
if not data_items:
|
|
142
171
|
break
|
|
143
|
-
|
|
172
|
+
|
|
173
|
+
yield [
|
|
174
|
+
remove_nodes_key(convert_datetime_fields(item)) for item in data_items
|
|
175
|
+
]
|
|
176
|
+
|
|
144
177
|
cursors = jsonpath.find_values(pagination_cursor_path, data)
|
|
145
178
|
if not cursors:
|
|
146
179
|
break
|
|
180
|
+
|
|
181
|
+
if pagination_cursor_has_next_page_path:
|
|
182
|
+
has_next_page = jsonpath.find_values(
|
|
183
|
+
pagination_cursor_has_next_page_path, data
|
|
184
|
+
)
|
|
185
|
+
if not has_next_page or not has_next_page[0]:
|
|
186
|
+
break
|
|
187
|
+
|
|
147
188
|
variables[pagination_variable_name] = cursors[-1]
|
ingestr/src/sources.py
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import csv
|
|
3
3
|
import json
|
|
4
|
-
from datetime import date
|
|
4
|
+
from datetime import date, datetime
|
|
5
5
|
from typing import Any, Callable, Optional
|
|
6
6
|
from urllib.parse import parse_qs, urlparse
|
|
7
7
|
|
|
8
8
|
import dlt
|
|
9
9
|
|
|
10
|
+
from ingestr.src.adjust._init_ import adjust_source
|
|
10
11
|
from ingestr.src.airtable import airtable_source
|
|
12
|
+
from ingestr.src.appsflyer._init_ import appsflyer_source
|
|
11
13
|
from ingestr.src.chess import source
|
|
14
|
+
from ingestr.src.facebook_ads import facebook_ads_source, facebook_insights_source
|
|
12
15
|
from ingestr.src.google_sheets import google_spreadsheet
|
|
13
16
|
from ingestr.src.gorgias import gorgias_source
|
|
14
17
|
from ingestr.src.hubspot import hubspot
|
|
18
|
+
from ingestr.src.kafka import kafka_consumer
|
|
19
|
+
from ingestr.src.kafka.helpers import KafkaCredentials
|
|
20
|
+
from ingestr.src.klaviyo._init_ import klaviyo_source
|
|
15
21
|
from ingestr.src.mongodb import mongodb_collection
|
|
16
22
|
from ingestr.src.notion import notion_databases
|
|
17
23
|
from ingestr.src.shopify import shopify_source
|
|
@@ -184,11 +190,6 @@ class ShopifySource:
|
|
|
184
190
|
return True
|
|
185
191
|
|
|
186
192
|
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
187
|
-
if kwargs.get("incremental_key"):
|
|
188
|
-
raise ValueError(
|
|
189
|
-
"Shopify takes care of incrementality on its own, you should not provide incremental_key"
|
|
190
|
-
)
|
|
191
|
-
|
|
192
193
|
source_fields = urlparse(uri)
|
|
193
194
|
source_params = parse_qs(source_fields.query)
|
|
194
195
|
api_key = source_params.get("api_key")
|
|
@@ -203,7 +204,19 @@ class ShopifySource:
|
|
|
203
204
|
date_args["end_date"] = kwargs.get("interval_end")
|
|
204
205
|
|
|
205
206
|
resource = None
|
|
206
|
-
if table in [
|
|
207
|
+
if table in [
|
|
208
|
+
"products",
|
|
209
|
+
"products_legacy",
|
|
210
|
+
"orders",
|
|
211
|
+
"customers",
|
|
212
|
+
"inventory_items",
|
|
213
|
+
"transactions",
|
|
214
|
+
"balance",
|
|
215
|
+
"events",
|
|
216
|
+
"price_rules",
|
|
217
|
+
"discounts",
|
|
218
|
+
"taxonomy",
|
|
219
|
+
]:
|
|
207
220
|
resource = table
|
|
208
221
|
else:
|
|
209
222
|
raise ValueError(
|
|
@@ -405,6 +418,48 @@ class StripeAnalyticsSource:
|
|
|
405
418
|
).with_resources(endpoint)
|
|
406
419
|
|
|
407
420
|
|
|
421
|
+
class FacebookAdsSource:
|
|
422
|
+
def handles_incrementality(self) -> bool:
|
|
423
|
+
return True
|
|
424
|
+
|
|
425
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
426
|
+
# facebook_ads://?access_token=abcd&account_id=1234
|
|
427
|
+
if kwargs.get("incremental_key"):
|
|
428
|
+
raise ValueError(
|
|
429
|
+
"Facebook Ads takes care of incrementality on its own, you should not provide incremental_key"
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
access_token = None
|
|
433
|
+
account_id = None
|
|
434
|
+
source_field = urlparse(uri)
|
|
435
|
+
source_params = parse_qs(source_field.query)
|
|
436
|
+
access_token = source_params.get("access_token")
|
|
437
|
+
account_id = source_params.get("account_id")
|
|
438
|
+
|
|
439
|
+
if not access_token or not account_id:
|
|
440
|
+
raise ValueError(
|
|
441
|
+
"access_token and accound_id are required to connect to Facebook Ads."
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
endpoint = None
|
|
445
|
+
if table in ["campaigns", "ad_sets", "ad_creatives", "ads", "leads"]:
|
|
446
|
+
endpoint = table
|
|
447
|
+
elif table in "facebook_insights":
|
|
448
|
+
return facebook_insights_source(
|
|
449
|
+
access_token=access_token[0],
|
|
450
|
+
account_id=account_id[0],
|
|
451
|
+
).with_resources("facebook_insights")
|
|
452
|
+
else:
|
|
453
|
+
raise ValueError(
|
|
454
|
+
"fResource '{table}' is not supported for Facebook Ads source yet, if you are interested in it please create a GitHub issue at https://github.com/bruin-data/ingestr"
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
return facebook_ads_source(
|
|
458
|
+
access_token=access_token[0],
|
|
459
|
+
account_id=account_id[0],
|
|
460
|
+
).with_resources(endpoint)
|
|
461
|
+
|
|
462
|
+
|
|
408
463
|
class SlackSource:
|
|
409
464
|
def handles_incrementality(self) -> bool:
|
|
410
465
|
return True
|
|
@@ -511,3 +566,171 @@ class AirtableSource:
|
|
|
511
566
|
return airtable_source(
|
|
512
567
|
base_id=base_id[0], table_names=tables, access_token=access_token[0]
|
|
513
568
|
)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class KlaviyoSource:
|
|
572
|
+
def handles_incrementality(self) -> bool:
|
|
573
|
+
return True
|
|
574
|
+
|
|
575
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
576
|
+
if kwargs.get("incremental_key"):
|
|
577
|
+
raise ValueError(
|
|
578
|
+
"klaviyo_source takes care of incrementality on its own, you should not provide incremental_key"
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
source_fields = urlparse(uri)
|
|
582
|
+
source_params = parse_qs(source_fields.query)
|
|
583
|
+
api_key = source_params.get("api_key")
|
|
584
|
+
|
|
585
|
+
if not api_key:
|
|
586
|
+
raise ValueError("api_key in the URI is required to connect to klaviyo")
|
|
587
|
+
|
|
588
|
+
resource = None
|
|
589
|
+
if table in [
|
|
590
|
+
"events",
|
|
591
|
+
"profiles",
|
|
592
|
+
"campaigns",
|
|
593
|
+
"metrics",
|
|
594
|
+
"tags",
|
|
595
|
+
"coupons",
|
|
596
|
+
"catalog-variants",
|
|
597
|
+
"catalog-categories",
|
|
598
|
+
"catalog-items",
|
|
599
|
+
"forms",
|
|
600
|
+
"lists",
|
|
601
|
+
"images",
|
|
602
|
+
"segments",
|
|
603
|
+
"flows",
|
|
604
|
+
"templates",
|
|
605
|
+
]:
|
|
606
|
+
resource = table
|
|
607
|
+
else:
|
|
608
|
+
raise ValueError(
|
|
609
|
+
f"Resource '{table}' is not supported for Klaviyo source yet, if you are interested in it please create a GitHub issue at https://github.com/bruin-data/ingestr"
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
start_date = kwargs.get("interval_start") or "2000-01-01"
|
|
613
|
+
return klaviyo_source(
|
|
614
|
+
api_key=api_key[0],
|
|
615
|
+
start_date=start_date,
|
|
616
|
+
).with_resources(resource)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
class KafkaSource:
|
|
620
|
+
def handles_incrementality(self) -> bool:
|
|
621
|
+
return False
|
|
622
|
+
|
|
623
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
624
|
+
# kafka://?bootstrap_servers=localhost:9092&group_id=test_group&security_protocol=SASL_SSL&sasl_mechanisms=PLAIN&sasl_username=example_username&sasl_password=example_secret
|
|
625
|
+
source_fields = urlparse(uri)
|
|
626
|
+
source_params = parse_qs(source_fields.query)
|
|
627
|
+
|
|
628
|
+
bootstrap_servers = source_params.get("bootstrap_servers")
|
|
629
|
+
group_id = source_params.get("group_id")
|
|
630
|
+
security_protocol = source_params.get("security_protocol", [])
|
|
631
|
+
sasl_mechanisms = source_params.get("sasl_mechanisms", [])
|
|
632
|
+
sasl_username = source_params.get("sasl_username", [])
|
|
633
|
+
sasl_password = source_params.get("sasl_password", [])
|
|
634
|
+
batch_size = source_params.get("batch_size", [3000])
|
|
635
|
+
batch_timeout = source_params.get("batch_timeout", [3])
|
|
636
|
+
|
|
637
|
+
if not bootstrap_servers:
|
|
638
|
+
raise ValueError(
|
|
639
|
+
"bootstrap_servers in the URI is required to connect to kafka"
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
if not group_id:
|
|
643
|
+
raise ValueError("group_id in the URI is required to connect to kafka")
|
|
644
|
+
|
|
645
|
+
start_date = kwargs.get("interval_start")
|
|
646
|
+
return kafka_consumer(
|
|
647
|
+
topics=[table],
|
|
648
|
+
credentials=KafkaCredentials(
|
|
649
|
+
bootstrap_servers=bootstrap_servers[0],
|
|
650
|
+
group_id=group_id[0],
|
|
651
|
+
security_protocol=security_protocol[0]
|
|
652
|
+
if len(security_protocol) > 0
|
|
653
|
+
else None, # type: ignore
|
|
654
|
+
sasl_mechanisms=sasl_mechanisms[0]
|
|
655
|
+
if len(sasl_mechanisms) > 0
|
|
656
|
+
else None, # type: ignore
|
|
657
|
+
sasl_username=sasl_username[0] if len(sasl_username) > 0 else None, # type: ignore
|
|
658
|
+
sasl_password=sasl_password[0] if len(sasl_password) > 0 else None, # type: ignore
|
|
659
|
+
),
|
|
660
|
+
start_from=start_date,
|
|
661
|
+
batch_size=int(batch_size[0]),
|
|
662
|
+
batch_timeout=int(batch_timeout[0]),
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
class AdjustSource:
|
|
667
|
+
def handles_incrementality(self) -> bool:
|
|
668
|
+
return True
|
|
669
|
+
|
|
670
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
671
|
+
if kwargs.get("incremental_key"):
|
|
672
|
+
raise ValueError(
|
|
673
|
+
"Adjust takes care of incrementality on its own, you should not provide incremental_key"
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
source_part = urlparse(uri)
|
|
677
|
+
source_params = parse_qs(source_part.query)
|
|
678
|
+
api_key = source_params.get("api_key")
|
|
679
|
+
|
|
680
|
+
if not api_key:
|
|
681
|
+
raise ValueError("api_key in the URI is required to connect to Adjust")
|
|
682
|
+
|
|
683
|
+
interval_start = kwargs.get("interval_start")
|
|
684
|
+
interval_end = kwargs.get("interval_end")
|
|
685
|
+
|
|
686
|
+
start_date = (
|
|
687
|
+
interval_start.strftime("%Y-%m-%d") if interval_start else "2000-01-01"
|
|
688
|
+
)
|
|
689
|
+
end_date = (
|
|
690
|
+
interval_end.strftime("%Y-%m-%d")
|
|
691
|
+
if interval_end
|
|
692
|
+
else datetime.now().strftime("%Y-%m-%d")
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
Endpoint = None
|
|
696
|
+
if table in ["campaigns", "creatives"]:
|
|
697
|
+
Endpoint = table
|
|
698
|
+
|
|
699
|
+
return adjust_source(
|
|
700
|
+
start_date=start_date, end_date=end_date, api_key=api_key[0]
|
|
701
|
+
).with_resources(Endpoint)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
class AppsflyerSource:
|
|
705
|
+
def handles_incrementality(self) -> bool:
|
|
706
|
+
return True
|
|
707
|
+
|
|
708
|
+
def dlt_source(self, uri: str, table: str, **kwargs):
|
|
709
|
+
if kwargs.get("incremental_key"):
|
|
710
|
+
raise ValueError(
|
|
711
|
+
"Appsflyer_Source takes care of incrementality on its own, you should not provide incremental_key"
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
source_fields = urlparse(uri)
|
|
715
|
+
source_params = parse_qs(source_fields.query)
|
|
716
|
+
api_key = source_params.get("api_key")
|
|
717
|
+
|
|
718
|
+
if not api_key:
|
|
719
|
+
raise ValueError("api_key in the URI is required to connect to Appsflyer")
|
|
720
|
+
|
|
721
|
+
resource = None
|
|
722
|
+
if table in ["campaigns", "creatives"]:
|
|
723
|
+
resource = table
|
|
724
|
+
else:
|
|
725
|
+
raise ValueError(
|
|
726
|
+
f"Resource '{table}' is not supported for Appsflyer source yet, if you are interested in it please create a GitHub issue at https://github.com/bruin-data/ingestr"
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
start_date = kwargs.get("interval_start") or "2024-01-02"
|
|
730
|
+
end_date = kwargs.get("interval_end") or "2024-01-29"
|
|
731
|
+
|
|
732
|
+
return appsflyer_source(
|
|
733
|
+
api_key=api_key[0],
|
|
734
|
+
start_date=start_date,
|
|
735
|
+
end_date=end_date,
|
|
736
|
+
).with_resources(resource)
|
ingestr/src/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.8.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ingestr
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
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
|
|
@@ -14,11 +14,13 @@ Classifier: Operating System :: OS Independent
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
15
|
Classifier: Topic :: Database
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
|
+
Requires-Dist: confluent-kafka>=2.3.0
|
|
17
18
|
Requires-Dist: cx-oracle==8.3.0
|
|
18
19
|
Requires-Dist: databricks-sql-connector==2.9.3
|
|
19
20
|
Requires-Dist: dlt==0.5.1
|
|
20
21
|
Requires-Dist: duckdb-engine==0.11.5
|
|
21
22
|
Requires-Dist: duckdb==0.10.2
|
|
23
|
+
Requires-Dist: facebook-business==20.0.0
|
|
22
24
|
Requires-Dist: google-api-python-client==2.130.0
|
|
23
25
|
Requires-Dist: google-cloud-bigquery-storage==2.24.0
|
|
24
26
|
Requires-Dist: mysql-connector-python==9.0.0
|
|
@@ -42,6 +44,7 @@ Requires-Dist: sqlalchemy==1.4.52
|
|
|
42
44
|
Requires-Dist: stripe==10.7.0
|
|
43
45
|
Requires-Dist: tqdm==4.66.2
|
|
44
46
|
Requires-Dist: typer==0.12.3
|
|
47
|
+
Requires-Dist: types-requests==2.32.0.20240907
|
|
45
48
|
Description-Content-Type: text/markdown
|
|
46
49
|
|
|
47
50
|
<div align="center">
|
|
@@ -176,15 +179,28 @@ Join our Slack community [here](https://join.slack.com/t/bruindatacommunity/shar
|
|
|
176
179
|
<tr>
|
|
177
180
|
<td colspan="3" style='text-align:center;'><strong>Platforms</strong></td>
|
|
178
181
|
</tr>
|
|
182
|
+
<td>Adjust</td>
|
|
183
|
+
<td>✅</td>
|
|
184
|
+
<td>-</td>
|
|
179
185
|
<tr>
|
|
180
186
|
<td>Airtable</td>
|
|
181
187
|
<td>✅</td>
|
|
182
188
|
<td>-</td>
|
|
189
|
+
</tr>
|
|
190
|
+
<tr>
|
|
191
|
+
<td>AppsFlyer</td>
|
|
192
|
+
<td>✅</td>
|
|
193
|
+
<td>-</td>
|
|
183
194
|
</tr>
|
|
184
195
|
<tr>
|
|
185
196
|
<td>Chess.com</td>
|
|
186
197
|
<td>✅</td>
|
|
187
198
|
<td>-</td>
|
|
199
|
+
</tr>
|
|
200
|
+
<tr>
|
|
201
|
+
<td>Facebook Ads</td>
|
|
202
|
+
<td>✅</td>
|
|
203
|
+
<td>-</td>
|
|
188
204
|
</tr>
|
|
189
205
|
<tr>
|
|
190
206
|
<td>Gorgias</td>
|
|
@@ -200,6 +216,11 @@ Join our Slack community [here](https://join.slack.com/t/bruindatacommunity/shar
|
|
|
200
216
|
<td>HubSpot</td>
|
|
201
217
|
<td>✅</td>
|
|
202
218
|
<td>-</td>
|
|
219
|
+
</tr>
|
|
220
|
+
<tr>
|
|
221
|
+
<td>Klaviyo</td>
|
|
222
|
+
<td>✅</td>
|
|
223
|
+
<td>-</td>
|
|
203
224
|
</tr>
|
|
204
225
|
<tr>
|
|
205
226
|
<td>Notion</td>
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
ingestr/main.py,sha256=
|
|
1
|
+
ingestr/main.py,sha256=U66TM57ePv-RdoAftQ0pFZx8woZUQnLepKa50C-bA5I,17655
|
|
2
|
+
ingestr/src/.gitignore,sha256=8cX1AZTSI0TcdZFGTmS_oyBjpfCzhOEt0DdAo2dFIY8,203
|
|
2
3
|
ingestr/src/destinations.py,sha256=2SfPMjtTelPmzQmc3zNs8xGcKIPuGn_hoZFIBUuhjXI,6338
|
|
3
|
-
ingestr/src/factory.py,sha256
|
|
4
|
-
ingestr/src/sources.py,sha256=
|
|
4
|
+
ingestr/src/factory.py,sha256=-_KwBpbNAegv_oXIB57klyjUb3K6e0lw_xdi5bwmarI,4645
|
|
5
|
+
ingestr/src/sources.py,sha256=0IGguMm85E3Rahu6zVLawoe2d4lqRY31uHuxlqCsiQc,25324
|
|
5
6
|
ingestr/src/table_definition.py,sha256=REbAbqdlmUMUuRh8nEQRreWjPVOQ5ZcfqGkScKdCrmk,390
|
|
6
|
-
ingestr/src/version.py,sha256=
|
|
7
|
+
ingestr/src/version.py,sha256=Ocl79hbbH8_jdr5dGC90VR1cAvZc05Rc0tkZttUnMjo,22
|
|
8
|
+
ingestr/src/adjust/_init_.py,sha256=_jJE3Ywvv-YyJ7ywICdht_X2Gnd1cKm6F1wAfnpXuWM,890
|
|
9
|
+
ingestr/src/adjust/helpers.py,sha256=kkYC3MqMHLNucuQ50klZWrvd3o8VfUysNtZTQSsKZ_c,2588
|
|
7
10
|
ingestr/src/airtable/__init__.py,sha256=GHWYrjI2qhs_JihdNJysB0Ni3bzqT_MLXn_S9_Q5zRA,2775
|
|
11
|
+
ingestr/src/appsflyer/_init_.py,sha256=ne2-9FQ654Drtd3GkKQv8Bwb6LEqCnJw49MfO5Jyzgs,739
|
|
12
|
+
ingestr/src/appsflyer/client.py,sha256=TNmwakLzmO6DZW3wcfLfQRl7aNBHgFqSsk4ef-MmJ1w,3084
|
|
8
13
|
ingestr/src/chess/__init__.py,sha256=PaxT2DObudOGlhyoENE5LjR6rTdsxiqKKpAZeyzVLCA,6791
|
|
9
14
|
ingestr/src/chess/helpers.py,sha256=v1HTImOMjAF7AzZUPDIuHu00e7ut0o5y1kWcVYo4QZw,549
|
|
10
15
|
ingestr/src/chess/settings.py,sha256=p0RlCGgtXUacPDEvZmwzSWmzX0Apj1riwfz-nrMK89k,158
|
|
16
|
+
ingestr/src/facebook_ads/__init__.py,sha256=ZZyogV48gmhDcC3CYQEsC4qT3Q6JI9IOnMff2NS1M-A,9207
|
|
17
|
+
ingestr/src/facebook_ads/exceptions.py,sha256=4Nlbc0Mv3i5g-9AoyT-n1PIa8IDi3VCTfEAzholx4Wc,115
|
|
18
|
+
ingestr/src/facebook_ads/helpers.py,sha256=ZLbNHiKer5lPb4g3_435XeBJr57Wv0o1KTyBA1mQ100,9068
|
|
19
|
+
ingestr/src/facebook_ads/settings.py,sha256=1IxZeP_4rN3IBvAncNHOoqpzAirx0Hz-MUK_tl6UTFk,4881
|
|
11
20
|
ingestr/src/google_sheets/README.md,sha256=wFQhvmGpRA38Ba2N_WIax6duyD4c7c_pwvvprRfQDnw,5470
|
|
12
21
|
ingestr/src/google_sheets/__init__.py,sha256=5qlX-6ilx5MW7klC7B_0jGSxloQSLkSESTh4nlY3Aos,6643
|
|
13
22
|
ingestr/src/google_sheets/helpers/__init__.py,sha256=5hXZrZK8cMO3UOuL-s4OKOpdACdihQD0hYYlSEu-iQ8,35
|
|
@@ -18,6 +27,11 @@ ingestr/src/gorgias/helpers.py,sha256=DamuijnvhGY9hysQO4txrVMf4izkGbh5qfBKImdOIN
|
|
|
18
27
|
ingestr/src/hubspot/__init__.py,sha256=eSD_lEIEd16YijAtUATFG8FGO8YGPm-MtAk94KKsx6o,9740
|
|
19
28
|
ingestr/src/hubspot/helpers.py,sha256=PTn-UHJv1ENIvA5azUTaHCmFXgmHLJC1tUatQ1N-KFE,6727
|
|
20
29
|
ingestr/src/hubspot/settings.py,sha256=9P1OKiRL88kl_m8n1HhuG-Qpq9VGbqPLn5Q0QYneToU,2193
|
|
30
|
+
ingestr/src/kafka/__init__.py,sha256=wMCXdiraeKd1Kssi9WcVCGZaNGm2tJEtnNyuB4aR5_k,3541
|
|
31
|
+
ingestr/src/kafka/helpers.py,sha256=V9WcVn3PKnEpggArHda4vnAcaV8VDuh__dSmRviJb5Y,7502
|
|
32
|
+
ingestr/src/klaviyo/_init_.py,sha256=nq2T1p3Xc7yiwGabsZBp2Jy2fa8_n5oxqxBnUGhKOgg,6592
|
|
33
|
+
ingestr/src/klaviyo/client.py,sha256=tPj79ia7AW0ZOJhzlKNPCliGbdojRNwUFp8HvB2ym5s,7434
|
|
34
|
+
ingestr/src/klaviyo/helpers.py,sha256=_i-SHffhv25feLDcjy6Blj1UxYLISCwVCMgGtrlnYHk,496
|
|
21
35
|
ingestr/src/mongodb/__init__.py,sha256=E7SDeCyYNkYZZ_RFhjCRDZUGpKtaxpPG5sFSmKJV62U,4336
|
|
22
36
|
ingestr/src/mongodb/helpers.py,sha256=80vtAeNyUn1iMN0CeLrTlKqYN6I6fHF81Kd2UuE8Kns,5653
|
|
23
37
|
ingestr/src/notion/__init__.py,sha256=36wUui8finbc85ObkRMq8boMraXMUehdABN_AMe_hzA,1834
|
|
@@ -25,9 +39,9 @@ ingestr/src/notion/settings.py,sha256=MwQVZViJtnvOegfjXYc_pJ50oUYgSRPgwqu7TvpeMO
|
|
|
25
39
|
ingestr/src/notion/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
40
|
ingestr/src/notion/helpers/client.py,sha256=QXuudkf5Zzff98HRsCqA1g1EZWIrnfn1falPrnKg_y4,5500
|
|
27
41
|
ingestr/src/notion/helpers/database.py,sha256=gigPibTeVefP3lA-8w4aOwX67pj7RlciPk5koDs1ry8,2737
|
|
28
|
-
ingestr/src/shopify/__init__.py,sha256=
|
|
42
|
+
ingestr/src/shopify/__init__.py,sha256=uqYoodwLHLuZHwvQx_lwOI23TzxnzHbGuZUUNzTA2Os,62031
|
|
29
43
|
ingestr/src/shopify/exceptions.py,sha256=BhV3lIVWeBt8Eh4CWGW_REFJpGCzvW6-62yZrBWa3nQ,50
|
|
30
|
-
ingestr/src/shopify/helpers.py,sha256=
|
|
44
|
+
ingestr/src/shopify/helpers.py,sha256=2MlqyCc7VJxpYlGAxw-bYpSuCZNfat0E5zDe1jQYVP4,6279
|
|
31
45
|
ingestr/src/shopify/settings.py,sha256=StY0EPr7wFJ7KzRRDN4TKxV0_gkIS1wPj2eR4AYSsDk,141
|
|
32
46
|
ingestr/src/slack/__init__.py,sha256=UfUhkS6FnCKJeXkkJ5QrmdT5nZm5czjtomsQu_x9WUM,9987
|
|
33
47
|
ingestr/src/slack/helpers.py,sha256=08TLK7vhFvH_uekdLVOLF3bTDe1zgH0QxHObXHzk1a8,6545
|
|
@@ -50,8 +64,8 @@ ingestr/testdata/delete_insert_part2.csv,sha256=B_KUzpzbNdDY_n7wWop1mT2cz36TmayS
|
|
|
50
64
|
ingestr/testdata/merge_expected.csv,sha256=DReHqWGnQMsf2PBv_Q2pfjsgvikYFnf1zYcQZ7ZqYN0,276
|
|
51
65
|
ingestr/testdata/merge_part1.csv,sha256=Pw8Z9IDKcNU0qQHx1z6BUf4rF_-SxKGFOvymCt4OY9I,185
|
|
52
66
|
ingestr/testdata/merge_part2.csv,sha256=T_GiWxA81SN63_tMOIuemcvboEFeAmbKc7xRXvL9esw,287
|
|
53
|
-
ingestr-0.
|
|
54
|
-
ingestr-0.
|
|
55
|
-
ingestr-0.
|
|
56
|
-
ingestr-0.
|
|
57
|
-
ingestr-0.
|
|
67
|
+
ingestr-0.8.1.dist-info/METADATA,sha256=r3eBIXMFdnmLPzlK7mh0BZWRJoHvXyZjls7UxkPb93Q,6755
|
|
68
|
+
ingestr-0.8.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
69
|
+
ingestr-0.8.1.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
|
|
70
|
+
ingestr-0.8.1.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
|
|
71
|
+
ingestr-0.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|