Skip to content

Commit 90e0642

Browse files
committed
feat: load and export for hdfs, iceberg, delta lake, hudi ..
1 parent 5c49e1a commit 90e0642

13 files changed

Lines changed: 3250 additions & 546 deletions

File tree

data_juicer/core/data/load_strategy.py

Lines changed: 473 additions & 2 deletions
Large diffs are not rendered by default.

data_juicer/core/exporter.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def __init__(
6060
self.keep_stats_in_res_ds = keep_stats_in_res_ds
6161
self.keep_hashes_in_res_ds = keep_hashes_in_res_ds
6262
self.export_stats = export_stats
63+
self.export_extra_args = kwargs
64+
self.export_type = export_type
6365
self.suffix = self._get_suffix(export_path) if export_type is None else export_type
6466
support_dict = self._router()
6567
if self.suffix not in support_dict:
@@ -159,12 +161,14 @@ def _get_suffix(self, export_path):
159161
"""
160162
Get the suffix of export path and check if it's supported.
161163
162-
We only support ["jsonl", "json", "parquet"] for now.
164+
We only support ["jsonl", "json", "parquet", "iceberg"] for now.
163165
164166
:param export_path: the path to export datasets.
165167
:return: the suffix of export_path.
166168
"""
167169
suffix = export_path.split(".")[-1].lower()
170+
if self.export_type == "iceberg":
171+
suffix = "iceberg"
168172
return suffix
169173

170174
@staticmethod
@@ -250,14 +254,16 @@ def _export_impl(self, dataset, export_path, suffix, export_stats=True):
250254
feature_fields = set(dataset.features.keys())
251255
removed_fields = extra_fields.intersection(feature_fields)
252256
dataset = dataset.remove_columns(removed_fields)
253-
export_method = Exporter._router()[suffix]
257+
export_method = Exporter._router().get(suffix, Exporter.to_parquet)
254258
if self.export_shard_size <= 0:
255259
# export the whole dataset into one single file.
256260
logger.info("Export dataset into a single file...")
257261
export_kwargs = {"num_proc": self.num_proc if self.export_in_parallel else 1}
258262
# Add storage_options if available (for S3 export)
259263
if self.storage_options is not None:
260264
export_kwargs["storage_options"] = self.storage_options
265+
if suffix == "iceberg":
266+
export_kwargs["export_extra_args"] = self.export_extra_args
261267
export_method(dataset, export_path, **export_kwargs)
262268
self._encrypt_local_file(export_path)
263269
else:
@@ -317,6 +323,8 @@ def _export_impl(self, dataset, export_path, suffix, export_stats=True):
317323
# Add storage_options if available (for S3 export)
318324
if self.storage_options is not None:
319325
export_kwargs["storage_options"] = self.storage_options
326+
if suffix == "iceberg":
327+
export_kwargs["export_extra_args"] = self.export_extra_args
320328
pool.apply_async(
321329
export_method,
322330
args=(
@@ -449,6 +457,66 @@ def to_parquet(dataset, export_path, **kwargs):
449457
else:
450458
dataset.to_parquet(export_path)
451459

460+
@staticmethod
461+
def to_iceberg(dataset, export_path, **kwargs):
462+
"""
463+
Export method for iceberg target tables.
464+
Checks for table existence/connectivity. If check fails, safe fall-back to JSON.
465+
"""
466+
from pyiceberg.catalog import load_catalog
467+
from pyiceberg.exceptions import NoSuchTableError
468+
469+
export_extra_args = kwargs.get("export_extra_args", {})
470+
catalog_kwargs = export_extra_args.get("catalog_kwargs", {})
471+
table_identifier = export_extra_args.get("table_identifier", export_path)
472+
473+
use_iceberg = False
474+
475+
try:
476+
catalog = load_catalog(**catalog_kwargs)
477+
catalog.load_table(table_identifier)
478+
logger.info(f"Iceberg table {table_identifier} exists. Writing to Iceberg.")
479+
use_iceberg = True
480+
481+
except NoSuchTableError as e:
482+
logger.warning(
483+
f"Iceberg target unavailable ({e.__class__.__name__}). Fallback to exporting to {export_path}..."
484+
)
485+
# Get pyarrow schema from HF Dataset
486+
schema = dataset.features.arrow_schema
487+
logger.info(f"Creating new Iceberg table {table_identifier} with schema: {schema}")
488+
try:
489+
catalog.create_table(table_identifier, schema)
490+
use_iceberg = True
491+
except Exception as e:
492+
logger.error(f"Failed to create Iceberg table: {e}. Fallback to exporting to {export_path}...")
493+
except Exception as e:
494+
logger.error(f"Unexpected error checking Iceberg: {e}. Fallback to exporting to {export_path}...")
495+
496+
if use_iceberg:
497+
try:
498+
import daft
499+
500+
# convert huggingface dataset to daft dataframe
501+
df = daft.from_arrow(dataset.data.table)
502+
table = catalog.load_table(table_identifier)
503+
df.write_iceberg(table, mode="append")
504+
return
505+
except Exception as e:
506+
logger.error(f"Write to Iceberg failed during execution: {e}. Fallback to json...")
507+
508+
suffix = os.path.splitext(export_path)[-1].strip(".").lower()
509+
if not suffix:
510+
suffix = "jsonl"
511+
logger.warning(f"No suffix found in {export_path}, using default fallback: {suffix}")
512+
513+
logger.info(f"Falling back to file export. Format: [{suffix}], Path: [{export_path}]")
514+
515+
if suffix in ["json", "jsonl"]:
516+
return Exporter.to_jsonl(dataset, export_path, **kwargs)
517+
else:
518+
return Exporter.to_parquet(dataset, export_path, **kwargs)
519+
452520
# suffix to export method
453521
@staticmethod
454522
def _router():
@@ -461,4 +529,5 @@ def _router():
461529
"jsonl": Exporter.to_jsonl,
462530
"json": Exporter.to_json,
463531
"parquet": Exporter.to_parquet,
532+
"iceberg": Exporter.to_iceberg,
464533
}

0 commit comments

Comments
 (0)