ingestr 0.13.38__py3-none-any.whl → 0.13.40__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/main.py CHANGED
@@ -34,6 +34,7 @@ PARQUET_SUPPORTED_DESTINATIONS = [
34
34
  "snowflake",
35
35
  "databricks",
36
36
  "synapse",
37
+ "s3",
37
38
  ]
38
39
 
39
40
  # these sources would return a JSON for sure, which means they cannot be used with Parquet loader for BigQuery
@@ -485,9 +486,7 @@ def ingest(
485
486
  print(
486
487
  f"[bold yellow] Primary Key:[/bold yellow] {primary_key if primary_key else 'None'}"
487
488
  )
488
- print(
489
- f"[bold yellow] Pipeline ID:[/bold yellow] {m.hexdigest()}"
490
- )
489
+ print(f"[bold yellow] Pipeline ID:[/bold yellow] {m.hexdigest()}")
491
490
  print()
492
491
 
493
492
  if not yes:
ingestr/src/buildinfo.py CHANGED
@@ -1 +1 @@
1
- version = "v0.13.38"
1
+ version = "v0.13.40"
@@ -7,11 +7,13 @@ import tempfile
7
7
  from urllib.parse import parse_qs, quote, urlparse
8
8
 
9
9
  import dlt
10
+ import dlt.destinations.impl.filesystem.filesystem
10
11
  from dlt.common.configuration.specs import AwsCredentials
11
12
  from dlt.destinations.impl.clickhouse.configuration import (
12
13
  ClickHouseCredentials,
13
14
  )
14
15
 
16
+ from ingestr.src.errors import MissingValueError
15
17
  from ingestr.src.loader import load_dlt_file
16
18
 
17
19
 
@@ -382,3 +384,71 @@ class ClickhouseDestination:
382
384
 
383
385
  def post_load(self):
384
386
  pass
387
+
388
+
389
+ class S3FSClient(dlt.destinations.impl.filesystem.filesystem.FilesystemClient):
390
+ @property
391
+ def dataset_path(self):
392
+ # override to remove dataset path
393
+ return self.bucket_path
394
+
395
+
396
+ class S3FS(dlt.destinations.filesystem):
397
+ @property
398
+ def client_class(self):
399
+ return S3FSClient
400
+
401
+
402
+ class S3Destination:
403
+ def dlt_dest(self, uri: str, **kwargs):
404
+ parsed_uri = urlparse(uri)
405
+ params = parse_qs(parsed_uri.query)
406
+
407
+ access_key_id = params.get("access_key_id", [None])[0]
408
+ if access_key_id is None:
409
+ raise MissingValueError("access_key_id", "S3")
410
+
411
+ secret_access_key = params.get("secret_access_key", [None])[0]
412
+ if secret_access_key is None:
413
+ raise MissingValueError("secret_access_key", "S3")
414
+
415
+ endpoint_url = params.get("endpoint_url", [None])[0]
416
+
417
+ creds = AwsCredentials(
418
+ aws_access_key_id=access_key_id,
419
+ aws_secret_access_key=secret_access_key,
420
+ endpoint_url=endpoint_url,
421
+ )
422
+
423
+ dest_table = self.validate_table(kwargs["dest_table"])
424
+ table_parts = dest_table.split("/")
425
+ base_path = "/".join(table_parts[:-1])
426
+
427
+ opts = {
428
+ "bucket_url": f"s3://{base_path}",
429
+ "credentials": creds,
430
+ # supresses dlt warnings about dataset name normalization.
431
+ # we don't use dataset names in S3 so it's fine to disable this.
432
+ "enable_dataset_name_normalization": False,
433
+ }
434
+ layout = params.get("layout", [None])[0]
435
+ if layout is not None:
436
+ opts["layout"] = layout
437
+
438
+ return S3FS(**opts) # type: ignore
439
+
440
+ def validate_table(self, table: str):
441
+ table = table.strip("/ ")
442
+ if len(table.split("/")) < 2:
443
+ raise ValueError("Table name must be in the format {bucket-name}/{path}")
444
+ return table
445
+
446
+ def dlt_run_params(self, uri: str, table: str, **kwargs):
447
+ table = self.validate_table(table)
448
+ table_parts = table.split("/")
449
+ return {
450
+ "table_name": table_parts[-1],
451
+ }
452
+
453
+ def post_load(self) -> None:
454
+ pass
@@ -0,0 +1,80 @@
1
+ from datetime import date, datetime
2
+ from typing import Any, Optional
3
+
4
+ import dlt
5
+ import pendulum
6
+ from dlt.common.time import ensure_pendulum_datetime
7
+ from pendulum import parse
8
+
9
+ from elasticsearch import Elasticsearch
10
+
11
+
12
+ @dlt.source
13
+ def elasticsearch_source(
14
+ connection_url: str,
15
+ index: str,
16
+ verify_certs: bool,
17
+ incremental: Optional[dlt.sources.incremental] = None,
18
+ ):
19
+ client = Elasticsearch(connection_url, verify_certs=verify_certs)
20
+
21
+ @dlt.resource(
22
+ name=index, primary_key="id", write_disposition="merge", incremental=incremental
23
+ )
24
+ def get_documents(incremental=incremental):
25
+ body = {"query": {"match_all": {}}}
26
+
27
+ if incremental:
28
+ start_value = incremental.last_value
29
+ range_filter = {"gte": start_value}
30
+ if incremental.end_value is not None:
31
+ range_filter["lt"] = incremental.end_value
32
+ body = {"query": {"range": {incremental.cursor_path: range_filter}}}
33
+
34
+ page = client.search(index=index, scroll="5m", size=5, body=body)
35
+
36
+ sid = page["_scroll_id"]
37
+ hits = page["hits"]["hits"]
38
+
39
+ if not hits:
40
+ return
41
+
42
+ # fetching first page (via .search)
43
+ for doc in hits:
44
+ doc_data = {"id": doc["_id"], **doc["_source"]}
45
+ if incremental:
46
+ doc_data[incremental.cursor_path] = convert_elasticsearch_objs(
47
+ doc_data[incremental.cursor_path]
48
+ )
49
+ yield doc_data
50
+
51
+ while True:
52
+ # fetching page 2 and other pages (via .scroll)
53
+ page = client.scroll(scroll_id=sid, scroll="5m")
54
+ sid = page["_scroll_id"]
55
+ hits = page["hits"]["hits"]
56
+ if not hits:
57
+ break
58
+ for doc in hits:
59
+ doc_data = {"id": doc["_id"], **doc["_source"]}
60
+ if incremental:
61
+ doc_data[incremental.cursor_path] = convert_elasticsearch_objs(
62
+ doc_data[incremental.cursor_path]
63
+ )
64
+ yield doc_data
65
+
66
+ client.clear_scroll(scroll_id=sid)
67
+
68
+ return get_documents
69
+
70
+
71
+ def convert_elasticsearch_objs(value: Any) -> Any:
72
+ if isinstance(value, str):
73
+ parsed_date = parse(value, strict=False)
74
+ if parsed_date is not None:
75
+ if isinstance(
76
+ parsed_date,
77
+ (pendulum.DateTime, pendulum.Date, datetime, date, str, float, int),
78
+ ):
79
+ return ensure_pendulum_datetime(parsed_date)
80
+ return value
ingestr/src/factory.py CHANGED
@@ -13,6 +13,7 @@ from ingestr.src.destinations import (
13
13
  MsSQLDestination,
14
14
  PostgresDestination,
15
15
  RedshiftDestination,
16
+ S3Destination,
16
17
  SnowflakeDestination,
17
18
  SynapseDestination,
18
19
  )
@@ -27,8 +28,10 @@ from ingestr.src.sources import (
27
28
  AsanaSource,
28
29
  ChessSource,
29
30
  DynamoDBSource,
31
+ ElasticsearchSource,
30
32
  FacebookAdsSource,
31
33
  FrankfurterSource,
34
+ FreshdeskSource,
32
35
  GCSSource,
33
36
  GitHubSource,
34
37
  GoogleAdsSource,
@@ -44,6 +47,7 @@ from ingestr.src.sources import (
44
47
  MongoDbSource,
45
48
  NotionSource,
46
49
  PersonioSource,
50
+ PhantombusterSource,
47
51
  PipedriveSource,
48
52
  S3Source,
49
53
  SalesforceSource,
@@ -53,8 +57,6 @@ from ingestr.src.sources import (
53
57
  StripeAnalyticsSource,
54
58
  TikTokSource,
55
59
  ZendeskSource,
56
- FreshdeskSource,
57
- PhantombusterSource,
58
60
  )
59
61
 
60
62
  SQL_SOURCE_SCHEMES = [
@@ -152,6 +154,7 @@ class SourceDestinationFactory:
152
154
  "frankfurter": FrankfurterSource,
153
155
  "freshdesk": FreshdeskSource,
154
156
  "phantombuster": PhantombusterSource,
157
+ "elasticsearch": ElasticsearchSource,
155
158
  }
156
159
  destinations: Dict[str, Type[DestinationProtocol]] = {
157
160
  "bigquery": BigQueryDestination,
@@ -170,6 +173,7 @@ class SourceDestinationFactory:
170
173
  "athena": AthenaDestination,
171
174
  "clickhouse+native": ClickhouseDestination,
172
175
  "clickhouse": ClickhouseDestination,
176
+ "s3": S3Destination,
173
177
  }
174
178
 
175
179
  def __init__(self, source_uri: str, destination_uri: str):
@@ -149,7 +149,7 @@ def get_report(
149
149
 
150
150
  # process request
151
151
  processed_response_generator = process_report(response=response)
152
-
152
+
153
153
  # import pdb; pdb.set_trace()
154
154
  yield from processed_response_generator
155
155
  offset += per_page
@@ -225,7 +225,9 @@ def _resolve_dimension_value(dimension_name: str, dimension_value: str) -> Any:
225
225
  return dimension_value
226
226
 
227
227
 
228
- def convert_minutes_ranges_to_minute_range_objects(minutes_ranges: str) -> List[MinuteRange]:
228
+ def convert_minutes_ranges_to_minute_range_objects(
229
+ minutes_ranges: str,
230
+ ) -> List[MinuteRange]:
229
231
  minutes_ranges = minutes_ranges.strip()
230
232
  minutes = minutes_ranges.replace(" ", "").split(",")
231
233
  if minutes == "":
@@ -233,7 +235,6 @@ def convert_minutes_ranges_to_minute_range_objects(minutes_ranges: str) -> List[
233
235
  "Invalid input. Minutes range should be startminute-endminute format. For example: 1-2,5-6"
234
236
  )
235
237
 
236
-
237
238
  minute_range_objects = []
238
239
  for min_range in minutes:
239
240
  if "-" not in min_range:
@@ -246,14 +247,16 @@ def convert_minutes_ranges_to_minute_range_objects(minutes_ranges: str) -> List[
246
247
  raise ValueError(
247
248
  f"Invalid input '{min_range}'. Both start and end minutes must be digits. For example: 1-2,5-6"
248
249
  )
249
-
250
+
250
251
  end_minutes_ago = int(parts[0])
251
252
  start_minutes_ago = int(parts[1])
252
- minute_range_objects.append(MinuteRange(
253
- name=f"{end_minutes_ago}-{start_minutes_ago} minutes ago",
254
- start_minutes_ago= start_minutes_ago,
255
- end_minutes_ago=end_minutes_ago
256
- ))
253
+ minute_range_objects.append(
254
+ MinuteRange(
255
+ name=f"{end_minutes_ago}-{start_minutes_ago} minutes ago",
256
+ start_minutes_ago=start_minutes_ago,
257
+ end_minutes_ago=end_minutes_ago,
258
+ )
259
+ )
257
260
 
258
261
  return minute_range_objects
259
262
 
@@ -3,11 +3,10 @@ from typing import Iterable, Optional
3
3
  import dlt
4
4
  import pendulum
5
5
  import requests
6
- from dlt.common.typing import TDataItem, TAnyDateTime
6
+ from dlt.common.typing import TAnyDateTime, TDataItem
7
7
  from dlt.sources import DltResource
8
8
  from dlt.sources.helpers.requests import Client
9
9
 
10
-
11
10
  from ingestr.src.phantombuster.client import PhantombusterClient
12
11
 
13
12
 
@@ -27,9 +26,13 @@ def create_client() -> requests.Session:
27
26
  request_backoff_factor=2,
28
27
  ).session
29
28
 
29
+
30
30
  @dlt.source(max_table_nesting=0)
31
- def phantombuster_source(api_key: str, agent_id: str, start_date: TAnyDateTime, end_date: TAnyDateTime | None) -> Iterable[DltResource]:
31
+ def phantombuster_source(
32
+ api_key: str, agent_id: str, start_date: TAnyDateTime, end_date: TAnyDateTime | None
33
+ ) -> Iterable[DltResource]:
32
34
  client = PhantombusterClient(api_key)
35
+
33
36
  @dlt.resource(
34
37
  write_disposition="merge",
35
38
  primary_key="container_id",
@@ -55,6 +58,8 @@ def phantombuster_source(api_key: str, agent_id: str, start_date: TAnyDateTime,
55
58
 
56
59
  start_dt = dateTime.last_value
57
60
 
58
- yield client.fetch_containers_result(create_client(), agent_id, start_date=start_dt, end_date=end_dt)
61
+ yield client.fetch_containers_result(
62
+ create_client(), agent_id, start_date=start_dt, end_date=end_dt
63
+ )
59
64
 
60
65
  return completed_phantoms
@@ -14,14 +14,22 @@ class PhantombusterClient:
14
14
  "accept": "application/json",
15
15
  }
16
16
 
17
- def fetch_containers_result(self, session: requests.Session, agent_id: str, start_date: pendulum.DateTime, end_date: pendulum.DateTime):
17
+ def fetch_containers_result(
18
+ self,
19
+ session: requests.Session,
20
+ agent_id: str,
21
+ start_date: pendulum.DateTime,
22
+ end_date: pendulum.DateTime,
23
+ ):
18
24
  url = "https://api.phantombuster.com/api/v2/containers/fetch-all/"
19
25
  before_ended_at = None
20
26
  limit = 100
21
27
 
22
- started_at = start_date.int_timestamp * 1000 + int(start_date.microsecond / 1000)
28
+ started_at = start_date.int_timestamp * 1000 + int(
29
+ start_date.microsecond / 1000
30
+ )
23
31
  ended_at = end_date.int_timestamp * 1000 + int(end_date.microsecond / 1000)
24
-
32
+
25
33
  while True:
26
34
  params: dict[str, Union[str, int, float, bytes, None]] = {
27
35
  "agentId": agent_id,
@@ -40,24 +48,33 @@ class PhantombusterClient:
40
48
  container_ended_at = container.get("endedAt")
41
49
 
42
50
  if before_ended_at is None or before_ended_at > container_ended_at:
43
- before_ended_at = container_ended_at
44
-
51
+ before_ended_at = container_ended_at
52
+
45
53
  if container_ended_at < started_at or container_ended_at > ended_at:
46
54
  continue
47
-
55
+
48
56
  try:
49
57
  result = self.fetch_result_object(session, container["id"])
50
- partition_dt = pendulum.from_timestamp(container_ended_at / 1000, tz="UTC").date()
51
- container_ended_at_datetime = pendulum.from_timestamp(container_ended_at / 1000, tz="UTC")
52
- row = {"container_id": container["id"],"container": container, "result": result, "partition_dt": partition_dt, "ended_at": container_ended_at_datetime}
58
+ partition_dt = pendulum.from_timestamp(
59
+ container_ended_at / 1000, tz="UTC"
60
+ ).date()
61
+ container_ended_at_datetime = pendulum.from_timestamp(
62
+ container_ended_at / 1000, tz="UTC"
63
+ )
64
+ row = {
65
+ "container_id": container["id"],
66
+ "container": container,
67
+ "result": result,
68
+ "partition_dt": partition_dt,
69
+ "ended_at": container_ended_at_datetime,
70
+ }
53
71
  yield row
54
-
72
+
55
73
  except requests.RequestException as e:
56
74
  print(f"Error fetching result for container {container['id']}: {e}")
57
-
75
+
58
76
  if data["maxLimitReached"] is False:
59
77
  break
60
-
61
78
 
62
79
  def fetch_result_object(self, session: requests.Session, container_id: str):
63
80
  result_url = (
ingestr/src/sources.py CHANGED
@@ -1492,7 +1492,9 @@ class GoogleAnalyticsSource:
1492
1492
 
1493
1493
  minute_range_objects = []
1494
1494
  if len(fields) == 4:
1495
- minute_range_objects = helpers.convert_minutes_ranges_to_minute_range_objects(fields[3])
1495
+ minute_range_objects = (
1496
+ helpers.convert_minutes_ranges_to_minute_range_objects(fields[3])
1497
+ )
1496
1498
 
1497
1499
  datetime = ""
1498
1500
  resource_name = fields[0].lower()
@@ -2217,10 +2219,10 @@ class FrankfurterSource:
2217
2219
 
2218
2220
 
2219
2221
  class FreshdeskSource:
2220
- # freshdesk://domain?api_key=<api_key>
2222
+ # freshdesk://domain?api_key=<api_key>
2221
2223
  def handles_incrementality(self) -> bool:
2222
2224
  return True
2223
-
2225
+
2224
2226
  def dlt_source(self, uri: str, table: str, **kwargs):
2225
2227
  parsed_uri = urlparse(uri)
2226
2228
  domain = parsed_uri.netloc
@@ -2230,43 +2232,54 @@ class FreshdeskSource:
2230
2232
  if not domain:
2231
2233
  raise MissingValueError("domain", "Freshdesk")
2232
2234
 
2233
- if '.' in domain:
2234
- domain = domain.split('.')[0]
2235
-
2235
+ if "." in domain:
2236
+ domain = domain.split(".")[0]
2237
+
2236
2238
  api_key = params.get("api_key")
2237
2239
  if api_key is None:
2238
2240
  raise MissingValueError("api_key", "Freshdesk")
2239
-
2240
- if table not in ["agents", "companies", "contacts", "groups", "roles", "tickets"]:
2241
+
2242
+ if table not in [
2243
+ "agents",
2244
+ "companies",
2245
+ "contacts",
2246
+ "groups",
2247
+ "roles",
2248
+ "tickets",
2249
+ ]:
2241
2250
  raise UnsupportedResourceError(table, "Freshdesk")
2242
-
2251
+
2243
2252
  from ingestr.src.freshdesk import freshdesk_source
2244
- return freshdesk_source(api_secret_key=api_key[0], domain=domain).with_resources(table)
2253
+
2254
+ return freshdesk_source(
2255
+ api_secret_key=api_key[0], domain=domain
2256
+ ).with_resources(table)
2257
+
2245
2258
 
2246
2259
  class PhantombusterSource:
2247
2260
  def handles_incrementality(self) -> bool:
2248
2261
  return True
2249
-
2262
+
2250
2263
  def dlt_source(self, uri: str, table: str, **kwargs):
2251
- #phantombuster://?api_key=<api_key>
2252
- #source table = phantom_results:agent_id
2264
+ # phantombuster://?api_key=<api_key>
2265
+ # source table = phantom_results:agent_id
2253
2266
  parsed_uri = urlparse(uri)
2254
2267
  params = parse_qs(parsed_uri.query)
2255
2268
  api_key = params.get("api_key")
2256
2269
  if api_key is None:
2257
2270
  raise MissingValueError("api_key", "Phantombuster")
2258
-
2271
+
2259
2272
  table_fields = table.replace(" ", "").split(":")
2260
2273
  table_name = table_fields[0]
2261
-
2274
+
2262
2275
  agent_id = table_fields[1] if len(table_fields) > 1 else None
2263
-
2276
+
2264
2277
  if table_name not in ["completed_phantoms"]:
2265
2278
  raise UnsupportedResourceError(table_name, "Phantombuster")
2266
-
2279
+
2267
2280
  if not agent_id:
2268
2281
  raise MissingValueError("agent_id", "Phantombuster")
2269
-
2282
+
2270
2283
  start_date = kwargs.get("interval_start")
2271
2284
  if start_date is None:
2272
2285
  start_date = ensure_pendulum_datetime("2018-01-01").in_tz("UTC")
@@ -2276,6 +2289,62 @@ class PhantombusterSource:
2276
2289
  end_date = kwargs.get("interval_end")
2277
2290
  if end_date is not None:
2278
2291
  end_date = ensure_pendulum_datetime(end_date).in_tz("UTC")
2279
-
2292
+
2280
2293
  from ingestr.src.phantombuster import phantombuster_source
2281
- return phantombuster_source(api_key=api_key[0], agent_id=agent_id, start_date=start_date, end_date=end_date).with_resources(table_name)
2294
+
2295
+ return phantombuster_source(
2296
+ api_key=api_key[0],
2297
+ agent_id=agent_id,
2298
+ start_date=start_date,
2299
+ end_date=end_date,
2300
+ ).with_resources(table_name)
2301
+
2302
+
2303
+ class ElasticsearchSource:
2304
+ def handles_incrementality(self) -> bool:
2305
+ return False
2306
+
2307
+ def dlt_source(self, uri: str, table: str, **kwargs):
2308
+ from ingestr.src.elasticsearch import elasticsearch_source
2309
+
2310
+ incremental = None
2311
+ if kwargs.get("incremental_key"):
2312
+ start_value = kwargs.get("interval_start")
2313
+ end_value = kwargs.get("interval_end")
2314
+
2315
+ incremental = dlt_incremental(
2316
+ kwargs.get("incremental_key", ""),
2317
+ initial_value=start_value,
2318
+ end_value=end_value,
2319
+ range_end="closed",
2320
+ range_start="closed",
2321
+ )
2322
+
2323
+ # elasticsearch://localhost:9200?secure=true&verify_certs=false
2324
+ parsed = urlparse(uri)
2325
+
2326
+ index = table
2327
+ if not index:
2328
+ raise ValueError("Table name must be provided which is the index name in elasticsearch")
2329
+
2330
+ query_params = parsed.query
2331
+ params = parse_qs(query_params)
2332
+
2333
+ secure = True
2334
+ if "secure" in params:
2335
+ secure = params["secure"][0].capitalize() == "True"
2336
+
2337
+ verify_certs = True
2338
+ if "verify_certs" in params:
2339
+ verify_certs = params["verify_certs"][0].capitalize() == "True"
2340
+
2341
+ scheme = "https" if secure else "http"
2342
+ netloc = parsed.netloc
2343
+ connection_url = f"{scheme}://{netloc}"
2344
+
2345
+ return elasticsearch_source(
2346
+ connection_url=connection_url,
2347
+ index=index,
2348
+ verify_certs=verify_certs,
2349
+ incremental=incremental,
2350
+ ).with_resources(table)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ingestr
3
- Version: 0.13.38
3
+ Version: 0.13.40
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
@@ -50,6 +50,8 @@ Requires-Dist: dlt==1.10.0
50
50
  Requires-Dist: dnspython==2.7.0
51
51
  Requires-Dist: duckdb-engine==0.17.0
52
52
  Requires-Dist: duckdb==1.2.1
53
+ Requires-Dist: elastic-transport==8.17.1
54
+ Requires-Dist: elasticsearch==8.10.1
53
55
  Requires-Dist: et-xmlfile==2.0.0
54
56
  Requires-Dist: facebook-business==20.0.0
55
57
  Requires-Dist: filelock==3.17.0
@@ -1,16 +1,16 @@
1
1
  ingestr/conftest.py,sha256=Q03FIJIZpLBbpj55cfCHIKEjc1FCvWJhMF2cidUJKQU,1748
2
- ingestr/main.py,sha256=QHLjpCItCgL5XHcTezBOp6Vdy_VHAPciRjqy63ZIK2s,25285
2
+ ingestr/main.py,sha256=Pe_rzwcDRKIYa7baEVUAAPOHyqQbX29RUexMl0F_S1k,25273
3
3
  ingestr/src/.gitignore,sha256=8cX1AZTSI0TcdZFGTmS_oyBjpfCzhOEt0DdAo2dFIY8,203
4
4
  ingestr/src/blob.py,sha256=onMe5ZHxPXTdcB_s2oGNdMo-XQJ3ajwOsWE9eSTGFmc,1495
5
- ingestr/src/buildinfo.py,sha256=iW7GVGJjTCNtuINLzbFuur_d0ALxf_JmZSmuwD9yZ2Y,21
6
- ingestr/src/destinations.py,sha256=Z79f01BSmEaXnQno2IQVt4Th4dmD-BiOQXlibZJ5sTw,13180
5
+ ingestr/src/buildinfo.py,sha256=kx4THAPAkQ2P32re7w8VrOWSESl3Fz3mnN83MSygHeE,21
6
+ ingestr/src/destinations.py,sha256=MctbeJUyNr0DRB0XYt2xAbEKkHZ40-nXXEOYCs4KuoE,15420
7
7
  ingestr/src/errors.py,sha256=Ufs4_DfE77_E3vnA1fOQdi6cmuLVNm7_SbFLkL1XPGk,686
8
- ingestr/src/factory.py,sha256=Mm_Be60PFO4mUIeJLBMDVU_uyH0IeCiZ1dtNDFiDFSo,5463
8
+ ingestr/src/factory.py,sha256=x-Ym3uHMgzj_svUk7Lopn3Jj-IhcQLCuDqA_eUPFLAI,5582
9
9
  ingestr/src/filters.py,sha256=C-_TIVkF_cxZBgG-Run2Oyn0TAhJgA8IWXZ-OPY3uek,1136
10
10
  ingestr/src/loader.py,sha256=9NaWAyfkXdqAZSS-N72Iwo36Lbx4PyqIfaaH1dNdkFs,1712
11
11
  ingestr/src/partition.py,sha256=BrIP6wFJvyR7Nus_3ElnfxknUXeCipK_E_bB8kZowfc,969
12
12
  ingestr/src/resource.py,sha256=XG-sbBapFVEM7OhHQFQRTdTLlh-mHB-N4V1t8F8Tsww,543
13
- ingestr/src/sources.py,sha256=q9FJ3vRaeTjbL5sgau_GHPxbn5LfSI4t5TBwycVdwSc,79060
13
+ ingestr/src/sources.py,sha256=RitbAjFVnq1I7MsjbD7hrn6Akd_92P6OCEg--YHivDw,80770
14
14
  ingestr/src/table_definition.py,sha256=REbAbqdlmUMUuRh8nEQRreWjPVOQ5ZcfqGkScKdCrmk,390
15
15
  ingestr/src/time.py,sha256=H_Fk2J4ShXyUM-EMY7MqCLZQhlnZMZvO952bmZPc4yE,254
16
16
  ingestr/src/version.py,sha256=J_2xgZ0mKlvuHcjdKCx2nlioneLH0I47JiU_Slr_Nwc,189
@@ -35,6 +35,7 @@ ingestr/src/chess/helpers.py,sha256=v1HTImOMjAF7AzZUPDIuHu00e7ut0o5y1kWcVYo4QZw,
35
35
  ingestr/src/chess/settings.py,sha256=p0RlCGgtXUacPDEvZmwzSWmzX0Apj1riwfz-nrMK89k,158
36
36
  ingestr/src/collector/spinner.py,sha256=_ZUqF5MI43hVIULdjF5s5mrAZbhEFXaiWirQmrv3Yk4,1201
37
37
  ingestr/src/dynamodb/__init__.py,sha256=swhxkeYBbJ35jn1IghCtvYWT2BM33KynVCh_oR4z28A,2264
38
+ ingestr/src/elasticsearch/__init__.py,sha256=m-q93HgUmTwGDUwHOjHawstWL06TC3WIX3H05szybrY,2556
38
39
  ingestr/src/facebook_ads/__init__.py,sha256=reEpSr4BaKA1wO3qVgCH51gW-TgWkbJ_g24UIhJWbac,9286
39
40
  ingestr/src/facebook_ads/exceptions.py,sha256=4Nlbc0Mv3i5g-9AoyT-n1PIa8IDi3VCTfEAzholx4Wc,115
40
41
  ingestr/src/facebook_ads/helpers.py,sha256=ZLbNHiKer5lPb4g3_435XeBJr57Wv0o1KTyBA1mQ100,9068
@@ -57,7 +58,7 @@ ingestr/src/google_ads/metrics.py,sha256=tAqpBpm-8l95oPT9cBxMWaEoDTNHVXnqUphYDHW
57
58
  ingestr/src/google_ads/predicates.py,sha256=K4wTuqfmJ9ko1RKeHTBDfQO_mUADVyuRqtywBPP-72w,683
58
59
  ingestr/src/google_ads/reports.py,sha256=AVY1pPt5yaIFskQe1k5VW2Dhlux3bzewsHlDrdGEems,12686
59
60
  ingestr/src/google_analytics/__init__.py,sha256=8b9CBWJFrBpHVRl993Z7J01sKKbYyXEtngdfEUwqlfE,4343
60
- ingestr/src/google_analytics/helpers.py,sha256=bUTPp5C-k5wqq-ccEAn-asRH2CLbBS2SOs1v9wiRU6U,10087
61
+ ingestr/src/google_analytics/helpers.py,sha256=tM7h_yughca3l7tnS_2XGIBM37mVm-Uewv7tK7XHVbc,10111
61
62
  ingestr/src/google_sheets/README.md,sha256=wFQhvmGpRA38Ba2N_WIax6duyD4c7c_pwvvprRfQDnw,5470
62
63
  ingestr/src/google_sheets/__init__.py,sha256=CL0HfY74uxX8-ge0ucI0VhWMYZVAfoX7WRPBitRi-CI,6647
63
64
  ingestr/src/google_sheets/helpers/__init__.py,sha256=5hXZrZK8cMO3UOuL-s4OKOpdACdihQD0hYYlSEu-iQ8,35
@@ -87,8 +88,8 @@ ingestr/src/notion/helpers/client.py,sha256=QXuudkf5Zzff98HRsCqA1g1EZWIrnfn1falP
87
88
  ingestr/src/notion/helpers/database.py,sha256=gigPibTeVefP3lA-8w4aOwX67pj7RlciPk5koDs1ry8,2737
88
89
  ingestr/src/personio/__init__.py,sha256=sHYpoV-rg-kA1YsflctChis0hKcTrL6mka9O0CHV4zA,11638
89
90
  ingestr/src/personio/helpers.py,sha256=EKmBN0Lf4R0lc3yqqs7D-RjoZ75E8gPcctt59xwHxrY,2901
90
- ingestr/src/phantombuster/__init__.py,sha256=5XGwbtNYmRxL4lmcC0TmyXgljY_21DvDIdw9zaWmgvI,1757
91
- ingestr/src/phantombuster/client.py,sha256=WO87AGU3Fphd9DgWTkrSAfldLH1XxcPxvNVmNxQ_5eU,2785
91
+ ingestr/src/phantombuster/__init__.py,sha256=8AQTiA8fp1NT8TellQQqwBCl6vGvGwUBLif6LIzgAik,1786
92
+ ingestr/src/phantombuster/client.py,sha256=9zx58sFunXjUNh6jeEYLNfwNxGxX9odifwAmS0E9AaY,3018
92
93
  ingestr/src/pipedrive/__init__.py,sha256=iRrxeMwo8_83ptgGnTFTNHV1nYvIsFfg0a3XzugPYeI,6982
93
94
  ingestr/src/pipedrive/settings.py,sha256=q119Fy4C5Ip1rMoCILX2BkHV3bwiXC_dW58KIiDUzsY,708
94
95
  ingestr/src/pipedrive/typing.py,sha256=lEMXu4hhAA3XkhVSlBUa-juqyupisd3c-qSQKxFvzoE,69
@@ -127,8 +128,8 @@ ingestr/testdata/delete_insert_part2.csv,sha256=B_KUzpzbNdDY_n7wWop1mT2cz36TmayS
127
128
  ingestr/testdata/merge_expected.csv,sha256=DReHqWGnQMsf2PBv_Q2pfjsgvikYFnf1zYcQZ7ZqYN0,276
128
129
  ingestr/testdata/merge_part1.csv,sha256=Pw8Z9IDKcNU0qQHx1z6BUf4rF_-SxKGFOvymCt4OY9I,185
129
130
  ingestr/testdata/merge_part2.csv,sha256=T_GiWxA81SN63_tMOIuemcvboEFeAmbKc7xRXvL9esw,287
130
- ingestr-0.13.38.dist-info/METADATA,sha256=ZknSXuae-_lb60AWDACr731hTr0Q_bX_9sUds6rHM28,13575
131
- ingestr-0.13.38.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
132
- ingestr-0.13.38.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
133
- ingestr-0.13.38.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
134
- ingestr-0.13.38.dist-info/RECORD,,
131
+ ingestr-0.13.40.dist-info/METADATA,sha256=DV_PkyMFlK4isa37puXrTKAfFPb4oQ4_cKv-b1lojI4,13653
132
+ ingestr-0.13.40.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
133
+ ingestr-0.13.40.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
134
+ ingestr-0.13.40.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
135
+ ingestr-0.13.40.dist-info/RECORD,,