ingestr 0.9.5__py3-none-any.whl → 0.10.0rc0__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
@@ -1,19 +1,13 @@
1
- import hashlib
2
- import tempfile
3
1
  from datetime import datetime
4
2
  from enum import Enum
5
3
  from typing import Optional
6
4
 
7
- import dlt
8
- import humanize
9
5
  import typer
10
- from dlt.common.pipeline import LoadInfo
11
- from dlt.common.runtime.collector import Collector, LogCollector
6
+ from dlt.common.runtime.collector import Collector
12
7
  from rich.console import Console
13
8
  from rich.status import Status
14
9
  from typing_extensions import Annotated
15
10
 
16
- from ingestr.src.factory import SourceDestinationFactory
17
11
  from ingestr.src.telemetry.event import track
18
12
 
19
13
  app = typer.Typer(
@@ -181,6 +175,20 @@ def ingest(
181
175
  envvar="PRIMARY_KEY",
182
176
  ),
183
177
  ] = None, # type: ignore
178
+ partition_by: Annotated[
179
+ Optional[str],
180
+ typer.Option(
181
+ help="The partition key to be used for partitioning the destination table",
182
+ envvar="PARTITION_BY",
183
+ ),
184
+ ] = None, # type: ignore
185
+ cluster_by: Annotated[
186
+ Optional[str],
187
+ typer.Option(
188
+ help="The clustering key to be used for clustering the destination table, not every destination supports clustering.",
189
+ envvar="CLUSTER_BY",
190
+ ),
191
+ ] = None, # type: ignore
184
192
  yes: Annotated[
185
193
  Optional[bool],
186
194
  typer.Option(
@@ -252,6 +260,66 @@ def ingest(
252
260
  ),
253
261
  ] = 5, # type: ignore
254
262
  ):
263
+ import hashlib
264
+ import tempfile
265
+ from datetime import datetime
266
+
267
+ import dlt
268
+ import humanize
269
+ import typer
270
+ from dlt.common.destination import Destination
271
+ from dlt.common.pipeline import LoadInfo
272
+ from dlt.common.runtime.collector import Collector, LogCollector
273
+ from dlt.common.schema.typing import TColumnSchema
274
+
275
+ from ingestr.src.factory import SourceDestinationFactory
276
+ from ingestr.src.telemetry.event import track
277
+
278
+ def report_errors(run_info: LoadInfo):
279
+ for load_package in run_info.load_packages:
280
+ failed_jobs = load_package.jobs["failed_jobs"]
281
+ if len(failed_jobs) == 0:
282
+ continue
283
+
284
+ print()
285
+ print("[bold red]Failed jobs:[/bold red]")
286
+ print()
287
+ for job in failed_jobs:
288
+ print(f"[bold red] {job.job_file_info.job_id()}[/bold red]")
289
+ print(f" [bold yellow]Error:[/bold yellow] {job.failed_message}")
290
+
291
+ raise typer.Exit(1)
292
+
293
+ def validate_source_dest_tables(
294
+ source_table: str, dest_table: str
295
+ ) -> tuple[str, str]:
296
+ if not dest_table:
297
+ if len(source_table.split(".")) != 2:
298
+ print(
299
+ "[red]Table name must be in the format schema.table for source table when dest-table is not given.[/red]"
300
+ )
301
+ raise typer.Abort()
302
+
303
+ print()
304
+ print(
305
+ "[yellow]Destination table is not given, defaulting to the source table.[/yellow]"
306
+ )
307
+ dest_table = source_table
308
+ return (source_table, dest_table)
309
+
310
+ def validate_loader_file_format(
311
+ dlt_dest: Destination, loader_file_format: Optional[LoaderFileFormat]
312
+ ):
313
+ if (
314
+ loader_file_format
315
+ and loader_file_format.value
316
+ not in dlt_dest.capabilities().supported_loader_file_formats
317
+ ):
318
+ print(
319
+ f"[red]Loader file format {loader_file_format.value} is not supported by the destination.[/red]"
320
+ )
321
+ raise typer.Abort()
322
+
255
323
  track(
256
324
  "command_triggered",
257
325
  {
@@ -267,29 +335,23 @@ def ingest(
267
335
  dlt.config["schema.naming"] = schema_naming.value
268
336
 
269
337
  try:
270
- if not dest_table:
271
- if len(source_table.split(".")) != 2:
272
- print(
273
- "[red]Table name must be in the format schema.table for source table when dest-table is not given.[/red]"
274
- )
275
- raise typer.Abort()
276
-
277
- print()
278
- print(
279
- "[yellow]Destination table is not given, defaulting to the source table.[/yellow]"
280
- )
281
- dest_table = source_table
338
+ (source_table, dest_table) = validate_source_dest_tables(
339
+ source_table, dest_table
340
+ )
282
341
 
283
342
  factory = SourceDestinationFactory(source_uri, dest_uri)
284
343
  source = factory.get_source()
285
344
  destination = factory.get_destination()
286
345
 
346
+ column_hints: dict[str, TColumnSchema] = {}
287
347
  original_incremental_strategy = incremental_strategy
288
348
 
289
349
  merge_key = None
290
350
  if incremental_strategy == IncrementalStrategy.delete_insert:
291
351
  merge_key = incremental_key
292
352
  incremental_strategy = IncrementalStrategy.merge
353
+ if incremental_key:
354
+ column_hints[incremental_key] = {"merge_key": True}
293
355
 
294
356
  m = hashlib.sha256()
295
357
  m.update(dest_table.encode("utf-8"))
@@ -303,11 +365,31 @@ def ingest(
303
365
  pipelines_dir = tempfile.mkdtemp()
304
366
  is_pipelines_dir_temp = True
305
367
 
368
+ dlt_dest = destination.dlt_dest(uri=dest_uri)
369
+ validate_loader_file_format(dlt_dest, loader_file_format)
370
+
371
+ if partition_by:
372
+ if partition_by not in column_hints:
373
+ column_hints[partition_by] = {}
374
+
375
+ column_hints[partition_by]["partition"] = True
376
+
377
+ if cluster_by:
378
+ if cluster_by not in column_hints:
379
+ column_hints[cluster_by] = {}
380
+
381
+ column_hints[cluster_by]["cluster"] = True
382
+
383
+ if primary_key:
384
+ for key in primary_key:
385
+ if key not in column_hints:
386
+ column_hints[key] = {}
387
+
388
+ column_hints[key]["primary_key"] = True
389
+
306
390
  pipeline = dlt.pipeline(
307
391
  pipeline_name=m.hexdigest(),
308
- destination=destination.dlt_dest(
309
- uri=dest_uri,
310
- ),
392
+ destination=dlt_dest,
311
393
  progress=progressInstance,
312
394
  pipelines_dir=pipelines_dir,
313
395
  refresh="drop_resources" if full_refresh else None,
@@ -400,27 +482,17 @@ def ingest(
400
482
  loader_file_format=loader_file_format.value
401
483
  if loader_file_format is not None
402
484
  else None, # type: ignore
485
+ columns=column_hints,
403
486
  )
404
487
 
405
- for load_package in run_info.load_packages:
406
- failed_jobs = load_package.jobs["failed_jobs"]
407
- if len(failed_jobs) > 0:
408
- print()
409
- print("[bold red]Failed jobs:[/bold red]")
410
- print()
411
- for job in failed_jobs:
412
- print(f"[bold red] {job.job_file_info.job_id()}[/bold red]")
413
- print(f" [bold yellow]Error:[/bold yellow] {job.failed_message}")
414
-
415
- raise typer.Exit(1)
488
+ report_errors(run_info)
416
489
 
417
490
  destination.post_load()
418
491
 
419
492
  end_time = datetime.now()
420
493
  elapsedHuman = ""
421
- if run_info.started_at:
422
- elapsed = end_time - start_time
423
- elapsedHuman = f"in {humanize.precisedelta(elapsed)}"
494
+ elapsed = end_time - start_time
495
+ elapsedHuman = f"in {humanize.precisedelta(elapsed)}"
424
496
 
425
497
  # remove the pipelines_dir folder if it was created by ingestr
426
498
  if is_pipelines_dir_temp:
@@ -99,12 +99,12 @@ def gorgias_source(
99
99
  "description": "When the user was last updated.",
100
100
  },
101
101
  "meta": {
102
- "data_type": "complex",
102
+ "data_type": "json",
103
103
  "nullable": True,
104
104
  "description": "Meta information associated with the user.",
105
105
  },
106
106
  "data": {
107
- "data_type": "complex",
107
+ "data_type": "json",
108
108
  "nullable": True,
109
109
  "description": "Additional data associated with the user.",
110
110
  },
@@ -185,17 +185,17 @@ def gorgias_source(
185
185
  "description": "Indicates if the ticket was created by an agent",
186
186
  },
187
187
  "customer": {
188
- "data_type": "complex",
188
+ "data_type": "json",
189
189
  "nullable": False,
190
190
  "description": "The customer linked to the ticket.",
191
191
  },
192
192
  "assignee_user": {
193
- "data_type": "complex",
193
+ "data_type": "json",
194
194
  "nullable": True,
195
195
  "description": "User assigned to the ticket",
196
196
  },
197
197
  "assignee_team": {
198
- "data_type": "complex",
198
+ "data_type": "json",
199
199
  "nullable": True,
200
200
  "description": "Team assigned to the ticket",
201
201
  },
@@ -210,17 +210,17 @@ def gorgias_source(
210
210
  "description": "Excerpt of the ticket",
211
211
  },
212
212
  "integrations": {
213
- "data_type": "complex",
213
+ "data_type": "json",
214
214
  "nullable": False,
215
215
  "description": "Integration information related to the ticket",
216
216
  },
217
217
  "meta": {
218
- "data_type": "complex",
218
+ "data_type": "json",
219
219
  "nullable": True,
220
220
  "description": "Meta information related to the ticket",
221
221
  },
222
222
  "tags": {
223
- "data_type": "complex",
223
+ "data_type": "json",
224
224
  "nullable": False,
225
225
  "description": "Tags associated with the ticket",
226
226
  },
@@ -354,7 +354,7 @@ def gorgias_source(
354
354
  "description": "How the message has been received, or sent from Gorgias.",
355
355
  },
356
356
  "sender": {
357
- "data_type": "complex",
357
+ "data_type": "json",
358
358
  "nullable": False,
359
359
  "description": "The person who sent the message. It can be a user or a customer.",
360
360
  },
@@ -364,7 +364,7 @@ def gorgias_source(
364
364
  "description": "ID of the integration that either received or sent the message.",
365
365
  },
366
366
  "intents": {
367
- "data_type": "complex",
367
+ "data_type": "json",
368
368
  "nullable": True,
369
369
  "description": "",
370
370
  },
@@ -379,7 +379,7 @@ def gorgias_source(
379
379
  "description": "Whether the message was sent by your company to a customer, or the opposite.",
380
380
  },
381
381
  "receiver": {
382
- "data_type": "complex",
382
+ "data_type": "json",
383
383
  "nullable": True,
384
384
  "description": "The primary receiver of the message. It can be a user or a customer. Optional when the source type is 'internal-note'.",
385
385
  },
@@ -414,27 +414,27 @@ def gorgias_source(
414
414
  "description": "",
415
415
  },
416
416
  "headers": {
417
- "data_type": "complex",
417
+ "data_type": "json",
418
418
  "nullable": True,
419
419
  "description": "Headers of the message",
420
420
  },
421
421
  "attachments": {
422
- "data_type": "complex",
422
+ "data_type": "json",
423
423
  "nullable": True,
424
424
  "description": "A list of files attached to the message.",
425
425
  },
426
426
  "actions": {
427
- "data_type": "complex",
427
+ "data_type": "json",
428
428
  "nullable": True,
429
429
  "description": "A list of actions performed on the message.",
430
430
  },
431
431
  "macros": {
432
- "data_type": "complex",
432
+ "data_type": "json",
433
433
  "nullable": True,
434
434
  "description": "A list of macros",
435
435
  },
436
436
  "meta": {
437
- "data_type": "complex",
437
+ "data_type": "json",
438
438
  "nullable": True,
439
439
  "description": "Message metadata",
440
440
  },
@@ -526,7 +526,7 @@ def gorgias_source(
526
526
  "description": "ID of the customer linked to the survey.",
527
527
  },
528
528
  "meta": {
529
- "data_type": "complex",
529
+ "data_type": "json",
530
530
  "nullable": True,
531
531
  "description": "Meta information associated with the survey.",
532
532
  },
@@ -89,12 +89,12 @@ def shopify_source(
89
89
  "description": "An unsigned 64-bit integer that's used as a unique identifier for the product.",
90
90
  },
91
91
  "images": {
92
- "data_type": "complex",
92
+ "data_type": "json",
93
93
  "nullable": True,
94
94
  "description": "A list of product image objects, each one representing an image associated with the product.",
95
95
  },
96
96
  "options": {
97
- "data_type": "complex",
97
+ "data_type": "json",
98
98
  "nullable": True,
99
99
  "description": "The custom product properties. For example, Size, Color, and Material.",
100
100
  },
@@ -139,7 +139,7 @@ def shopify_source(
139
139
  "description": "The date and time (ISO 8601 format) when the product was last modified.",
140
140
  },
141
141
  "variants": {
142
- "data_type": "complex",
142
+ "data_type": "json",
143
143
  "nullable": True,
144
144
  "description": "An array of product variants, each representing a different version of the product.",
145
145
  },
@@ -191,7 +191,7 @@ def shopify_source(
191
191
  "description": "The ID of the app that created the order.",
192
192
  },
193
193
  "billing_address": {
194
- "data_type": "complex",
194
+ "data_type": "json",
195
195
  "nullable": True,
196
196
  "description": "The mailing address associated with the payment method.",
197
197
  },
@@ -226,7 +226,7 @@ def shopify_source(
226
226
  "description": "A unique value referencing the checkout associated with the order.",
227
227
  },
228
228
  "client_details": {
229
- "data_type": "complex",
229
+ "data_type": "json",
230
230
  "nullable": True,
231
231
  "description": "Information about the browser the customer used when placing the order.",
232
232
  },
@@ -236,7 +236,7 @@ def shopify_source(
236
236
  "description": "The date and time when the order was closed.",
237
237
  },
238
238
  "company": {
239
- "data_type": "complex",
239
+ "data_type": "json",
240
240
  "nullable": True,
241
241
  "description": "Information about the purchasing company for the order.",
242
242
  },
@@ -261,7 +261,7 @@ def shopify_source(
261
261
  "description": "The three-letter code (ISO 4217 format) for the shop currency.",
262
262
  },
263
263
  "current_total_additional_fees_set": {
264
- "data_type": "complex",
264
+ "data_type": "json",
265
265
  "nullable": True,
266
266
  "description": "The current total additional fees on the order in shop and presentment currencies.",
267
267
  },
@@ -271,12 +271,12 @@ def shopify_source(
271
271
  "description": "The current total discounts on the order in the shop currency.",
272
272
  },
273
273
  "current_total_discounts_set": {
274
- "data_type": "complex",
274
+ "data_type": "json",
275
275
  "nullable": True,
276
276
  "description": "The current total discounts on the order in shop and presentment currencies.",
277
277
  },
278
278
  "current_total_duties_set": {
279
- "data_type": "complex",
279
+ "data_type": "json",
280
280
  "nullable": True,
281
281
  "description": "The current total duties charged on the order in shop and presentment currencies.",
282
282
  },
@@ -286,7 +286,7 @@ def shopify_source(
286
286
  "description": "The current total price of the order in the shop currency.",
287
287
  },
288
288
  "current_total_price_set": {
289
- "data_type": "complex",
289
+ "data_type": "json",
290
290
  "nullable": True,
291
291
  "description": "The current total price of the order in shop and presentment currencies.",
292
292
  },
@@ -296,7 +296,7 @@ def shopify_source(
296
296
  "description": "The sum of prices for all line items after discounts and returns in the shop currency.",
297
297
  },
298
298
  "current_subtotal_price_set": {
299
- "data_type": "complex",
299
+ "data_type": "json",
300
300
  "nullable": True,
301
301
  "description": "The sum of the prices for all line items after discounts and returns in shop and presentment currencies.",
302
302
  },
@@ -306,12 +306,12 @@ def shopify_source(
306
306
  "description": "The sum of the prices for all tax lines applied to the order in the shop currency.",
307
307
  },
308
308
  "current_total_tax_set": {
309
- "data_type": "complex",
309
+ "data_type": "json",
310
310
  "nullable": True,
311
311
  "description": "The sum of the prices for all tax lines applied to the order in shop and presentment currencies.",
312
312
  },
313
313
  "customer": {
314
- "data_type": "complex",
314
+ "data_type": "json",
315
315
  "nullable": True,
316
316
  "description": "Information about the customer.",
317
317
  },
@@ -321,12 +321,12 @@ def shopify_source(
321
321
  "description": "The two or three-letter language code, optionally followed by a region modifier.",
322
322
  },
323
323
  "discount_applications": {
324
- "data_type": "complex",
324
+ "data_type": "json",
325
325
  "nullable": True,
326
326
  "description": "An ordered list of stacked discount applications.",
327
327
  },
328
328
  "discount_codes": {
329
- "data_type": "complex",
329
+ "data_type": "json",
330
330
  "nullable": True,
331
331
  "description": "A list of discounts applied to the order.",
332
332
  },
@@ -346,7 +346,7 @@ def shopify_source(
346
346
  "description": "The status of payments associated with the order.",
347
347
  },
348
348
  "fulfillments": {
349
- "data_type": "complex",
349
+ "data_type": "json",
350
350
  "nullable": True,
351
351
  "description": "An array of fulfillments associated with the order.",
352
352
  },
@@ -372,7 +372,7 @@ def shopify_source(
372
372
  "description": "The URL for the page where the buyer landed when they entered the shop.",
373
373
  },
374
374
  "line_items": {
375
- "data_type": "complex",
375
+ "data_type": "json",
376
376
  "nullable": True,
377
377
  "description": "A list of line item objects containing information about an item in the order.",
378
378
  },
@@ -397,7 +397,7 @@ def shopify_source(
397
397
  "description": "An optional note that a shop owner can attach to the order.",
398
398
  },
399
399
  "note_attributes": {
400
- "data_type": "complex",
400
+ "data_type": "json",
401
401
  "nullable": True,
402
402
  "description": "Extra information added to the order as key-value pairs.",
403
403
  },
@@ -412,22 +412,22 @@ def shopify_source(
412
412
  "description": "The order's position in the shop's count of orders, starting at 1001.",
413
413
  },
414
414
  "original_total_additional_fees_set": {
415
- "data_type": "complex",
415
+ "data_type": "json",
416
416
  "nullable": True,
417
417
  "description": "The original total additional fees on the order in shop and presentment currencies.",
418
418
  },
419
419
  "original_total_duties_set": {
420
- "data_type": "complex",
420
+ "data_type": "json",
421
421
  "nullable": True,
422
422
  "description": "The original total duties charged on the order in shop and presentment currencies.",
423
423
  },
424
424
  "payment_terms": {
425
- "data_type": "complex",
425
+ "data_type": "json",
426
426
  "nullable": True,
427
427
  "description": "The terms and conditions under which a payment should be processed.",
428
428
  },
429
429
  "payment_gateway_names": {
430
- "data_type": "complex",
430
+ "data_type": "json",
431
431
  "nullable": True,
432
432
  "description": "The list of payment gateways used for the order.",
433
433
  },
@@ -457,17 +457,17 @@ def shopify_source(
457
457
  "description": "The website where the customer clicked a link to the shop.",
458
458
  },
459
459
  "refunds": {
460
- "data_type": "complex",
460
+ "data_type": "json",
461
461
  "nullable": True,
462
462
  "description": "A list of refunds applied to the order.",
463
463
  },
464
464
  "shipping_address": {
465
- "data_type": "complex",
465
+ "data_type": "json",
466
466
  "nullable": True,
467
467
  "description": "The mailing address where the order will be shipped.",
468
468
  },
469
469
  "shipping_lines": {
470
- "data_type": "complex",
470
+ "data_type": "json",
471
471
  "nullable": True,
472
472
  "description": "An array detailing the shipping methods used.",
473
473
  },
@@ -492,7 +492,7 @@ def shopify_source(
492
492
  "description": "The price of the order in the shop currency after discounts but before shipping, duties, taxes, and tips.",
493
493
  },
494
494
  "subtotal_price_set": {
495
- "data_type": "complex",
495
+ "data_type": "json",
496
496
  "nullable": True,
497
497
  "description": "The subtotal of the order in shop and presentment currencies after discounts but before shipping, duties, taxes, and tips.",
498
498
  },
@@ -502,7 +502,7 @@ def shopify_source(
502
502
  "description": "Tags attached to the order, formatted as a string of comma-separated values.",
503
503
  },
504
504
  "tax_lines": {
505
- "data_type": "complex",
505
+ "data_type": "json",
506
506
  "nullable": True,
507
507
  "description": "An array of tax line objects detailing taxes applied to the order.",
508
508
  },
@@ -527,7 +527,7 @@ def shopify_source(
527
527
  "description": "The total discounts applied to the price of the order in the shop currency.",
528
528
  },
529
529
  "total_discounts_set": {
530
- "data_type": "complex",
530
+ "data_type": "json",
531
531
  "nullable": True,
532
532
  "description": "The total discounts applied to the price of the order in shop and presentment currencies.",
533
533
  },
@@ -537,7 +537,7 @@ def shopify_source(
537
537
  "description": "The sum of all line item prices in the shop currency.",
538
538
  },
539
539
  "total_line_items_price_set": {
540
- "data_type": "complex",
540
+ "data_type": "json",
541
541
  "nullable": True,
542
542
  "description": "The total of all line item prices in shop and presentment currencies.",
543
543
  },
@@ -552,12 +552,12 @@ def shopify_source(
552
552
  "description": "The sum of all line item prices, discounts, shipping, taxes, and tips in the shop currency.",
553
553
  },
554
554
  "total_price_set": {
555
- "data_type": "complex",
555
+ "data_type": "json",
556
556
  "nullable": True,
557
557
  "description": "The total price of the order in shop and presentment currencies.",
558
558
  },
559
559
  "total_shipping_price_set": {
560
- "data_type": "complex",
560
+ "data_type": "json",
561
561
  "nullable": True,
562
562
  "description": "The total shipping price of the order in shop and presentment currencies.",
563
563
  },
@@ -567,7 +567,7 @@ def shopify_source(
567
567
  "description": "The sum of the prices for all tax lines applied to the order in the shop currency.",
568
568
  },
569
569
  "total_tax_set": {
570
- "data_type": "complex",
570
+ "data_type": "json",
571
571
  "nullable": True,
572
572
  "description": "The sum of the prices for all tax lines applied to the order in shop and presentment currencies.",
573
573
  },
@@ -1669,27 +1669,27 @@ query discountNodes($after: String, $query: String, $first: Int) {
1669
1669
  "description": "A globally unique ID for the product.",
1670
1670
  },
1671
1671
  "availablePublicationsCount": {
1672
- "data_type": "complex",
1672
+ "data_type": "json",
1673
1673
  "nullable": False,
1674
1674
  "description": "The number of publications that a resource is published to",
1675
1675
  },
1676
1676
  "category": {
1677
- "data_type": "complex",
1677
+ "data_type": "json",
1678
1678
  "nullable": True,
1679
1679
  "description": "The category of the product from Shopify's Standard Product Taxonomy.",
1680
1680
  },
1681
1681
  "combinedListing": {
1682
- "data_type": "complex",
1682
+ "data_type": "json",
1683
1683
  "nullable": True,
1684
1684
  "description": "A special product type that combines separate products into a single product listing.",
1685
1685
  },
1686
1686
  "combinedListingRole": {
1687
- "data_type": "complex",
1687
+ "data_type": "json",
1688
1688
  "nullable": True,
1689
1689
  "description": "The role of the product in a combined listing.",
1690
1690
  },
1691
1691
  "compareAtPriceRange": {
1692
- "data_type": "complex",
1692
+ "data_type": "json",
1693
1693
  "nullable": True,
1694
1694
  "description": "The compare-at price range of the product in the shop's default currency.",
1695
1695
  },
@@ -1719,17 +1719,17 @@ query discountNodes($after: String, $query: String, $first: Int) {
1719
1719
  "description": "A unique, human-readable string of the product's title.",
1720
1720
  },
1721
1721
  "metafields": {
1722
- "data_type": "complex",
1722
+ "data_type": "json",
1723
1723
  "nullable": True,
1724
1724
  "description": "A list of custom fields associated with the product.",
1725
1725
  },
1726
1726
  "options": {
1727
- "data_type": "complex",
1727
+ "data_type": "json",
1728
1728
  "nullable": True,
1729
1729
  "description": "A list of product options, e.g., size, color.",
1730
1730
  },
1731
1731
  "priceRangeV2": {
1732
- "data_type": "complex",
1732
+ "data_type": "json",
1733
1733
  "nullable": False,
1734
1734
  "description": "The minimum and maximum prices of a product.",
1735
1735
  },
@@ -1784,12 +1784,12 @@ query discountNodes($after: String, $query: String, $first: Int) {
1784
1784
  "description": "The date and time when the product was last modified.",
1785
1785
  },
1786
1786
  "variantsFirst250": {
1787
- "data_type": "complex",
1787
+ "data_type": "json",
1788
1788
  "nullable": False,
1789
1789
  "description": "A list of variants associated with the product, first 250.",
1790
1790
  },
1791
1791
  "variantsCount": {
1792
- "data_type": "complex",
1792
+ "data_type": "json",
1793
1793
  "nullable": False,
1794
1794
  "description": "The number of variants associated with the product.",
1795
1795
  },
@@ -166,7 +166,7 @@ def slack_source(
166
166
  @dlt.resource(
167
167
  name="messages",
168
168
  primary_key=("channel", "ts"),
169
- columns={"blocks": {"data_type": "complex"}},
169
+ columns={"blocks": {"data_type": "json"}},
170
170
  write_disposition=write_disposition,
171
171
  )
172
172
  def messages_resource(
@@ -249,7 +249,7 @@ def slack_source(
249
249
  table_name=table_name,
250
250
  primary_key=("channel", "ts"),
251
251
  write_disposition=write_disposition,
252
- columns={"blocks": {"data_type": "complex"}},
252
+ columns={"blocks": {"data_type": "json"}},
253
253
  )(channel)
254
254
 
255
255
  yield messages_channel