from django.db import transaction from django.contrib.contenttypes.models import ContentType from django.utils import timezone from typing import List, Dict, Any, Optional from concurrent.futures import ThreadPoolExecutor import logging from .models import VersionBranch, ChangeSet from .caching import VersionHistoryCache from .signals import get_current_branch logger = logging.getLogger('version_control') class BatchOperation: """ Handles batch operations for version control system. Provides efficient handling of multiple changes and updates. """ def __init__(self, max_workers: int = 4): self.max_workers = max_workers self.changes: List[Dict[str, Any]] = [] self.error_handler = self.default_error_handler def default_error_handler(self, error: Exception, item: Dict[str, Any]) -> None: """Default error handling for batch operations""" logger.error(f"Batch operation error: {error}, item: {item}") raise error def set_error_handler(self, handler) -> None: """Set custom error handler for batch operations""" self.error_handler = handler def add_change(self, obj: Any, data: Dict[str, Any], branch: Optional[VersionBranch] = None) -> None: """Add a change to the batch""" content_type = ContentType.objects.get_for_model(obj) self.changes.append({ 'content_type': content_type, 'object_id': obj.pk, 'data': data, 'branch': branch or get_current_branch() }) @transaction.atomic def process_change(self, change: Dict[str, Any]) -> ChangeSet: """Process a single change in the batch""" try: changeset = ChangeSet.objects.create( branch=change['branch'], content_type=change['content_type'], object_id=change['object_id'], data=change['data'], status='pending' ) # Apply the change changeset.apply() # Cache the result VersionHistoryCache.cache_change(changeset.to_dict()) return changeset except Exception as e: self.error_handler(e, change) raise def process_parallel(self) -> List[ChangeSet]: """Process changes in parallel using thread pool""" results = [] with ThreadPoolExecutor(max_workers=self.max_workers) as executor: future_to_change = { executor.submit(self.process_change, change): change for change in self.changes } for future in future_to_change: try: changeset = future.result() results.append(changeset) except Exception as e: change = future_to_change[future] self.error_handler(e, change) return results @transaction.atomic def process_sequential(self) -> List[ChangeSet]: """Process changes sequentially in a single transaction""" results = [] for change in self.changes: try: changeset = self.process_change(change) results.append(changeset) except Exception as e: self.error_handler(e, change) return results def commit(self, parallel: bool = False) -> List[ChangeSet]: """Commit all changes in the batch""" if not self.changes: return [] start_time = timezone.now() logger.info(f"Starting batch operation with {len(self.changes)} changes") try: results = self.process_parallel() if parallel else self.process_sequential() duration = (timezone.now() - start_time).total_seconds() logger.info( f"Batch operation completed: {len(results)} changes processed in {duration:.2f}s" ) return results finally: self.changes = [] # Clear the batch class BulkVersionControl: """ Handles bulk version control operations for collections of objects. """ def __init__(self, model_class, branch: Optional[VersionBranch] = None): self.model_class = model_class self.branch = branch or get_current_branch() self.content_type = ContentType.objects.get_for_model(model_class) self.batch = BatchOperation() def prepare_bulk_update(self, objects: List[Any], data: Dict[str, Any]) -> None: """Prepare bulk update for multiple objects""" for obj in objects: self.batch.add_change(obj, data, self.branch) def prepare_bulk_delete(self, objects: List[Any]) -> None: """Prepare bulk delete for multiple objects""" for obj in objects: self.batch.add_change(obj, {'action': 'delete'}, self.branch) def prepare_bulk_create(self, data_list: List[Dict[str, Any]]) -> None: """Prepare bulk create for multiple objects""" for data in data_list: # Create temporary object for content type temp_obj = self.model_class() self.batch.add_change(temp_obj, {'action': 'create', **data}, self.branch) def commit(self, parallel: bool = True) -> List[ChangeSet]: """Commit all prepared bulk operations""" return self.batch.commit(parallel=parallel) class VersionControlQueue: """ Queue system for handling version control operations. Allows for delayed processing and batching of changes. """ def __init__(self, batch_size: int = 100, auto_commit: bool = True): self.batch_size = batch_size self.auto_commit = auto_commit self.current_batch = BatchOperation() self._queued_count = 0 def queue_change(self, obj: Any, data: Dict[str, Any], branch: Optional[VersionBranch] = None) -> None: """Queue a change for processing""" self.current_batch.add_change(obj, data, branch) self._queued_count += 1 if self.auto_commit and self._queued_count >= self.batch_size: self.process_queue() def process_queue(self, parallel: bool = True) -> List[ChangeSet]: """Process all queued changes""" if not self._queued_count: return [] results = self.current_batch.commit(parallel=parallel) self._queued_count = 0 return results def batch_version_control(func): """ Decorator for batching version control operations within a function. """ def wrapper(*args, **kwargs): batch = BatchOperation() try: with transaction.atomic(): result = func(*args, batch=batch, **kwargs) if batch.changes: batch.commit() return result except Exception as e: logger.error(f"Batch operation failed: {e}") raise return wrapper