Source code for pycharmers.api.google_drive

"""A Wrapper class for GoogleDrive in `PyDrive <https://pythonhosted.org/PyDrive/index.html>`_"""
#coding: utf-8
import os
import re
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive

from ..utils.print_utils import tabulate
from ..utils._colorings import toBLUE, toGREEN, toRED

[docs]class PyCharmersGoogleDrive(GoogleDrive): """Wrapper class for GoogleDrive. Args: settings_file (str) : path of settings file. Defaults to ``'settings.yaml'`` . http_timeout (int) : HTTP timeout. Defaults to None. Examples: >>> from pycharmers.api import PyCharmersGoogleDrive >>> drive = PyCharmersGoogleDrive(settings_file='dir/subdir/settings.yaml') >>> drive When executed as above, the directory structure is as follows. .. code-block:: shell $ tree . . └── dir └── subdir ├── client_secrets.json ├── credentials.json └── settings.yaml ``settings.yaml`` is like `this <https://pythonhosted.org/PyDrive/oauth.html?highlight=yaml#sample-settings-yaml>`_ . .. code-block:: yaml client_config_backend: settings client_config: client_id: <CLIENT_ID> client_secret: <CLIENT_SECRET save_credentials: True save_credentials_backend: file save_credentials_file: credentials.json get_refresh_token: True oauth_scope: - https://www.googleapis.com/auth/drive.file - https://www.googleapis.com/auth/drive.install - https://www.googleapis.com/auth/drive """ def __init__(self, settings_file='settings.yaml', http_timeout=None): self.auth = self.authenticate(settings_file=settings_file, http_timeout=http_timeout) super().__init__(auth=self.auth) self.dirId2name = {}
[docs] @staticmethod def authenticate(settings_file='settings.yaml', http_timeout=None): """Get an Authentication. See the `PyDrive's documentation <https://pythonhosted.org/PyDrive/oauth.html?highlight=yaml#sample-settings-yaml>`_ Args: settings_file (str) : path of settings file. Defaults to ``'settings.yaml'`` . http_timeout (int) : HTTP timeout. Defaults to None. Returns: GoogleAuth: Wrapper class for oauth2client library in google-api-python-client. """ dirname, filename = os.path.split(settings_file) cwd = os.getcwd() # Change directory to where settings_file exists. if dirname!="": os.chdir(dirname) gauth = GoogleAuth(settings_file=filename) gauth.LocalWebserverAuth() # Back to the original directory. os.chdir(cwd) return gauth
[docs] @staticmethod def arrange_queries(queries=[], ext=None, isfile=None, trashed=False, verbose=False): """Arrange queries for ``Files.List()`` Args: queries (list) : Current queries. Defaults to ``[]`` . ext (str) : File Extensions. Defaults to ``None`` . isfile (bool) : If this value is ``True``, extract only "file", else if this value is ``False``, extract only "folder", else (if this value is ``None`` ) extract "both". trashed (bool) : Whether trashed file or not. Defaults to ``False`` verbose (bool) : Whether to print the query or not. Defaults to ``False`` Returns: dict: parameter to be sent to ``Files.List()`` . Examples: >>> from pycharmers.api import PyCharmersGoogleDrive >>> PyCharmersGoogleDrive.arrange_queries(queries=[], ext=".mp4", isfile=True, trashed=False) {'q': 'title contains ".mp4" and mimeType != "application/vnd.google-apps.folder" and trashed = false'} >>> PyCharmersGoogleDrive.arrange_queries(queries=[], ext=None, isfile=False, trashed=None) {'q': 'mimeType = "application/vnd.google-apps.folder" and trashed = none'} """ if ext is not None: queries.append(QUERY.TITLE_CONTAIN.format(q=ext)) if isfile is not None: if isfile: queries.append(QUERY.FILES) else: queries.append(QUERY.FOLDERS) queries.append(QUERY.TRASHED.format(q=str(trashed).lower())) if verbose: print("[Queries]") for q in queries: print(f"* {q}") return {"q": " and ".join(queries)}
[docs] def getListFile(self, param=None): """Create an instance of GoogleDriveFileList with auth of this instance. Args: param (dict) : parameter to be sent to ``Files.List()`` . Returns: GoogleDriveFileList: Google Drive File List. """ return self.ListFile(param=param).GetList()
[docs] def get_file_list(self, filename=None, dirname=None, dirId="root", ext=None, isfile=None, trashed=False, recursive=False, verbose=False): """Use queries effortlessly to get a list of files. Args: filename (str) : Exact File Name. Defaults to ``None`` . dirname (str) : Directory Name. Defaults to ``None`` . dirId (str) : Directory Id. Defaults to ``None`` . ext (str) : File Extensions. Defaults to ``None`` . isfile (bool) : If this value is ``True``, extract only "file", else if this value is ``False``, extract only "folder", else (if this value is ``None`` ) extract "both". trashed (bool) : Whether trashed file or not. Defaults to ``False`` recursive (bool) : Whether to find files recursively. Defaults to ``False`` verbose (bool) : Whether to print the query or not. Defaults to ``False`` Returns: GoogleDriveFileList: Google Drive File List. Examples: >>> from pycharmers.api import PyCharmersGoogleDrive >>> drive = PyCharmersGoogleDrive(settings_file="settings.json") >>> for f in drive.get_file_list(dirname="DIRNAME"): ... print(f["title"], f["id"]) """ queries = [QUERY.PARENT.format(q=self.get_dirId(dirname=dirname, dirId=dirId))] if filename is not None: queries.append(QUERY.TITLE_MATCH.format(q=filename)) fileLists = self.getListFile(param=self.arrange_queries(queries=queries, ext=ext, isfile=isfile, trashed=trashed, verbose=verbose)) if recursive: for f in self.getListFile(param=self.arrange_queries(queries=queries[:1], isfile=False, trashed=trashed, verbose=verbose)): fileLists.extend(self.get_file_list(filename=filename, dirname=None, dirId=f["id"], ext=ext, isfile=isfile, trashed=trashed, recursive=recursive, verbose=verbose)) return fileLists
[docs] def get_dirId(self, dirname=None, dirId="root"): """Get directory Id. Args: dirname (str) : Directory Name. Defaults to ``None`` . dirId (str) : Directory Id. Defaults to ``"root"`` . Returns: str : Directory Id. """ if dirname is not None: directory = self.getListFile(param={'q': QUERY.TITLE_MATCH.format(q=dirname)}) if len(directory)>0: dirId = directory[0]["id"] else: print(f"Folder {toBLUE(dirname)} is not found in your GoogleDrive. Try to specify the {toGREEN('dirId')}") return dirId
[docs] def upload(self, filepath, verbose=True): """Upload file. Args: filepath (str): name of the file to be uploaded. Returns: GoogleDriveFile: Google Drive File Info. """ file = self.CreateFile() file.SetContentFile(filename=filepath) file["title"] = os.path.basename(filepath) file.Upload() if verbose: print(f"{toGREEN('[success]')} Upload {toBLUE(filepath)} (ID={toGREEN(file['id'])})") return file
[docs] def download_file(self, filename=None, dirname=None, dirId="root", ext=None, isfile=None, trashed=False, dst=None, mimetype=None, remove_bom=False, verbose=True): """Download file. Args: filename (str) : File Name. Defaults to ``None`` . dirname (str) : Directory Name. Defaults to ``None`` . dirId (str) : Directory Id. Defaults to ``None`` . ext (str) : File Extensions. Defaults to ``None`` . isfile (bool) : If this value is ``True``, extract only "file", else if this value is ``False``, extract only "folder", else (if this value is ``None`` ) extract "both". trashed (bool) : Whether trashed file or not. Defaults to ``False`` dst (str) : Where to download the file. Defaults to ``None`` . mimetype (str) : mimeType of the file.. Defaults to ``None`` . remove_bom (bool) : Whether to remove the byte order marking.. Defaults to ``False``. verbose (bool) : Whether to print the result or not. Defaults to ``True`` Returns: str : Path to downloaded file. Examples: >>> from pycharmers.api import PyCharmersGoogleDrive >>> drive = PyCharmersGoogleDrive(settings_file="settings.json") >>> drive.download_file(filename="file.png", dirId="root", verbose=True) """ filelist = self.get_file_list(filename=filename, dirname=dirname, dirId=dirId, ext=ext, isfile=isfile, trashed=trashed, verbose=False) if len(filelist)>0: f = filelist[0] filename = f["title"] file = self.CreateFile({"id": f["id"]}) dst = dst or filename file.GetContentFile(filename=dst, mimetype=mimetype, remove_bom=remove_bom) if verbose: print(f"{toGREEN('[success]') if len(filelist)>0 else toRED('[failure]')} Downloaded {toGREEN(filename)} to {toBLUE(dst)}") return dst
[docs] def tree(self, dirname=None, dirId="root"): """list contents of directories in a tree-like format. Args: dirname (str) : Directory Name. Defaults to ``None`` . dirId (str) : Directory Id. Defaults to ``"root"`` . TODO: To display the file contents in a tree-like format. Examples: >>> from pycharmers.api import PyCharmersGoogleDrive >>> drive = PyCharmersGoogleDrive(settings_file="settings.json") >>> drive.tree() """ root_file_list = self.get_file_list(dirname=None, dirId="root") for f in root_file_list: p = f["parents"] if len(p)>0: self.dirId2name[p[0]["id"]] = "." self.dirId2name["root/"] = "./" break def dirId2name(f): for o,n in self.dirId2name.items(): f = f.replace(o,n) return f for f in sorted(set([dirId2name(f) for f in self.tree_recursive(dirname=dirname, dirId=dirId, parents=[])])): print(f)
[docs] def tree_recursive(self, dirname=None, dirId="root", parents=[]): """[summary] Args: dirname (str) : Directory Name. Defaults to ``None`` . dirId (str) : Directory Id. Defaults to ``"root"`` . parents (list) : Parent Directories. Defaults to ``[]``. Returns: list: All filepaths from initial ``dirname`` ( ``dirId`` ) """ filepaths = [] dirId = self.get_dirId(dirname=dirname, dirId=dirId) if len(parents)==0: parents = [dirId] for f in self.get_file_list(dirname=None, dirId=dirId): title = f["title"] is_folder = f["mimeType"]=='application/vnd.google-apps.folder' if len(f["parents"])>0: fp = "/".join(parents[:-1] + [p["id"] for p in reversed(f["parents"])] + [title]) else: fp = f"shared/{title}" filepaths.append(fp) if is_folder: self.dirId2name[f["id"]] = f["title"] filepaths.extend(self.tree_recursive(dirname=None, dirId=f["id"], parents=parents+[f["id"]])) return filepaths
[docs]class QUERY: """Query to be sent to ``Files.List()`` . +------------------------------+---------------------------------------------------------+ | References | URL | +==============================+=========================================================+ | Japanese cheatsheet | https://note.nkmk.me/python-pydrive-list-file/ | +------------------------------+---------------------------------------------------------+ | Commonly Used MIME Types | https://learndataanalysis.org/commonly-used-mime-types/ | +------------------------------+---------------------------------------------------------+ | G Suite and Drive MIME Types | https://developers.google.com/drive/api/v3/mime-types | +------------------------------+---------------------------------------------------------+ """ FOLDERS = 'mimeType = "application/vnd.google-apps.folder"' FILES = 'mimeType != "application/vnd.google-apps.folder"' TITLE_MATCH = 'title = "{q}"' TITLE_CONTAIN = 'title contains "{q}"' PARENT = '"{q}" in parents' TRASHED = 'trashed = {q}'
[docs] @staticmethod def show(): """Show all Queries. Examples: >>> from pycharmers.api.google_drive import QUERY >>> QUERY.show() """ tabulate(tabular_data=[[k,v] for k,v in QUERY.__dict__.items() if re.match(pattern=r"[A-Z]+", string=k)], headers=["NAME", "query"])