@@ -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