Commit initial code.
This commit is contained in:
parent
ba9257e42d
commit
9130ff0ab5
8 changed files with 492 additions and 0 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -60,3 +60,15 @@ target/
|
|||
|
||||
#Ipython Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
#Vim
|
||||
# swap
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
# session
|
||||
Session.vim
|
||||
# temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# auto-generated tag files
|
||||
tags
|
||||
|
|
|
|||
0
drivepy/__init__.py
Normal file
0
drivepy/__init__.py
Normal file
31
drivepy/auth.py
Normal file
31
drivepy/auth.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from oauth2client.service_account import ServiceAccountCredentials
|
||||
import json
|
||||
import drivepy.config as config
|
||||
from httplib2 import Http
|
||||
|
||||
class ServiceAuthentication(object):
|
||||
"""Service Authorization for server-server OAuth2 support
|
||||
|
||||
Fields:
|
||||
credentials: ServiceAccountCredentials object loaded from the json file
|
||||
authorization: HTTP authorization object
|
||||
"""
|
||||
def __init__(self, auth_file):
|
||||
"""Creates a ServiceAuthentication object
|
||||
|
||||
Args:
|
||||
auth_file: a json file location that contains Google API credentials
|
||||
Returns:
|
||||
An instance of the ServiceAuthentication object
|
||||
"""
|
||||
credentials = None
|
||||
if isinstance(auth_file, file):
|
||||
auth_dict = json.load(auth_file)
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_dict(auth_dict,
|
||||
scopes=[config.GOOGLE_DRIVE_SCOPE])
|
||||
else:
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_name(auth_file,
|
||||
scopes=[config.GOOGLE_DRIVE_SCOPE])
|
||||
|
||||
self.credentials = credentials
|
||||
self.authorization = self.credentials.authorize(Http())
|
||||
114
drivepy/config.py
Normal file
114
drivepy/config.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import mimetypes
|
||||
import os
|
||||
|
||||
GOOGLE_DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive'
|
||||
|
||||
ALL_FILE_FIELDS = 'files,kind,nextPageToken'
|
||||
|
||||
ALL_SINGLE_FILE_FIELDS = 'appProperties,capabilities,contentHints,createdTime,description,explicitlyTrashed,fileExtension,folderColorRgb,fullFileExtension,headRevisionId,iconLink,id,imageMediaMetadata,isAppAuthorized,kind,lastModifyingUser,md5Checksum,mimeType,modifiedByMeTime,modifiedTime,name,originalFilename,ownedByMe,owners,parents,permissions,properties,quotaBytesUsed,shared,sharedWithMeTime,sharingUser,size,spaces,starred,thumbnailLink,trashed,version,videoMediaMetadata,viewedByMe,viewedByMeTime,viewersCanCopyContent,webContentLink,webViewLink,writersCanShare'
|
||||
|
||||
ALL_PERMISSION_FIELDS = 'allowFileDiscovery,displayName,domain,emailAddress,id,kind,photoLink,role,type'
|
||||
|
||||
class PermissionRole(object):
|
||||
Reader = 'reader'
|
||||
Writer = 'writer'
|
||||
Owner = 'owner'
|
||||
Commenter = 'commenter'
|
||||
|
||||
class PermissionType(object):
|
||||
User = 'user'
|
||||
Group = 'group'
|
||||
Domain = 'domain'
|
||||
Anyone = 'anyone'
|
||||
|
||||
class DownloadType(object):
|
||||
# Documents
|
||||
HTML = 'text/html'
|
||||
RichText = 'text/rtf'
|
||||
OpenOfficeDoc = 'application/vnd.oasis.opendocument.text'
|
||||
WordDoc = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
|
||||
# Spreadsheets
|
||||
CSV = 'text/csv'
|
||||
Excel = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
OpenOfficeSheet = 'application/x-vnd.oasis.opendocument.spreadsheet'
|
||||
|
||||
# Drawings
|
||||
JPEG = 'image/jpeg'
|
||||
PNG = 'image/png'
|
||||
SVG = 'image/svg+xml'
|
||||
|
||||
# Presentations
|
||||
PowerPoint = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
|
||||
# Scripts
|
||||
JSON = 'application/vnd.google-apps.script+json'
|
||||
|
||||
# All except Scripts
|
||||
PDF = 'application/pdf'
|
||||
|
||||
# All except scripts and sheets
|
||||
PlainText = 'text/plain'
|
||||
|
||||
class MimeTypes(object):
|
||||
Audio = 'application/vnd.google-apps.audio'
|
||||
Document = 'application/vnd.google-apps.document'
|
||||
Drawing = 'application/vnd.google-apps.drawing'
|
||||
File = 'application/vnd.google-apps.file'
|
||||
Folder = 'application/vnd.google-apps.folder'
|
||||
Form = 'application/vnd.google-apps.form'
|
||||
FusionTable = 'application/vnd.google-apps.fusiontable'
|
||||
Map = 'application/vnd.google-apps.map'
|
||||
Photo = 'application/vnd.google-apps.photo'
|
||||
Presentation = 'application/vnd.google-apps.presentation'
|
||||
Script = 'application/vnd.google-apps.script'
|
||||
Sites = 'application/vnd.google-apps.sites'
|
||||
Spreadsheet = 'application/vnd.google-apps.spreadsheet'
|
||||
Unknown = 'application/vnd.google-apps.unknown'
|
||||
Video = 'application/vnd.google-apps.video'
|
||||
|
||||
TypeDict = {}
|
||||
|
||||
types = {
|
||||
Audio: ['mp3', 'wav', 'aac', 'ogg', 'wma'],
|
||||
Document: ['txt', 'doc', 'docx', 'log', 'odt', 'rtf'],
|
||||
Folder: [''],
|
||||
Presentation: ['ppt', 'pptx'],
|
||||
Spreadsheet: ['csv', 'xls', 'xlsx', 'xlt', 'xml', 'ods'],
|
||||
Video: ['flv', 'mp4', 'mpg', 'mov', 'mkv', 'avi']
|
||||
}
|
||||
|
||||
for mime, exts in types.items():
|
||||
for ext in exts:
|
||||
TypeDict[ext] = mime
|
||||
|
||||
DownloadTypeDict = {
|
||||
Document: DownloadType.WordDoc,
|
||||
Spreadsheet: DownloadType.Excel,
|
||||
Drawing: DownloadType.PDF,
|
||||
Presentation: DownloadType.PowerPoint,
|
||||
Script: DownloadType.JSON
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_download_type(mime_type):
|
||||
"""Tries to guess the download type based on the mime_type."""
|
||||
return MimeTypes.DownloadTypeDict.get(mime_type, 'text/plain')
|
||||
|
||||
@staticmethod
|
||||
def guess_type(path):
|
||||
"""Guesses the mimetype of a file extension
|
||||
|
||||
Args:
|
||||
path: the file name or path
|
||||
|
||||
Returns:
|
||||
A mimetype string
|
||||
"""
|
||||
ext = os.path.split(path)[-1].replace('.', '')
|
||||
guess = MimeTypes.TypeDict.get(ext)
|
||||
|
||||
if guess is None:
|
||||
guess = mimetypes.guess_type(path)[0] or MimeTypes.File
|
||||
|
||||
return guess
|
||||
136
drivepy/drive.py
Normal file
136
drivepy/drive.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
from googleapiclient.discovery import build
|
||||
import drivepy.config as config
|
||||
from drivepy.config import MimeTypes
|
||||
from pprint import pprint
|
||||
from drivepy.files import GoogleFile
|
||||
|
||||
class GoogleDrive(object):
|
||||
def __init__(self, authentication):
|
||||
"""Create an instance of GoogleDrive
|
||||
|
||||
Args:
|
||||
authentication: an authentication object to create the service
|
||||
(eg: auth.ServiceAccountAuthentication)
|
||||
Returns:
|
||||
A GoogleDrive instance
|
||||
"""
|
||||
self.authentication = authentication
|
||||
|
||||
self.service = build('drive', 'v3', http=self.authentication.authorization)
|
||||
|
||||
def list_files(self, **params):
|
||||
"""List files in a way specified by params
|
||||
|
||||
Args:
|
||||
params: a dict-like object, all available parameters for
|
||||
the REST API "files" may be specified here
|
||||
Returns:
|
||||
A list of files in the drive.
|
||||
"""
|
||||
fields = params.pop('fields', config.ALL_FILE_FIELDS)
|
||||
files = self.service.files().list(fields=fields,
|
||||
**params).execute().get('files')
|
||||
return files
|
||||
|
||||
def create_file_from_path(self, path, content=None, mime_type=None, permissions=None):
|
||||
"""Creates a file on google drive from a path
|
||||
|
||||
Args:
|
||||
path: a file or folder path
|
||||
content: a string with the contents of the new file
|
||||
mime_type: the type of the file to be created
|
||||
permissions: a list of dict-like objects that describe permissions
|
||||
(eg. {'email': 'jap@hotmail.com',
|
||||
'role': PermissionRole.Writer,
|
||||
'type': PermissionType.User}
|
||||
|
||||
Returns:
|
||||
A list of the newly created file and it's directories
|
||||
|
||||
Raises:
|
||||
A GoogleFileException if the permissions object is specified and
|
||||
there is no 'email' key
|
||||
"""
|
||||
file_sections = path.split('/')
|
||||
|
||||
folders = file_sections[:-1]
|
||||
file_name = file_sections[-1]
|
||||
|
||||
parent_id = None
|
||||
|
||||
parent_folders = []
|
||||
|
||||
for folder_name in folders:
|
||||
params = {}
|
||||
|
||||
if parent_id:
|
||||
params['body'] = {'parents': [parent_id]}
|
||||
|
||||
google_file = GoogleFile.create(self.service, folder_name,
|
||||
mime_type=MimeTypes.Folder,
|
||||
**params)
|
||||
|
||||
parent_id = google_file.meta_data['id']
|
||||
parent_folders.append(google_file)
|
||||
|
||||
main_params = {}
|
||||
|
||||
if parent_id:
|
||||
main_params['body'] = {'parents': [parent_id]}
|
||||
|
||||
main_file = GoogleFile.create(self.service, file_name,
|
||||
mime_type=mime_type,
|
||||
**main_params)
|
||||
|
||||
result = parent_folders + [main_file]
|
||||
|
||||
if permissions:
|
||||
for permission in permissions:
|
||||
email = permission.pop('email', '')
|
||||
if not email:
|
||||
raise Exception('There must be an email key in the permissions object!')
|
||||
for google_file in result:
|
||||
google_file.give_permission(email, **permission)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_file_from_path(self, path):
|
||||
"""
|
||||
Parses a file path into a file query and gets
|
||||
the corresponding file objects associated
|
||||
|
||||
Args:
|
||||
path: a file path from the root of Google Drive
|
||||
in the form "Folder/SubFolder/file.ext"
|
||||
Returns:
|
||||
A list of file objects
|
||||
"""
|
||||
file_sections = path.split('/')
|
||||
|
||||
folders = file_sections[:-1]
|
||||
file_name = file_sections[-1]
|
||||
|
||||
parent_id = None
|
||||
|
||||
for folder in folders:
|
||||
if parent_id:
|
||||
folds = self.list_files(q='name = "{}" and "{}" in parents'.format(folder, parent_id),
|
||||
fields='files/id')
|
||||
else:
|
||||
folds = self.list_files(q='name = "{}"'.format(folder), fields='files/id')
|
||||
|
||||
if folds:
|
||||
fold = folds[0]
|
||||
parent_id = fold['id']
|
||||
else:
|
||||
raise Exception('File {} does not exist!'.format(path))
|
||||
|
||||
files = self.list_files(q='name = "{}" and "{}" in parents'.format(file_name, parent_id))
|
||||
|
||||
result_files = []
|
||||
|
||||
for file_meta in files:
|
||||
result_files.append(GoogleFile(self.service, file_meta))
|
||||
|
||||
return result_files
|
||||
183
drivepy/files.py
Normal file
183
drivepy/files.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
from googleapiclient.http import MediaIoBaseUpload, MediaIoBaseDownload
|
||||
from drivepy.config import MimeTypes, PermissionType, PermissionRole
|
||||
import drivepy.config as config
|
||||
import io
|
||||
|
||||
class GoogleFile(object):
|
||||
"""An object representing a file on GoogleDrive"""
|
||||
def __init__(self, service, meta_data):
|
||||
"""Create an instance of a GoogleFile
|
||||
|
||||
Args:
|
||||
service: the api service object
|
||||
metadata: the metadata for the file
|
||||
|
||||
Returns:
|
||||
An instance of GoogleFile
|
||||
"""
|
||||
|
||||
self.meta_data = meta_data
|
||||
self.service = service
|
||||
self.content = ''
|
||||
|
||||
@staticmethod
|
||||
def create(service, name, mime_type=None, content=None, body=None, **params):
|
||||
"""Creates a new file in GoogleDrive
|
||||
|
||||
Args:
|
||||
service: the API service object
|
||||
name: the name of the file, including the extension
|
||||
mime_type: the mimetype of the file. See config.MimeTypes
|
||||
content: the content string of the file
|
||||
body: a dict-like object, describes additional request body args
|
||||
params: any additional parameters to send to the API
|
||||
|
||||
Returns:
|
||||
A GoogleFile object
|
||||
"""
|
||||
fields = config.ALL_SINGLE_FILE_FIELDS
|
||||
|
||||
body = body or {}
|
||||
body.update({'name': name})
|
||||
|
||||
if mime_type is not None:
|
||||
body['mimeType'] = mime_type
|
||||
else:
|
||||
guess = MimeTypes.guess_type(name)
|
||||
if guess is not None:
|
||||
body['mimeType'] = guess
|
||||
|
||||
params = {}
|
||||
|
||||
if content:
|
||||
cont = io.BytesIO(content.encode('utf-8'))
|
||||
media_body = MediaIoBaseUpload(cont,
|
||||
body['mimeType'],
|
||||
resumable=True)
|
||||
params['media_body'] = media_body
|
||||
|
||||
meta_data = service.files().create(body=body, fields=fields, **params).execute()
|
||||
|
||||
google_file = GoogleFile(service, meta_data)
|
||||
google_file.content = content
|
||||
|
||||
return google_file
|
||||
|
||||
def refresh(self):
|
||||
"""Gets the meta data of the file from Google Drive
|
||||
|
||||
Returns:
|
||||
The meta data of the file
|
||||
"""
|
||||
fields = config.ALL_SINGLE_FILE_FIELDS
|
||||
self.meta_data = self.service.files().get(fileId=self.meta_data['id'], fields=fields).execute()
|
||||
return self.meta_data
|
||||
|
||||
def give_permission(self, email, send_notification=False,
|
||||
email_message='', fields=None, **params):
|
||||
"""Gives permission to an email to view a file
|
||||
|
||||
Args:
|
||||
email: a valid Google email address
|
||||
send_notification: whether to send an email about the share
|
||||
email_message: the contents of the email
|
||||
fields: a string with comma separated field names to include
|
||||
params: a dict-like object of parameters that can be passed to the api call
|
||||
|
||||
Returns:
|
||||
Meta data from the api call
|
||||
"""
|
||||
new_params = {'role': PermissionRole.Reader,
|
||||
'type': PermissionType.User}
|
||||
new_params.update(params)
|
||||
new_params['emailAddress'] = email
|
||||
|
||||
fields = fields or config.ALL_PERMISSION_FIELDS
|
||||
|
||||
file_id = self.meta_data.get('id')
|
||||
|
||||
permission_service = self.service.permissions()
|
||||
|
||||
email_params = {}
|
||||
if email_message:
|
||||
email_params['emailMessage'] = email_message
|
||||
|
||||
res = permission_service.create(
|
||||
fileId=file_id,
|
||||
sendNotificationEmail=send_notification,
|
||||
fields=fields,
|
||||
body=new_params,
|
||||
**email_params
|
||||
).execute()
|
||||
|
||||
self.refresh()
|
||||
|
||||
return res
|
||||
|
||||
def download(self, mime_type=None):
|
||||
"""Download the content of the file from Google Drive
|
||||
|
||||
Args:
|
||||
mime_type: the mime type of the file to download.
|
||||
see here:
|
||||
https://developers.google.com/drive/v3/web/manage-downloads#downloading_google_documents
|
||||
|
||||
Returns:
|
||||
The content of the file
|
||||
"""
|
||||
|
||||
if mime_type is None:
|
||||
download_type = MimeTypes.get_download_type(self.meta_data['mimeType'])
|
||||
else:
|
||||
download_type = mime_type
|
||||
|
||||
req = self.service.files().export_media(fileId=self.meta_data['id'],
|
||||
mimeType=download_type)
|
||||
data = io.BytesIO()
|
||||
downloader = MediaIoBaseDownload(data, req)
|
||||
done = False
|
||||
while not done:
|
||||
_, done = downloader.next_chunk()
|
||||
|
||||
data.seek(0)
|
||||
self.content = data.read()
|
||||
|
||||
return self.content
|
||||
|
||||
def delete(self):
|
||||
"""Delete the file on Google Drive
|
||||
|
||||
Returns:
|
||||
Metadata for the deleted file
|
||||
"""
|
||||
return self.service.files().delete(fileId=self.meta_data['id']).execute()
|
||||
|
||||
def update(self, meta_data, content=None):
|
||||
"""Update the current file meta data and contents on Google Drive
|
||||
|
||||
Args:
|
||||
meta_data: A dict-like object, updates the current meta data from
|
||||
this data
|
||||
contents: A string that replaces the contents of the current file
|
||||
on Google Drive
|
||||
|
||||
Returns:
|
||||
Metadata from the file update
|
||||
"""
|
||||
self.meta_data.update(meta_data)
|
||||
file_id = self.meta_data['id']
|
||||
|
||||
params = {}
|
||||
|
||||
if content:
|
||||
cont = io.BytesIO(content.encode('utf-8'))
|
||||
media_body = MediaIoBaseUpload(cont,
|
||||
self.meta_data['mimeType'],
|
||||
resumable=True)
|
||||
params['media_body'] = media_body
|
||||
self.content = content
|
||||
|
||||
res = self.service.files().update(fileId=file_id,
|
||||
body=self.meta_data,
|
||||
**params).execute()
|
||||
return res
|
||||
0
drivepy/test/__init__.py
Normal file
0
drivepy/test/__init__.py
Normal file
16
setup.py
Normal file
16
setup.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='PyDrive',
|
||||
version='0.1.0',
|
||||
author='Joey Yakimowich-Payne',
|
||||
author_email='jyapayne@gmail.com',
|
||||
packages=['drivepy', 'drivepy.test'],
|
||||
url='https://github.com/jyapayne/DrivePy/',
|
||||
license='LICENSE',
|
||||
description='Pythonic Google Drive API Bindings.',
|
||||
long_description=open('README.md').read(),
|
||||
install_requires=[
|
||||
'google-api-python-client >= 1.5.0'
|
||||
]
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue