2121
2222logger = get_logger (__name__ )
2323
24+
2425# -------------------------
2526# Config model
2627# -------------------------
@@ -105,6 +106,7 @@ def __init__(self, config: Dict[str, Any]) -> None:
105106
106107 # Token file default - use data directory for persistence
107108 from config .paths import get_data_file
109+
108110 token_file = config .get ("token_file" ) or get_data_file ("google_drive_token.json" )
109111 Path (token_file ).parent .mkdir (parents = True , exist_ok = True )
110112
@@ -120,9 +122,7 @@ def __init__(self, config: Dict[str, Any]) -> None:
120122 )
121123
122124 # Normalize incoming IDs from any of the supported alias keys
123- def _first_present_list (
124- cfg : Dict [str , Any ], keys : Iterable [str ]
125- ) -> Optional [List [str ]]:
125+ def _first_present_list (cfg : Dict [str , Any ], keys : Iterable [str ]) -> Optional [List [str ]]:
126126 for k in keys :
127127 v = cfg .get (k )
128128 if v : # accept non-empty list
@@ -362,11 +362,16 @@ def _iter_selected_items(self) -> List[Dict[str, Any]]:
362362 folders_to_expand : List [str ] = []
363363
364364 if self .cfg .file_ids :
365- logger .debug ("[GoogleDrive] _iter_selected_items: processing %d file_id(s)" , len (self .cfg .file_ids ))
365+ logger .debug (
366+ "[GoogleDrive] _iter_selected_items: processing %d file_id(s)" ,
367+ len (self .cfg .file_ids ),
368+ )
366369 for fid in self .cfg .file_ids :
367370 meta = self ._get_file_meta_by_id (fid )
368371 if not meta :
369- logger .debug ("[GoogleDrive] _iter_selected_items: no metadata for file_id=%s" , fid )
372+ logger .debug (
373+ "[GoogleDrive] _iter_selected_items: no metadata for file_id=%s" , fid
374+ )
370375 continue
371376
372377 if meta .get ("mimeType" ) == "application/vnd.google-apps.folder" :
@@ -384,7 +389,9 @@ def _iter_selected_items(self) -> List[Dict[str, Any]]:
384389 folders_to_expand .extend (self .cfg .folder_ids )
385390
386391 if folders_to_expand :
387- logger .debug ("[GoogleDrive] _iter_selected_items: expanding %d folder(s)" , len (folders_to_expand ))
392+ logger .debug (
393+ "[GoogleDrive] _iter_selected_items: expanding %d folder(s)" , len (folders_to_expand )
394+ )
388395 folder_children = self ._bfs_expand_folders (folders_to_expand )
389396 for meta in folder_children :
390397 meta = self ._resolve_shortcut (meta )
@@ -400,11 +407,7 @@ def _iter_selected_items(self) -> List[Dict[str, Any]]:
400407 return []
401408
402409 items = self ._filter_by_mime (items )
403- items = [
404- m
405- for m in items
406- if m .get ("mimeType" ) != "application/vnd.google-apps.folder"
407- ]
410+ items = [m for m in items if m .get ("mimeType" ) != "application/vnd.google-apps.folder" ]
408411
409412 if not items and (self .cfg .file_ids or self .cfg .folder_ids ):
410413 logger .warning (
@@ -497,9 +500,7 @@ def _download_file_bytes(self, file_meta: Dict[str, Any]) -> bytes:
497500 export_mime ,
498501 )
499502 # NOTE: export_media does not accept supportsAllDrives/includeItemsFromAllDrives
500- request = self .service .files ().export_media (
501- fileId = file_id , mimeType = export_mime
502- )
503+ request = self .service .files ().export_media (fileId = file_id , mimeType = export_mime )
503504 else :
504505 # This is a regular uploaded file (PDF, image, video, etc.) - use get_media
505506 # Also handles non-exportable Google Apps files (Forms, Sites, Maps, etc.)
@@ -525,9 +526,7 @@ def _download_file_bytes(self, file_meta: Dict[str, Any]) -> bytes:
525526 f"Retrying with export_media (file might be a Google Doc)"
526527 )
527528 export_mime = "application/pdf"
528- request = self .service .files ().export_media (
529- fileId = file_id , mimeType = export_mime
530- )
529+ request = self .service .files ().export_media (fileId = file_id , mimeType = export_mime )
531530 fh = io .BytesIO ()
532531 downloader = MediaIoBaseDownload (fh , request , chunksize = 1024 * 1024 )
533532 done = False
@@ -601,7 +600,9 @@ async def list_files(
601600 "Google Drive service is not initialized. Please authenticate first."
602601 )
603602
604- logger .debug ("[GoogleDrive] list_files: entry (page_token=%s, max_files=%s)" , page_token , max_files )
603+ logger .debug (
604+ "[GoogleDrive] list_files: entry (page_token=%s, max_files=%s)" , page_token , max_files
605+ )
605606
606607 try :
607608 items = await asyncio .to_thread (self ._iter_selected_items )
@@ -636,10 +637,14 @@ def _extract_google_drive_acl(self, file_meta: Dict) -> DocumentACL:
636637 """
637638 try :
638639 # Fetch permissions (requires additional API call)
639- permissions_list = self .service .permissions ().list (
640- fileId = file_meta ["id" ],
641- fields = "permissions(emailAddress,role,type,deleted,displayName)"
642- ).execute ()
640+ permissions_list = (
641+ self .service .permissions ()
642+ .list (
643+ fileId = file_meta ["id" ],
644+ fields = "permissions(emailAddress,role,type,deleted,displayName)" ,
645+ )
646+ .execute ()
647+ )
643648
644649 allowed_users = []
645650 allowed_groups = []
@@ -744,12 +749,12 @@ def parse_datetime(dt_str):
744749 metadata = {
745750 "parents" : meta .get ("parents" ),
746751 "driveId" : meta .get ("driveId" ),
747- "size" : int (meta .get ("size" , 0 ))
748- if str (meta .get ("size" , "" )).isdigit ()
749- else None ,
752+ "size" : int (meta .get ("size" , 0 )) if str (meta .get ("size" , "" )).isdigit () else None ,
750753 },
751754 )
752- logger .debug ("[GoogleDrive] get_file_content: done for file_id=%s (%d bytes)" , file_id , len (blob ))
755+ logger .debug (
756+ "[GoogleDrive] get_file_content: done for file_id=%s (%d bytes)" , file_id , len (blob )
757+ )
753758 return doc
754759
755760 async def setup_subscription (self ) -> str :
@@ -766,9 +771,7 @@ async def setup_subscription(self) -> str:
766771 # 1) Ensure we are authenticated and have a live Drive service
767772 ok = await self .authenticate ()
768773 if not ok :
769- raise RuntimeError (
770- "GoogleDriveConnector.setup_subscription: not authenticated"
771- )
774+ raise RuntimeError ("GoogleDriveConnector.setup_subscription: not authenticated" )
772775
773776 # 2) Resolve webhook address (no param in ABC, so pull from config/env)
774777 webhook_address = getattr (self .cfg , "webhook_address" , None ) or os .getenv (
@@ -824,9 +827,7 @@ async def setup_subscription(self) -> str:
824827 }
825828
826829 if not isinstance (channel_id , str ) or not channel_id :
827- raise RuntimeError (
828- f"Drive watch returned invalid channel id: { channel_id !r} "
829- )
830+ raise RuntimeError (f"Drive watch returned invalid channel id: { channel_id !r} " )
830831
831832 return channel_id
832833
@@ -902,9 +903,7 @@ async def cleanup_subscription(self, subscription_id: str) -> bool:
902903 ):
903904 self ._active_channel = {}
904905
905- if hasattr (self , "_subscriptions" ) and isinstance (
906- self ._subscriptions , dict
907- ):
906+ if hasattr (self , "_subscriptions" ) and isinstance (self ._subscriptions , dict ):
908907 self ._subscriptions .pop (subscription_id , None )
909908
910909 return True
@@ -955,9 +954,7 @@ async def handle_webhook(self, payload: Dict[str, Any]) -> List[str]:
955954 except Exception as e :
956955 selected_ids = set ()
957956 try :
958- logger .error (
959- f"handle_webhook: scope build failed, proceeding unfiltered: { e } "
960- )
957+ logger .error (f"handle_webhook: scope build failed, proceeding unfiltered: { e } " )
961958 except Exception :
962959 pass
963960
@@ -994,11 +991,7 @@ async def handle_webhook(self, payload: Dict[str, Any]) -> List[str]:
994991 # Filter to our selected scope if we have one; otherwise accept all
995992 if selected_ids and (rid not in selected_ids ):
996993 # Shortcut target might be in scope even if the shortcut isn't
997- tgt = (
998- fobj .get ("shortcutDetails" , {}).get ("targetId" )
999- if fobj
1000- else None
1001- )
994+ tgt = fobj .get ("shortcutDetails" , {}).get ("targetId" ) if fobj else None
1002995 if not (tgt and tgt in selected_ids ):
1003996 continue
1004997
@@ -1047,9 +1040,7 @@ def sync_once(self) -> None:
10471040 blob = self ._download_file_bytes (meta )
10481041 except HttpError as e :
10491042 # Skip/record failures
1050- logger .error (
1051- f"Failed to download { meta .get ('name' )} ({ meta .get ('id' )} ): { e } "
1052- )
1043+ logger .error (f"Failed to download { meta .get ('name' )} ({ meta .get ('id' )} ): { e } " )
10531044 continue
10541045
10551046 from datetime import datetime
@@ -1095,9 +1086,7 @@ def parse_datetime(dt_str):
10951086 # -------------------------
10961087 def get_start_page_token (self ) -> str :
10971088 # getStartPageToken accepts supportsAllDrives (not includeItemsFromAllDrives)
1098- resp = (
1099- self .service .changes ().getStartPageToken (** self ._drives_get_flags ).execute ()
1100- )
1089+ resp = self .service .changes ().getStartPageToken (** self ._drives_get_flags ).execute ()
11011090 return resp ["startPageToken" ]
11021091
11031092 def poll_changes_and_sync (self ) -> Optional [str ]:
@@ -1136,10 +1125,7 @@ def poll_changes_and_sync(self) -> Optional[str]:
11361125 # Match scope
11371126 if fid not in selected_ids :
11381127 # also consider shortcut target
1139- if (
1140- file_obj .get ("mimeType" )
1141- == "application/vnd.google-apps.shortcut"
1142- ):
1128+ if file_obj .get ("mimeType" ) == "application/vnd.google-apps.shortcut" :
11431129 tgt = file_obj .get ("shortcutDetails" , {}).get ("targetId" )
11441130 if tgt and tgt in selected_ids :
11451131 pass
0 commit comments