Copyright (C) 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Service details and instances for the Docs service.
Some use cases: Upload a document: docs upload --folder "Some folder" path_to_doc
Edit a document in your word editor: docs edit --title "Grocery List" --editor vim (editor also set in prefs)
Download docs: docs get --folder "Some folder"
from __future__ import with_statement
__author__ = 'tom.h.miller@gmail.com (Tom Miller)'
import gdata.docs.service
import logging
import os
import shutil
import googlecl
import googlecl.base
import googlecl.service
from googlecl.docs import SECTION_HEADER
import googlecl.docs.base
Renamed here to reduce verbosity in other sections
safe_encode = googlecl.safe_encode
safe_decode = googlecl.safe_decode
LOG = logging.getLogger(googlecl.docs.LOGGER_NAME + '.service')
Extends gdata.docs.service.DocsClient for the command line.
class DocsServiceCL(gdata.docs.service.DocsService,
googlecl.docs.base.DocsBaseCL,
googlecl.service.BaseServiceCL):
This class adds some features focused on using Google Docs via an installed app with a command line interface.
DOCLIST_FEED_URI = '/feeds/documents/private/full'
Constructor.
def __init__(self, config):
gdata.docs.service.DocsService.__init__(self, source='GoogleCL')
googlecl.service.BaseServiceCL.__init__(self, SECTION_HEADER, config)
302 Moved Temporarily errors began cropping up for new style docs during export. Using https solves the problem, so set ssl True here.
self.ssl = True
Downloads a file.
def _DownloadFile(self, uri, file_path):
Overloaded from docs.service.DocsService to optionally decode from UTF.
Args: uri: string The full Export URL to download the file from. file_path: string The full path to save the file to.
Raises: RequestError: on error response from server.
server_response = self.request('GET', uri)
response_body = server_response.read()
if server_response.status != 200:
raise gdata.service.RequestError, {'status': server_response.status,
'reason': server_response.reason,
'body': response_body}
if googlecl.docs.base.can_export(uri) and\
self.config.lazy_get(SECTION_HEADER, 'decode_utf_8', False, bool):
try:
file_string = response_body.decode('utf-8-sig')
except UnicodeError, err:
LOG.debug('Could not decode: ' + str(err))
file_string = response_body
else:
file_string = response_body
with open(file_path, 'wb') as download_file:
download_file.write(file_string)
download_file.flush()
Stolen from gdata-2.0.10 to make recursive directory upload work.
def _create_folder(self, title, folder_or_uri=None):
try:
return gdata.docs.service.DocsService.CreateFolder(self, title,
folder_or_uri)
except AttributeError:
import atom
if folder_or_uri:
try:
uri = folder_or_uri.content.src
except AttributeError:
uri = folder_or_uri
else:
uri = '/feeds/documents/private/full'
folder_entry = gdata.docs.DocumentListEntry()
folder_entry.title = atom.Title(text=title)
folder_entry.category.append(_make_kind_category(
googlecl.docs.FOLDER_LABEL))
folder_entry = self.Post(folder_entry, uri,
converter=gdata.docs.DocumentListEntryFromString)
return folder_entry
def _determine_content_type(self, file_ext):
from gdata.docs.service import SUPPORTED_FILETYPES
try:
return SUPPORTED_FILETYPES[file_ext.upper()]
except KeyError:
LOG.info('No supported filetype found for extension %s', file_ext)
return None
Export old and new version docs.
def export(self, entry_or_id_or_url, file_path, gid=None, extra_params=None):
Ripped from gdata.docs.DocsService, adds 'format' parameter to make new version documents happy.
ext = googlecl.get_extension_from_path(file_path)
if ext:
if extra_params is None:
extra_params = {}
Fix issue with new-style docs always downloading to PDF (gdata-issues Issue 2157)
extra_params['format'] = ext
self.Download(entry_or_id_or_url, file_path, ext, gid, extra_params)
Export = export
Get a list of document entries from a feed.
def get_doclist(self, titles=None, folder_entry_list=None):
Keyword arguments: titles: list or string Title(s) of entries to return. Will be compared to entry.title.text, using regular expressions if self.use_regex. Default None for all entries from feed. folder_entry_list: List of GDataEntry's of folders to get from. Only files found in these folders will be returned. Default None for all folders.
Returns: List of entries.
if folder_entry_list:
entries = []
for folder in folder_entry_list:
folder.content.src is the uri to query for documents in that folder.
entries.extend(self.GetEntries(folder.content.src,
titles,
converter=gdata.docs.DocumentListFeedFromString))
else:
query = gdata.docs.service.DocumentQuery()
entries = self.GetEntries(query.ToUri(),
titles,
converter=gdata.docs.DocumentListFeedFromString)
return entries
Return exactly one doc_entry.
def get_single_doc(self, title=None, folder_entry_list=None):
Keyword arguments: title: Title to match on for document. Default None for any title. folder_entry_list: GDataEntry of folders to look in. Default None for any folder.
Returns: None if there were no matches, or one entry matching the given title.
if folder_entry_list:
if len(folder_entry_list) == 1:
return self.GetSingleEntry(folder_entry_list[0].content.src,
title,
converter=gdata.docs.DocumentListFeedFromString)
else:
entries = self.get_doclist(title, folder_entry_list)
Technically don't need the converter for this call because we have the entries.
return self.GetSingleEntry(entries, title)
else:
return self.GetSingleEntry(gdata.docs.service.DocumentQuery().ToUri(),
title,
converter=gdata.docs.DocumentListFeedFromString)
GetSingleDoc = get_single_doc
Return entries for one or more folders.
def get_folder(self, title):
Keyword arguments: title: Title of the folder.
Returns: GDataEntry representing a folder, or None of title is None.
if title:
query = gdata.docs.service.DocumentQuery(categories=['folder'],
params={'showfolders': 'true'})
folder_entries = self.GetEntries(query.ToUri(), title)
if not folder_entries:
LOG.warning('No folder found that matches ' + title)
return folder_entries
else:
return None
GetFolder = get_folder
Check that the token being used is valid.
def is_token_valid(self, test_uri=None):
if not test_uri:
docs_uri = gdata.docs.service.DocumentQuery().ToUri()
sheets_uri = ('https://spreadsheets.google.com/feeds/spreadsheets'
'/private/full')
docs_test = googlecl.service.BaseServiceCL.IsTokenValid(self, docs_uri)
sheets_test = googlecl.service.BaseServiceCL.IsTokenValid(self, sheets_uri)
return docs_test and sheets_test
IsTokenValid = is_token_valid
Replace content of a DocEntry.
def _modify_entry(self, doc_entry, path_to_new_content, file_ext):
Args: doc_entry: DocEntry whose content will be replaced. path_to_new_content: str Path to file that has new content. file_ext: str Extension to use to determine MIME type of upload (e.g. 'txt', 'doc')
from gdata.docs.service import SUPPORTED_FILETYPES
try:
content_type = SUPPORTED_FILETYPES[file_ext.upper()]
except KeyError:
print 'Could not find mimetype for ' + file_ext
while file_ext not in SUPPORTED_FILETYPES.keys():
file_ext = raw_input('Please enter one of ' +
SUPPORTED_FILETYPES.keys() +
' for a content type to upload as.')
content_type = SUPPORTED_FILETYPES[file_ext.upper()]
mediasource = gdata.MediaSource(file_path=path_to_new_content,
content_type=content_type)
return self.Put(mediasource, doc_entry.GetEditMediaLink().href)
Request access as in BaseServiceCL, but specify scopes.
def request_access(self, domain, display_name, scopes=None, browser=None):
When people use docs (writely), they expect access to spreadsheets as well (wise).
if not scopes:
scopes = gdata.service.CLIENT_LOGIN_SCOPES['writely'] +\
gdata.service.CLIENT_LOGIN_SCOPES['wise']
return googlecl.service.BaseServiceCL.request_access(self, domain,
display_name,
scopes=scopes,
browser=browser)
RequestAccess = request_access
Upload a document.
def _transmit_doc(self, path, entry_title, post_uri, content_type, file_ext):
The final step in uploading a document. The process differs between versions of the gdata python client library, hence its definition here.
Args: path: Path to the file to upload. entry_title: Name of the document. post_uri: URI to make request to. content_type: MIME type of request. file_ext: File extension that determined the content_type.
Returns: Entry representing the document uploaded.
media = gdata.MediaSource(file_path=path, content_type=content_type)
try:
Upload() wasn't added until later versions of DocsService, so we may not have it.
return self.Upload(media, entry_title, post_uri)
except AttributeError:
import atom
entry = gdata.docs.DocumentListEntry()
entry.title = atom.Title(text=entry_title)
Cover the supported filetypes in gdata-2.0.10 even though they aren't listed in gdata 1.2.4... see what happens.
if file_ext.lower() in ['csv', 'tsv', 'tab', 'ods', 'xls', 'xlsx']:
category = _make_kind_category(googlecl.docs.SPREADSHEET_LABEL)
elif file_ext.lower() in ['ppt', 'pps']:
category = _make_kind_category(googlecl.docs.PRESENTATION_LABEL)
elif file_ext.lower() in ['pdf']:
category = _make_kind_category(googlecl.docs.PDF_LABEL)
Treat everything else as a document
else:
category = _make_kind_category(googlecl.docs.DOCUMENT_LABEL)
entry.category.append(category)
To support uploading to folders for earlier versions of the API, expose the lower-level Post
return self.Post(entry, post_uri, media_source=media,
extra_headers={'Slug': media.file_name},
converter=gdata.docs.DocumentListEntryFromString)
SERVICE_CLASS = DocsServiceCL
Stolen from gdata-2.0.10 docs.service.
def _make_kind_category(label):
import atom
if label is None:
return None
documents_namespace = 'http://schemas.google.com/docs/2007'
return atom.Category(scheme=gdata.docs.service.DATA_KIND_SCHEME,
term=documents_namespace + '#' + label, label=label)