From cd735ef028fa5f7067bd0a19e10e3c902ff43b59 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Thu, 9 Apr 2026 17:27:59 +0200 Subject: [PATCH] another gcal try without chunking --- eventplanner_gcal/google_sync.py | 103 +++++++++---------------------- 1 file changed, 29 insertions(+), 74 deletions(-) diff --git a/eventplanner_gcal/google_sync.py b/eventplanner_gcal/google_sync.py index 59f6487..063b04f 100644 --- a/eventplanner_gcal/google_sync.py +++ b/eventplanner_gcal/google_sync.py @@ -79,13 +79,6 @@ def create_gcal_service_object(): return None -def _invalidate_service_on_error(exc): - """Reset the cached service object so the next call retries credential loading.""" - global _service_object - logger.warning(f"Invalidating cached GCal service due to error: {exc}") - _service_object = None - - def get_service_object(): """Get or create the Google Calendar service object.""" global _service_object @@ -204,24 +197,29 @@ def build_gcal_event(event, timezone="Europe/Berlin"): # ------------------------------ Callback Functions ------------------------------------------------ -def on_gcal_event_created(request_id, response, exception=None): - """Callback function for created events to enter new gcal id in the mapping table.""" - if exception is not None: - logger.error(f"Error creating GCal event: {exception}") - return # Don't raise — let the batch continue processing other events - +def _save_gcal_mapping_from_response(response): + """Save a GCal mapping from an insert response.""" google_id = response["id"] django_id = response["extendedProperties"]["private"]["blechreizID"] try: event = Event.objects.get(pk=django_id) - mapping = GCalMapping(gcal_id=google_id, event=event) - mapping.save() + GCalMapping.objects.update_or_create( + event=event, defaults={"gcal_id": google_id} + ) logger.info(f"Created mapping: GCal {google_id} <-> Event {django_id}") except Event.DoesNotExist: logger.error(f"Event {django_id} not found when creating GCal mapping") +def on_gcal_event_created(request_id, response, exception=None): + """Callback function for batch delete_all — kept for backwards compat.""" + if exception is not None: + logger.error(f"Error creating GCal event: {exception}") + return + _save_gcal_mapping_from_response(response) + + # ------------------------------ GCal Api Calls ------------------------------------------------- @@ -328,51 +326,12 @@ def delete_all_gcal_events(service=None): batch.execute() except Exception as e: logger.error(f"Error deleting GCal events: {e}") - status = getattr(e, 'status_code', None) or getattr(e, 'resp', {}).get('status') - if str(status) in ('401', '403'): - _invalidate_service_on_error(e) GCalMapping.objects.all().delete() return count -def _execute_in_chunks(service, request_callback_pairs, chunk_size=30, delay=12): - """ - Execute API requests in small batches with a sleep between chunks. - - Google Calendar API allows 500 requests per 100 seconds per user (~5 req/s). - Default: 30 requests per chunk, 12 s sleep → ~2.5 req/s average. - - request_callback_pairs: list of (request, callback_or_None) - """ - total = len(request_callback_pairs) - for i in range(0, total, chunk_size): - chunk = request_callback_pairs[i : i + chunk_size] - batch = service.new_batch_http_request() - for req, cb in chunk: - if cb is not None: - batch.add(req, callback=cb) - else: - batch.add(req) - try: - batch.execute() - except Exception as e: - logger.error(f"Error executing batch chunk {i // chunk_size + 1}: {e}") - status = getattr(e, "status_code", None) or getattr(e, "resp", {}).get("status") - if str(status) in ("401", "403"): - _invalidate_service_on_error(e) - return # auth broken, no point continuing - - if i + chunk_size < total: - logger.info( - f"Chunk {i // chunk_size + 1} done " - f"({min(i + chunk_size, total)}/{total}), " - f"sleeping {delay}s to stay within rate limits..." - ) - time.sleep(delay) - - def sync_from_local_to_google(service=None): """ Creates a google event for each local event (if it does not exist yet) and @@ -425,13 +384,13 @@ def sync_from_local_to_google(service=None): if django_id not in local_events_django_id } - # --- Deletes (usually few, single batch is fine) --- - if events_to_delete_google_id: - delete_pairs = [ - (service.events().delete(calendarId="primary", eventId=gcal_id), None) - for gcal_id in events_to_delete_google_id - ] - _execute_in_chunks(service, delete_pairs) + # --- Deletes: one by one --- + for gcal_id in events_to_delete_google_id: + try: + service.events().delete(calendarId="primary", eventId=gcal_id).execute() + GCalMapping.objects.filter(gcal_id=gcal_id).delete() + except Exception as e: + logger.error(f"Failed to delete GCal event {gcal_id}: {e}") # --- Creates: future events first (soonest upcoming), then past events --- today = datetime.date.today() @@ -445,23 +404,20 @@ def sync_from_local_to_google(service=None): .order_by("-date") .values_list("pk", flat=True) ) - ordered_create_ids = future_ids + past_ids + ordered_create_ids = future_ids # + past_ids - create_pairs = [] for event_django_id in ordered_create_ids: try: event = Event.objects.get(pk=event_django_id) - create_pairs.append( - (create_gcal_event_request(service, event), on_gcal_event_created) - ) + request = create_gcal_event_request(service, event) + response = request.execute() + _save_gcal_mapping_from_response(response) except Event.DoesNotExist: pass - - if create_pairs: - _execute_in_chunks(service, create_pairs) + except Exception as e: + logger.error(f"Failed to create GCal event for Event {event_django_id}: {e}") # --- Updates: attendee status changes --- - update_pairs = [] for gcal_ev in all_events: try: event_django_id = int( @@ -473,14 +429,13 @@ def sync_from_local_to_google(service=None): local_attendees = build_gcal_attendees_obj(django_ev) if gcal_attendees != local_attendees: - update_pairs.append((update_gcal_event_request(service, django_ev), None)) + update_gcal_event_request(service, django_ev).execute() except Event.DoesNotExist: pass except (KeyError, ValueError): pass - - if update_pairs: - _execute_in_chunks(service, update_pairs) + except Exception as e: + logger.error(f"Failed to update GCal event: {e}") return len(events_to_create_django_id), len(events_to_delete_google_id)