"""os.walk() variation with Google Drive API v3.""" from collections.abc import Iterator, Sequence import os import pathlib from typing import TypedDict # $ pip install google-api-python-client google-auth-oauthlib from apiclient import discovery from google.oauth2 import credentials from google_auth_oauthlib import flow as flow_lib SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'] FOLDER = 'application/vnd.google-apps.folder' def get_credentials(scopes: Sequence[str], *, secrets: os.PathLike[str] | str = '~/client_secrets.json', storage: os.PathLike[str] | str | None = '~/authorized_user.json' ) -> credentials.Credentials: creds = None if storage is not None: storage = pathlib.Path(storage).expanduser() if storage.exists(): creds = credentials.Credentials.from_authorized_user_file(storage, scopes=scopes) if creds is None or creds.token_state.name == 'INVALID': secrets = pathlib.Path(secrets).expanduser() flow = flow_lib.InstalledAppFlow.from_client_secrets_file(secrets, scopes=scopes) flow.run_local_server() creds = flow.credentials if storage is not None: authorized_user_info = creds.to_json() storage.write_text(authorized_user_info) return creds creds = get_credentials(SCOPES) service = discovery.build('drive', version='v3', credentials=creds) def iterfiles(name: str | None = None, *, is_folder: bool | None = None, parent: str | None = None, order_by: str = 'folder,name,createdTime') -> Iterator['File']: q = [] if name is not None: q.append("name = '{}'".format(name.replace("'", "\\'"))) if is_folder is not None: q.append("mimeType {} '{}'".format('=' if is_folder else '!=', FOLDER)) if parent is not None: q.append("'{}' in parents".format(parent.replace("'", "\\'"))) params = {'pageToken': None, 'orderBy': order_by} if q: params['q'] = ' and '.join(q) while True: response = service.files().list(**params).execute() for f in response['files']: yield f try: params['pageToken'] = response['nextPageToken'] except KeyError: return class File(TypedDict): id: str kind: str name: str mimeType: str resourceKey: str def walk(top: str = 'root', *, by_name: bool = False) -> Iterator[tuple[str, list[File], list[File]]]: if by_name: (top,) = iterfiles(name=top, is_folder=True) else: top = service.files().get(fileId=top).execute() if top['mimeType'] != FOLDER: raise ValueError(f'not a folder: {top!r}') stack = [((top['name'],), top)] while stack: (path, top) = stack.pop() (dirs, files) = is_file = ([], []) for f in iterfiles(parent=top['id']): is_file[f['mimeType'] != FOLDER].append(f) yield path, top, dirs, files if dirs: stack.extend((path + (d['name'],), d) for d in reversed(dirs)) for kwargs in [{'top': 'spam', 'by_name': True}, {}]: print('', f'walk(**{kwargs!r})', sep='\n') for path, root, dirs, files in walk(**kwargs): print('/'.join(path), f'{len(dirs):d}', f'{len(files):d}', sep='\t')