@@ -612,6 +612,109 @@ def is_empty(self) -> bool:
612612 return len (self ._batch ) == 0
613613
614614
615+ # Data Export Classes
616+ class DataExporter :
617+ """Utility for exporting API response data to JSON/CSV formats."""
618+
619+ def __init__ (self ) -> None :
620+ """Initialize data exporter."""
621+ pass
622+
623+ def _flatten_response (self , data : Any , prefix : str = "" ) -> dict [str , Any ]:
624+ """Flatten nested response data for CSV export."""
625+ flattened = {}
626+
627+ if isinstance (data , dict ):
628+ for key , value in data .items ():
629+ new_key = f"{ prefix } .{ key } " if prefix else key
630+ if isinstance (value , (dict , list )):
631+ flattened .update (self ._flatten_response (value , new_key ))
632+ else :
633+ flattened [new_key ] = value
634+ elif isinstance (data , list ):
635+ # For lists, create indexed keys
636+ for i , item in enumerate (data ):
637+ new_key = f"{ prefix } [{ i } ]" if prefix else f"[{ i } ]"
638+ if isinstance (item , (dict , list )):
639+ flattened .update (self ._flatten_response (item , new_key ))
640+ else :
641+ flattened [new_key ] = item
642+ else :
643+ flattened [prefix ] = data
644+
645+ return flattened
646+
647+ def export_json (self , data : Any , file_path : str | None = None , indent : int = 2 ) -> str | None :
648+ """Export data to JSON format.
649+
650+ Args:
651+ data: Data to export
652+ file_path: Optional file path to save to
653+ indent: JSON indentation level
654+
655+ Returns:
656+ JSON string if no file_path provided, None otherwise
657+ """
658+ import json
659+
660+ json_str = json .dumps (data , indent = indent , default = str )
661+
662+ if file_path :
663+ with open (file_path , 'w' , encoding = 'utf-8' ) as f :
664+ f .write (json_str )
665+ return None
666+
667+ return json_str
668+
669+ def export_csv (self , data : Any , file_path : str | None = None , headers : list [str ] | None = None ) -> str | None :
670+ """Export data to CSV format.
671+
672+ Args:
673+ data: Data to export (list of dicts or single dict)
674+ file_path: Optional file path to save to
675+ headers: Optional custom headers
676+
677+ Returns:
678+ CSV string if no file_path provided, None otherwise
679+ """
680+ import csv
681+ import io
682+
683+ # Ensure data is a list
684+ if not isinstance (data , list ):
685+ data = [data ]
686+
687+ # Flatten each item in the data
688+ flattened_data = [self ._flatten_response (item ) for item in data ]
689+
690+ # Determine headers
691+ if not headers :
692+ all_keys = set ()
693+ for item in flattened_data :
694+ all_keys .update (item .keys ())
695+ headers = sorted (all_keys )
696+
697+ # Create CSV content
698+ output = io .StringIO () if not file_path else open (file_path , 'w' , newline = '' , encoding = 'utf-8' )
699+
700+ try :
701+ writer = csv .DictWriter (output , fieldnames = headers )
702+ writer .writeheader ()
703+
704+ for item in flattened_data :
705+ # Fill missing keys with empty strings
706+ row = {header : item .get (header , '' ) for header in headers }
707+ writer .writerow (row )
708+
709+ if not file_path :
710+ return output .getvalue ()
711+ return None
712+
713+ finally :
714+ if file_path :
715+ output .close ()
716+
717+
615718# API Key Validation Functions
616719def validate_api_key (api_key : str | None ) -> bool :
617720 """Validate an API key format.
0 commit comments