# python/01-etl-pipeline-design.py"""ETL pipeline design patterns and data processing architecture"""fromtypingimportList,Dict,Any,Optional,Callable,Iterator,Unionfromdataclassesimportdataclass,asdictfromenumimportEnumimportasyncioimporttimeimportjsonimportcsvimportpandasaspdfromdatetimeimportdatetime,timedeltafrompathlibimportPathimportloggingfromabcimportABC,abstractmethod# Configure logginglogging.basicConfig(level=logging.INFO)logger=logging.getLogger(__name__)classPipelineStage(Enum):"""Pipeline stage enumeration"""EXTRACT="extract"TRANSFORM="transform"LOAD="load"VALIDATE="validate"CLEAN="clean"classDataSource(Enum):"""Data source enumeration"""FILE="file"DATABASE="database"API="api"STREAM="stream"CACHE="cache"@dataclassclassDataRecord:"""Data record definition"""id:strdata:Dict[str,Any]source:strtimestamp:datetimemetadata:Dict[str,Any]=Nonedefto_dict(self)->Dict[str,Any]:"""Convert to dictionary"""return{"id":self.id,"data":self.data,"source":self.source,"timestamp":self.timestamp.isoformat(),"metadata":self.metadataor{}}classDataExtractor(ABC):"""Abstract data extractor"""@abstractmethodasyncdefextract(self,source:str,**kwargs)->Iterator[DataRecord]:"""Extract data from source"""pass@abstractmethoddefget_schema(self)->Dict[str,Any]:"""Get data schema"""passclassFileExtractor(DataExtractor):"""File-based data extractor"""def__init__(self,file_format:str="json"):self.file_format=file_formatself.schema={}asyncdefextract(self,source:str,**kwargs)->Iterator[DataRecord]:"""Extract data from file"""file_path=Path(source)ifnotfile_path.exists():raiseFileNotFoundError(f"File not found: {source}")ifself.file_format=="json":asyncforrecordinself._extract_json(source):yieldrecordelifself.file_format=="csv":asyncforrecordinself._extract_csv(source):yieldrecordelse:raiseValueError(f"Unsupported file format: {self.file_format}")asyncdef_extract_json(self,source:str)->Iterator[DataRecord]:"""Extract from JSON file"""withopen(source,'r')asf:data=json.load(f)ifisinstance(data,list):fori,iteminenumerate(data):record=DataRecord(id=f"json_{i}",data=item,source=source,timestamp=datetime.utcnow())yieldrecordelse:record=DataRecord(id="json_0",data=data,source=source,timestamp=datetime.utcnow())yieldrecordasyncdef_extract_csv(self,source:str)->Iterator[DataRecord]:"""Extract from CSV file"""withopen(source,'r')asf:reader=csv.DictReader(f)fori,rowinenumerate(reader):record=DataRecord(id=f"csv_{i}",data=row,source=source,timestamp=datetime.utcnow())yieldrecorddefget_schema(self)->Dict[str,Any]:"""Get file schema"""return{"type":"file","format":self.file_format,"schema":self.schema}classDatabaseExtractor(DataExtractor):"""Database data extractor"""def__init__(self,connection_string:str):self.connection_string=connection_stringself.schema={}asyncdefextract(self,source:str,**kwargs)->Iterator[DataRecord]:"""Extract data from database"""query=kwargs.get("query",f"SELECT * FROM {source}")batch_size=kwargs.get("batch_size",1000)# Simulate database extractionforiinrange(batch_size):record=DataRecord(id=f"db_{i}",data={"id":i,"name":f"Record {i}","value":i*10},source=source,timestamp=datetime.utcnow())yieldrecorddefget_schema(self)->Dict[str,Any]:"""Get database schema"""return{"type":"database","connection":self.connection_string,"schema":self.schema}classDataTransformer(ABC):"""Abstract data transformer"""@abstractmethodasyncdeftransform(self,record:DataRecord)->DataRecord:"""Transform data record"""pass@abstractmethoddefget_transformation_rules(self)->Dict[str,Any]:"""Get transformation rules"""passclassFieldMapper(DataTransformer):"""Field mapping transformer"""def__init__(self,field_mappings:Dict[str,str]):self.field_mappings=field_mappingsasyncdeftransform(self,record:DataRecord)->DataRecord:"""Transform record by mapping fields"""transformed_data={}forold_field,new_fieldinself.field_mappings.items():ifold_fieldinrecord.data:transformed_data[new_field]=record.data[old_field]# Keep unmapped fieldsforfield,valueinrecord.data.items():iffieldnotinself.field_mappings:transformed_data[field]=valuereturnDataRecord(id=record.id,data=transformed_data,source=record.source,timestamp=record.timestamp,metadata=record.metadata)defget_transformation_rules(self)->Dict[str,Any]:"""Get transformation rules"""return{"type":"field_mapping","mappings":self.field_mappings}classDataValidator(DataTransformer):"""Data validation transformer"""def__init__(self,validation_rules:Dict[str,Callable]):self.validation_rules=validation_rulesasyncdeftransform(self,record:DataRecord)->DataRecord:"""Validate and transform record"""errors=[]forfield,validatorinself.validation_rules.items():iffieldinrecord.data:try:ifnotvalidator(record.data[field]):errors.append(f"Validation failed for field {field}")exceptExceptionase:errors.append(f"Validation error for field {field}: {e}")iferrors:record.metadata=record.metadataor{}record.metadata["validation_errors"]=errorsreturnrecorddefget_transformation_rules(self)->Dict[str,Any]:"""Get transformation rules"""return{"type":"validation","rules":list(self.validation_rules.keys())}classDataLoader(ABC):"""Abstract data loader"""@abstractmethodasyncdefload(self,records:List[DataRecord],destination:str)->bool:"""Load records to destination"""pass@abstractmethoddefget_loader_info(self)->Dict[str,Any]:"""Get loader information"""passclassFileLoader(DataLoader):"""File-based data loader"""def__init__(self,file_format:str="json"):self.file_format=file_formatasyncdefload(self,records:List[DataRecord],destination:str)->bool:"""Load records to file"""try:ifself.file_format=="json":awaitself._load_json(records,destination)elifself.file_format=="csv":awaitself._load_csv(records,destination)else:raiseValueError(f"Unsupported file format: {self.file_format}")returnTrueexceptExceptionase:logger.error(f"Failed to load data: {e}")returnFalseasyncdef_load_json(self,records:List[DataRecord],destination:str)->None:"""Load records to JSON file"""data=[record.to_dict()forrecordinrecords]withopen(destination,'w')asf:json.dump(data,f,indent=2)asyncdef_load_csv(self,records:List[DataRecord],destination:str)->None:"""Load records to CSV file"""ifnotrecords:returnfieldnames=set()forrecordinrecords:fieldnames.update(record.data.keys())withopen(destination,'w',newline='')asf:writer=csv.DictWriter(f,fieldnames=list(fieldnames))writer.writeheader()forrecordinrecords:writer.writerow(record.data)defget_loader_info(self)->Dict[str,Any]:"""Get loader information"""return{"type":"file","format":self.file_format}classDatabaseLoader(DataLoader):"""Database data loader"""def__init__(self,connection_string:str):self.connection_string=connection_stringasyncdefload(self,records:List[DataRecord],destination:str)->bool:"""Load records to database"""try:# Simulate database loadingforrecordinrecords:# In real implementation, this would insert into databaseawaitasyncio.sleep(0.001)logger.info(f"Loaded {len(records)} records to {destination}")returnTrueexceptExceptionase:logger.error(f"Failed to load to database: {e}")returnFalsedefget_loader_info(self)->Dict[str,Any]:"""Get loader information"""return{"type":"database","connection":self.connection_string}classETLPipeline:"""ETL pipeline orchestrator"""def__init__(self,name:str):self.name=nameself.extractors:List[DataExtractor]=[]self.transformers:List[DataTransformer]=[]self.loaders:List[DataLoader]=[]self.metrics:Dict[str,Any]={}self.errors:List[str]=[]defadd_extractor(self,extractor:DataExtractor)->None:"""Add data extractor"""self.extractors.append(extractor)defadd_transformer(self,transformer:DataTransformer)->None:"""Add data transformer"""self.transformers.append(transformer)defadd_loader(self,loader:DataLoader)->None:"""Add data loader"""self.loaders.append(loader)asyncdefrun(self,source:str,destination:str,**kwargs)->bool:"""Run ETL pipeline"""start_time=time.time()self.metrics={"start_time":start_time,"records_processed":0,"records_failed":0,"extraction_time":0,"transformation_time":0,"loading_time":0}try:# Extract dataextraction_start=time.time()records=[]forextractorinself.extractors:asyncforrecordinextractor.extract(source,**kwargs):records.append(record)self.metrics["extraction_time"]=time.time()-extraction_startself.metrics["records_processed"]=len(records)# Transform datatransformation_start=time.time()transformed_records=[]forrecordinrecords:transformed_record=recordfortransformerinself.transformers:transformed_record=awaittransformer.transform(transformed_record)transformed_records.append(transformed_record)self.metrics["transformation_time"]=time.time()-transformation_start# Load dataloading_start=time.time()forloaderinself.loaders:success=awaitloader.load(transformed_records,destination)ifnotsuccess:self.errors.append(f"Failed to load data with {loader.__class__.__name__}")self.metrics["loading_time"]=time.time()-loading_startself.metrics["total_time"]=time.time()-start_timereturnlen(self.errors)==0exceptExceptionase:self.errors.append(f"Pipeline error: {e}")logger.error(f"Pipeline {self.name} failed: {e}")returnFalsedefget_metrics(self)->Dict[str,Any]:"""Get pipeline metrics"""returnself.metrics.copy()defget_errors(self)->List[str]:"""Get pipeline errors"""returnself.errors.copy()# Usage examplesasyncdefexample_etl_pipeline():"""Example ETL pipeline usage"""# Create pipelinepipeline=ETLPipeline("user_data_pipeline")# Add extractorfile_extractor=FileExtractor(file_format="json")pipeline.add_extractor(file_extractor)# Add transformersfield_mapper=FieldMapper({"user_id":"id","user_name":"name","user_email":"email"})pipeline.add_transformer(field_mapper)# Add validatorsdefvalidate_email(email:str)->bool:return"@"inemailand"."inemaildefvalidate_name(name:str)->bool:returnlen(name)>0validator=DataValidator({"email":validate_email,"name":validate_name})pipeline.add_transformer(validator)# Add loaderfile_loader=FileLoader(file_format="json")pipeline.add_loader(file_loader)# Run pipelinesuccess=awaitpipeline.run("input.json","output.json")print(f"Pipeline success: {success}")# Get metricsmetrics=pipeline.get_metrics()print(f"Pipeline metrics: {metrics}")# Get errorserrors=pipeline.get_errors()iferrors:print(f"Pipeline errors: {errors}")
# python/02-stream-processing.py"""Stream processing patterns for real-time data processing"""fromtypingimportList,Dict,Any,Optional,Callable,AsyncIteratorfromdataclassesimportdataclass,asdictfromenumimportEnumimportasyncioimporttimeimportjsonfromdatetimeimportdatetime,timedeltafromcollectionsimportdequeimportlogginglogger=logging.getLogger(__name__)classStreamEvent:"""Stream event definition"""def__init__(self,event_type:str,data:Dict[str,Any],timestamp:datetime=None):self.event_type=event_typeself.data=dataself.timestamp=timestampordatetime.utcnow()self.id=f"{event_type}_{int(self.timestamp.timestamp()*1000)}"defto_dict(self)->Dict[str,Any]:"""Convert to dictionary"""return{"id":self.id,"event_type":self.event_type,"data":self.data,"timestamp":self.timestamp.isoformat()}classStreamProcessor:"""Stream processor for real-time data"""def__init__(self,name:str,buffer_size:int=1000):self.name=nameself.buffer_size=buffer_sizeself.buffer:deque=deque(maxlen=buffer_size)self.processors:List[Callable]=[]self.metrics:Dict[str,Any]={"events_processed":0,"events_failed":0,"processing_time":0,"buffer_utilization":0}self.running=Falsedefadd_processor(self,processor:Callable)->None:"""Add event processor"""self.processors.append(processor)asyncdefprocess_event(self,event:StreamEvent)->bool:"""Process single event"""try:start_time=time.time()# Add to bufferself.buffer.append(event)# Process eventforprocessorinself.processors:ifasyncio.iscoroutinefunction(processor):awaitprocessor(event)else:processor(event)# Update metricsprocessing_time=time.time()-start_timeself.metrics["events_processed"]+=1self.metrics["processing_time"]+=processing_timeself.metrics["buffer_utilization"]=len(self.buffer)/self.buffer_sizereturnTrueexceptExceptionase:self.metrics["events_failed"]+=1logger.error(f"Failed to process event {event.id}: {e}")returnFalseasyncdefprocess_stream(self,stream:AsyncIterator[StreamEvent])->None:"""Process stream of events"""self.running=Trueasyncforeventinstream:ifnotself.running:breakawaitself.process_event(event)defstop(self)->None:"""Stop stream processing"""self.running=Falsedefget_metrics(self)->Dict[str,Any]:"""Get processing metrics"""returnself.metrics.copy()defget_buffer_status(self)->Dict[str,Any]:"""Get buffer status"""return{"buffer_size":len(self.buffer),"max_buffer_size":self.buffer_size,"utilization":len(self.buffer)/self.buffer_size,"oldest_event":self.buffer[0].timestampifself.bufferelseNone,"newest_event":self.buffer[-1].timestampifself.bufferelseNone}classWindowProcessor:"""Window-based stream processor"""def__init__(self,window_size:int,window_type:str="time"):self.window_size=window_sizeself.window_type=window_typeself.windows:List[Dict[str,Any]]=[]self.current_window:Dict[str,Any]=Noneself.window_start=Nonedefadd_event(self,event:StreamEvent)->Optional[Dict[str,Any]]:"""Add event to window"""ifself.window_type=="time":returnself._add_to_time_window(event)elifself.window_type=="count":returnself._add_to_count_window(event)else:raiseValueError(f"Unsupported window type: {self.window_type}")def_add_to_time_window(self,event:StreamEvent)->Optional[Dict[str,Any]]:"""Add event to time window"""ifself.window_startisNone:self.window_start=event.timestampself.current_window={"start_time":self.window_start,"end_time":self.window_start+timedelta(seconds=self.window_size),"events":[]}# Check if event is within current windowifevent.timestamp<self.current_window["end_time"]:self.current_window["events"].append(event)returnNone# Window is complete, start new windowcompleted_window=self.current_window.copy()self.windows.append(completed_window)self.window_start=event.timestampself.current_window={"start_time":self.window_start,"end_time":self.window_start+timedelta(seconds=self.window_size),"events":[event]}returncompleted_windowdef_add_to_count_window(self,event:StreamEvent)->Optional[Dict[str,Any]]:"""Add event to count window"""ifself.current_windowisNone:self.current_window={"start_time":event.timestamp,"events":[]}self.current_window["events"].append(event)iflen(self.current_window["events"])>=self.window_size:completed_window=self.current_window.copy()self.windows.append(completed_window)self.current_window=Nonereturncompleted_windowreturnNonedefget_window_stats(self)->Dict[str,Any]:"""Get window statistics"""ifnotself.windows:return{"total_windows":0,"avg_events_per_window":0}total_events=sum(len(window["events"])forwindowinself.windows)return{"total_windows":len(self.windows),"total_events":total_events,"avg_events_per_window":total_events/len(self.windows),"window_size":self.window_size,"window_type":self.window_type}classStreamAggregator:"""Stream aggregator for real-time analytics"""def__init__(self):self.aggregations:Dict[str,Any]={}self.counts:Dict[str,int]={}self.sums:Dict[str,float]={}self.mins:Dict[str,float]={}self.maxs:Dict[str,float]={}self.avgs:Dict[str,List[float]]={}defadd_event(self,event:StreamEvent)->None:"""Add event to aggregations"""event_type=event.event_type# Update countsself.counts[event_type]=self.counts.get(event_type,0)+1# Update numeric aggregationsforfield,valueinevent.data.items():ifisinstance(value,(int,float)):key=f"{event_type}.{field}"# Sumself.sums[key]=self.sums.get(key,0)+value# Min/Maxifkeynotinself.mins:self.mins[key]=valueself.maxs[key]=valueelse:self.mins[key]=min(self.mins[key],value)self.maxs[key]=max(self.maxs[key],value)# Averageifkeynotinself.avgs:self.avgs[key]=[]self.avgs[key].append(value)defget_aggregations(self)->Dict[str,Any]:"""Get current aggregations"""aggregations={}forevent_typeinself.counts.keys():aggregations[event_type]={"count":self.counts[event_type],"fields":{}}forfieldinself.avgs.keys():iffield.startswith(f"{event_type}."):field_name=field.split(".",1)[1]aggregations[event_type]["fields"][field_name]={"sum":self.sums.get(field,0),"min":self.mins.get(field,0),"max":self.maxs.get(field,0),"avg":sum(self.avgs[field])/len(self.avgs[field])ifself.avgs[field]else0}returnaggregationsdefreset(self)->None:"""Reset all aggregations"""self.aggregations.clear()self.counts.clear()self.sums.clear()self.mins.clear()self.maxs.clear()self.avgs.clear()# Usage examplesasyncdefexample_stream_processing():"""Example stream processing usage"""# Create stream processorprocessor=StreamProcessor("event_processor",buffer_size=100)# Add processorsdeflog_processor(event:StreamEvent):print(f"Processing event: {event.event_type}")defmetrics_processor(event:StreamEvent):print(f"Event metrics: {event.data}")processor.add_processor(log_processor)processor.add_processor(metrics_processor)# Create window processorwindow_processor=WindowProcessor(window_size=5,window_type="count")# Create aggregatoraggregator=StreamAggregator()# Simulate stream processingasyncdefgenerate_events():"""Generate sample events"""foriinrange(20):event=StreamEvent(event_type="user_action",data={"user_id":f"user_{i%5}","action":"click","value":i*10})yieldeventawaitasyncio.sleep(0.1)# Process eventsasyncforeventingenerate_events():awaitprocessor.process_event(event)# Add to window processorcompleted_window=window_processor.add_event(event)ifcompleted_window:print(f"Completed window: {len(completed_window['events'])} events")# Add to aggregatoraggregator.add_event(event)# Get metricsmetrics=processor.get_metrics()print(f"Processor metrics: {metrics}")# Get window statswindow_stats=window_processor.get_window_stats()print(f"Window stats: {window_stats}")# Get aggregationsaggregations=aggregator.get_aggregations()print(f"Aggregations: {aggregations}")
# python/03-batch-processing.py"""Batch processing patterns for large-scale data processing"""fromtypingimportList,Dict,Any,Optional,Callable,Iteratorfromdataclassesimportdataclass,asdictfromenumimportEnumimportasyncioimporttimeimportjsonfromdatetimeimportdatetime,timedeltafrompathlibimportPathimportloggingfromconcurrent.futuresimportThreadPoolExecutor,ProcessPoolExecutorimportmultiprocessinglogger=logging.getLogger(__name__)classBatchProcessor:"""Batch processor for large-scale data processing"""def__init__(self,batch_size:int=1000,max_workers:int=None):self.batch_size=batch_sizeself.max_workers=max_workersormultiprocessing.cpu_count()self.metrics:Dict[str,Any]={"batches_processed":0,"records_processed":0,"processing_time":0,"errors":0}asyncdefprocess_batches(self,data_source:Iterator[Dict[str,Any]],processor:Callable)->List[Any]:"""Process data in batches"""results=[]batch=[]start_time=time.time()forrecordindata_source:batch.append(record)iflen(batch)>=self.batch_size:try:result=awaitself._process_batch(batch,processor)results.extend(result)self.metrics["batches_processed"]+=1self.metrics["records_processed"]+=len(batch)exceptExceptionase:logger.error(f"Batch processing error: {e}")self.metrics["errors"]+=1batch=[]# Process remaining recordsifbatch:try:result=awaitself._process_batch(batch,processor)results.extend(result)self.metrics["batches_processed"]+=1self.metrics["records_processed"]+=len(batch)exceptExceptionase:logger.error(f"Final batch processing error: {e}")self.metrics["errors"]+=1self.metrics["processing_time"]=time.time()-start_timereturnresultsasyncdef_process_batch(self,batch:List[Dict[str,Any]],processor:Callable)->List[Any]:"""Process single batch"""ifasyncio.iscoroutinefunction(processor):returnawaitprocessor(batch)else:returnprocessor(batch)defget_metrics(self)->Dict[str,Any]:"""Get processing metrics"""returnself.metrics.copy()classParallelProcessor:"""Parallel processor for CPU-intensive tasks"""def__init__(self,max_workers:int=None):self.max_workers=max_workersormultiprocessing.cpu_count()self.thread_pool=ThreadPoolExecutor(max_workers=self.max_workers)self.process_pool=ProcessPoolExecutor(max_workers=self.max_workers)asyncdefprocess_parallel(self,data:List[Any],processor:Callable,use_processes:bool=False)->List[Any]:"""Process data in parallel"""ifuse_processes:# Use process pool for CPU-intensive tasksloop=asyncio.get_event_loop()results=awaitloop.run_in_executor(self.process_pool,self._process_batch,data,processor)else:# Use thread pool for I/O-intensive tasksloop=asyncio.get_event_loop()results=awaitloop.run_in_executor(self.thread_pool,self._process_batch,data,processor)returnresultsdef_process_batch(self,data:List[Any],processor:Callable)->List[Any]:"""Process batch of data"""return[processor(item)foritemindata]defclose(self)->None:"""Close thread and process pools"""self.thread_pool.shutdown(wait=True)self.process_pool.shutdown(wait=True)classDataPartitioner:"""Data partitioner for distributed processing"""def__init__(self,num_partitions:int=4):self.num_partitions=num_partitionsdefpartition_data(self,data:List[Any],partition_key:Callable=None)->List[List[Any]]:"""Partition data into multiple chunks"""ifpartition_keyisNone:# Simple round-robin partitioningpartitions=[[]for_inrange(self.num_partitions)]fori,iteminenumerate(data):partitions[i%self.num_partitions].append(item)else:# Key-based partitioningpartitions=[[]for_inrange(self.num_partitions)]foritemindata:key=partition_key(item)partition_index=hash(key)%self.num_partitionspartitions[partition_index].append(item)returnpartitionsdefget_partition_info(self,partitions:List[List[Any]])->Dict[str,Any]:"""Get partition information"""return{"num_partitions":len(partitions),"partition_sizes":[len(partition)forpartitioninpartitions],"total_items":sum(len(partition)forpartitioninpartitions),"avg_partition_size":sum(len(partition)forpartitioninpartitions)/len(partitions)}classDataMerger:"""Data merger for combining processed results"""def__init__(self,merge_strategy:str="append"):self.merge_strategy=merge_strategydefmerge_results(self,results:List[List[Any]])->List[Any]:"""Merge results from multiple partitions"""ifself.merge_strategy=="append":merged=[]forresultinresults:merged.extend(result)returnmergedelifself.merge_strategy=="concat":return[itemforresultinresultsforiteminresult]else:raiseValueError(f"Unsupported merge strategy: {self.merge_strategy}")defmerge_with_deduplication(self,results:List[List[Any]],key_func:Callable=None)->List[Any]:"""Merge results with deduplication"""ifkey_funcisNone:# Simple deduplicationseen=set()merged=[]forresultinresults:foriteminresult:ifitemnotinseen:seen.add(item)merged.append(item)returnmergedelse:# Key-based deduplicationseen_keys=set()merged=[]forresultinresults:foriteminresult:key=key_func(item)ifkeynotinseen_keys:seen_keys.add(key)merged.append(item)returnmerged# Usage examplesasyncdefexample_batch_processing():"""Example batch processing usage"""# Create batch processorbatch_processor=BatchProcessor(batch_size=100)# Create parallel processorparallel_processor=ParallelProcessor(max_workers=4)# Create data partitionerpartitioner=DataPartitioner(num_partitions=4)# Create data mergermerger=DataMerger(merge_strategy="append")# Sample datadata=[{"id":i,"value":i*10}foriinrange(1000)]# Partition datapartitions=partitioner.partition_data(data)partition_info=partitioner.get_partition_info(partitions)print(f"Partition info: {partition_info}")# Process partitions in parallelasyncdefprocess_partition(partition:List[Dict[str,Any]])->List[Dict[str,Any]]:"""Process single partition"""results=[]foriteminpartition:# Simulate processingprocessed_item={"id":item["id"],"value":item["value"],"processed":True,"timestamp":datetime.utcnow().isoformat()}results.append(processed_item)returnresults# Process all partitionspartition_results=[]forpartitioninpartitions:result=awaitparallel_processor.process_parallel(partition,process_partition,use_processes=False)partition_results.append(result)# Merge resultsmerged_results=merger.merge_results(partition_results)print(f"Merged {len(merged_results)} results")# Get batch processor metricsmetrics=batch_processor.get_metrics()print(f"Batch processor metrics: {metrics}")# Close parallel processorparallel_processor.close()