/gdata/docs/client.py
Python | 608 lines | 570 code | 12 blank | 26 comment | 7 complexity | 0e0aee6b0154368514d5a393394f5f38 MD5 | raw file
1#!/usr/bin/python 2# 3# Copyright 2009 Google Inc. All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""DocsClient extends gdata.client.GDClient to streamline DocList API calls.""" 18 19 20__author__ = 'e.bidelman (Eric Bidelman)' 21 22import mimetypes 23import urllib 24import atom.data 25import atom.http_core 26import gdata.client 27import gdata.docs.data 28import gdata.gauth 29 30 31# Feed URI templates 32DOCLIST_FEED_URI = '/feeds/default/private/full/' 33FOLDERS_FEED_TEMPLATE = DOCLIST_FEED_URI + '%s/contents' 34ACL_FEED_TEMPLATE = DOCLIST_FEED_URI + '%s/acl' 35REVISIONS_FEED_TEMPLATE = DOCLIST_FEED_URI + '%s/revisions' 36 37 38class DocsClient(gdata.client.GDClient): 39 """Client extension for the Google Documents List API.""" 40 41 host = 'docs.google.com' # default server for the API 42 api_version = '3.0' # default major version for the service. 43 auth_service = 'writely' 44 auth_scopes = gdata.gauth.AUTH_SCOPES['writely'] 45 46 def __init__(self, auth_token=None, **kwargs): 47 """Constructs a new client for the DocList API. 48 49 Args: 50 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 51 OAuthToken which authorizes this client to edit the user's data. 52 kwargs: The other parameters to pass to gdata.client.GDClient constructor. 53 """ 54 gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs) 55 56 def get_file_content(self, uri, auth_token=None, **kwargs): 57 """Fetches the file content from the specified uri. 58 59 This method is useful for downloading/exporting a file within enviornments 60 like Google App Engine, where the user does not have the ability to write 61 the file to a local disk. 62 63 Args: 64 uri: str The full URL to fetch the file contents from. 65 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 66 OAuthToken which authorizes this client to edit the user's data. 67 kwargs: Other parameters to pass to self.request(). 68 69 Returns: 70 The binary file content. 71 72 Raises: 73 gdata.client.RequestError: on error response from server. 74 """ 75 server_response = self.request('GET', uri, auth_token=auth_token, **kwargs) 76 if server_response.status != 200: 77 raise gdata.client.RequestError, {'status': server_response.status, 78 'reason': server_response.reason, 79 'body': server_response.read()} 80 return server_response.read() 81 82 GetFileContent = get_file_content 83 84 def _download_file(self, uri, file_path, auth_token=None, **kwargs): 85 """Downloads a file to disk from the specified URI. 86 87 Note: to download a file in memory, use the GetFileContent() method. 88 89 Args: 90 uri: str The full URL to download the file from. 91 file_path: str The full path to save the file to. 92 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 93 OAuthToken which authorizes this client to edit the user's data. 94 kwargs: Other parameters to pass to self.get_file_content(). 95 96 Raises: 97 gdata.client.RequestError: on error response from server. 98 """ 99 f = open(file_path, 'wb') 100 try: 101 f.write(self.get_file_content(uri, auth_token=auth_token, **kwargs)) 102 except gdata.client.RequestError, e: 103 f.close() 104 raise e 105 f.flush() 106 f.close() 107 108 _DownloadFile = _download_file 109 110 def get_doclist(self, uri=None, limit=None, auth_token=None, **kwargs): 111 """Retrieves the main doclist feed containing the user's items. 112 113 Args: 114 uri: str (optional) A URI to query the doclist feed. 115 limit: int (optional) A maximum cap for the number of results to 116 return in the feed. By default, the API returns a maximum of 100 117 per page. Thus, if you set limit=5000, you will get <= 5000 118 documents (guarenteed no more than 5000), and will need to follow the 119 feed's next links (feed.GetNextLink()) to the rest. See 120 get_everything(). Similarly, if you set limit=50, only <= 50 121 documents are returned. Note: if the max-results parameter is set in 122 the uri parameter, it is chosen over a value set for limit. 123 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 124 OAuthToken which authorizes this client to edit the user's data. 125 kwargs: Other parameters to pass to self.get_feed(). 126 127 Returns: 128 gdata.docs.data.DocList feed. 129 """ 130 if uri is None: 131 uri = DOCLIST_FEED_URI 132 133 if isinstance(uri, (str, unicode)): 134 uri = atom.http_core.Uri.parse_uri(uri) 135 136 # Add max-results param if it wasn't included in the uri. 137 if limit is not None and not 'max-results' in uri.query: 138 uri.query['max-results'] = limit 139 140 return self.get_feed(uri, desired_class=gdata.docs.data.DocList, 141 auth_token=auth_token, **kwargs) 142 143 GetDocList = get_doclist 144 145 def get_doc(self, resource_id, etag=None, auth_token=None, **kwargs): 146 """Retrieves a particular document given by its resource id. 147 148 Args: 149 resource_id: str The document/item's resource id. Example spreadsheet: 150 'spreadsheet%3A0A1234567890'. 151 etag: str (optional) The document/item's etag value to be used in a 152 conditional GET. See http://code.google.com/apis/documents/docs/3.0/ 153 developers_guide_protocol.html#RetrievingCached. 154 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 155 OAuthToken which authorizes this client to edit the user's data. 156 kwargs: Other parameters to pass to self.get_entry(). 157 158 Returns: 159 A gdata.docs.data.DocsEntry object representing the retrieved entry. 160 161 Raises: 162 ValueError if the resource_id is not a valid format. 163 """ 164 match = gdata.docs.data.RESOURCE_ID_PATTERN.match(resource_id) 165 if match is None: 166 raise ValueError, 'Invalid resource id: %s' % resource_id 167 return self.get_entry( 168 DOCLIST_FEED_URI + resource_id, etag=etag, 169 desired_class=gdata.docs.data.DocsEntry, 170 auth_token=auth_token, **kwargs) 171 172 GetDoc = get_doc 173 174 def get_everything(self, uri=None, auth_token=None, **kwargs): 175 """Retrieves the user's entire doc list. 176 177 The method makes multiple HTTP requests (by following the feed's next links) 178 in order to fetch the user's entire document list. 179 180 Args: 181 uri: str (optional) A URI to query the doclist feed with. 182 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 183 OAuthToken which authorizes this client to edit the user's data. 184 kwargs: Other parameters to pass to self.GetDocList(). 185 186 Returns: 187 A list of gdata.docs.data.DocsEntry objects representing the retrieved 188 entries. 189 """ 190 if uri is None: 191 uri = DOCLIST_FEED_URI 192 193 feed = self.GetDocList(uri=uri, auth_token=auth_token, **kwargs) 194 entries = feed.entry 195 196 while feed.GetNextLink() is not None: 197 feed = self.GetDocList( 198 feed.GetNextLink().href, auth_token=auth_token, **kwargs) 199 entries.extend(feed.entry) 200 201 return entries 202 203 GetEverything = get_everything 204 205 def get_acl_permissions(self, resource_id, auth_token=None, **kwargs): 206 """Retrieves a the ACL sharing permissions for a document. 207 208 Args: 209 resource_id: str The document/item's resource id. Example for pdf: 210 'pdf%3A0A1234567890'. 211 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 212 OAuthToken which authorizes this client to edit the user's data. 213 kwargs: Other parameters to pass to self.get_feed(). 214 215 Returns: 216 A gdata.docs.data.AclFeed object representing the document's ACL entries. 217 218 Raises: 219 ValueError if the resource_id is not a valid format. 220 """ 221 match = gdata.docs.data.RESOURCE_ID_PATTERN.match(resource_id) 222 if match is None: 223 raise ValueError, 'Invalid resource id: %s' % resource_id 224 225 return self.get_feed( 226 ACL_FEED_TEMPLATE % resource_id, desired_class=gdata.docs.data.AclFeed, 227 auth_token=auth_token, **kwargs) 228 229 GetAclPermissions = get_acl_permissions 230 231 def get_revisions(self, resource_id, auth_token=None, **kwargs): 232 """Retrieves the revision history for a document. 233 234 Args: 235 resource_id: str The document/item's resource id. Example for pdf: 236 'pdf%3A0A1234567890'. 237 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 238 OAuthToken which authorizes this client to edit the user's data. 239 kwargs: Other parameters to pass to self.get_feed(). 240 241 Returns: 242 A gdata.docs.data.RevisionFeed representing the document's revisions. 243 244 Raises: 245 ValueError if the resource_id is not a valid format. 246 """ 247 match = gdata.docs.data.RESOURCE_ID_PATTERN.match(resource_id) 248 if match is None: 249 raise ValueError, 'Invalid resource id: %s' % resource_id 250 251 return self.get_feed( 252 REVISIONS_FEED_TEMPLATE % resource_id, 253 desired_class=gdata.docs.data.RevisionFeed, auth_token=auth_token, 254 **kwargs) 255 256 GetRevisions = get_revisions 257 258 def create(self, doc_type, title, folder_or_id=None, writers_can_invite=None, 259 auth_token=None, **kwargs): 260 """Creates a new item in the user's doclist. 261 262 Args: 263 doc_type: str The type of object to create. For example: 'document', 264 'spreadsheet', 'folder', 'presentation'. 265 title: str A title for the document. 266 folder_or_id: gdata.docs.data.DocsEntry or str (optional) Folder entry or 267 the resouce id of a folder to create the object under. Note: A valid 268 resource id for a folder is of the form: folder%3Afolder_id. 269 writers_can_invite: bool (optional) False prevents collaborators from 270 being able to invite others to edit or view the document. 271 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 272 OAuthToken which authorizes this client to edit the user's data. 273 kwargs: Other parameters to pass to self.post(). 274 275 Returns: 276 gdata.docs.data.DocsEntry containing information newly created item. 277 """ 278 entry = gdata.docs.data.DocsEntry(title=atom.data.Title(text=title)) 279 entry.category.append(gdata.docs.data.make_kind_category(doc_type)) 280 281 if isinstance(writers_can_invite, gdata.docs.data.WritersCanInvite): 282 entry.writers_can_invite = writers_can_invite 283 elif isinstance(writers_can_invite, bool): 284 entry.writers_can_invite = gdata.docs.data.WritersCanInvite( 285 value=str(writers_can_invite).lower()) 286 287 uri = DOCLIST_FEED_URI 288 289 if folder_or_id is not None: 290 if isinstance(folder_or_id, gdata.docs.data.DocsEntry): 291 # Verify that we're uploading the resource into to a folder. 292 if folder_or_id.get_document_type() == gdata.docs.data.FOLDER_LABEL: 293 uri = folder_or_id.content.src 294 else: 295 raise gdata.client.Error, 'Trying to upload item to a non-folder.' 296 else: 297 uri = FOLDERS_FEED_TEMPLATE % folder_or_id 298 299 return self.post(entry, uri, auth_token=auth_token, **kwargs) 300 301 Create = create 302 303 def copy(self, source_entry, title, auth_token=None, **kwargs): 304 """Copies a native Google document, spreadsheet, or presentation. 305 306 Note: arbitrary file types and PDFs do not support this feature. 307 308 Args: 309 source_entry: gdata.docs.data.DocsEntry An object representing the source 310 document/folder. 311 title: str A title for the new document. 312 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 313 OAuthToken which authorizes this client to edit the user's data. 314 kwargs: Other parameters to pass to self.post(). 315 316 Returns: 317 A gdata.docs.data.DocsEntry of the duplicated document. 318 """ 319 entry = gdata.docs.data.DocsEntry( 320 title=atom.data.Title(text=title), 321 id=atom.data.Id(text=source_entry.GetSelfLink().href)) 322 return self.post(entry, DOCLIST_FEED_URI, auth_token=auth_token, **kwargs) 323 324 Copy = copy 325 326 def move(self, source_entry, folder_entry=None, 327 keep_in_folders=False, auth_token=None, **kwargs): 328 """Moves an item into a different folder (or to the root document list). 329 330 Args: 331 source_entry: gdata.docs.data.DocsEntry An object representing the source 332 document/folder. 333 folder_entry: gdata.docs.data.DocsEntry (optional) An object representing 334 the destination folder. If None, set keep_in_folders to 335 True to remove the item from all parent folders. 336 keep_in_folders: boolean (optional) If True, the source entry 337 is not removed from any existing parent folders it is in. 338 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 339 OAuthToken which authorizes this client to edit the user's data. 340 kwargs: Other parameters to pass to self.post(). 341 342 Returns: 343 A gdata.docs.data.DocsEntry of the moved entry or True if just moving the 344 item out of all folders (e.g. Move(source_entry)). 345 """ 346 entry = gdata.docs.data.DocsEntry(id=source_entry.id) 347 348 # Remove the item from any folders it is already in. 349 if not keep_in_folders: 350 for folder in source_entry.InFolders(): 351 self.delete( 352 '%s/contents/%s' % (folder.href, source_entry.resource_id.text), 353 force=True) 354 355 # If we're moving the resource into a folder, verify it is a folder entry. 356 if folder_entry is not None: 357 if folder_entry.get_document_type() == gdata.docs.data.FOLDER_LABEL: 358 return self.post(entry, folder_entry.content.src, 359 auth_token=auth_token, **kwargs) 360 else: 361 raise gdata.client.Error, 'Trying to move item into a non-folder.' 362 363 return True 364 365 Move = move 366 367 def upload(self, media, title, folder_or_uri=None, content_type=None, 368 auth_token=None, **kwargs): 369 """Uploads a file to Google Docs. 370 371 Args: 372 media: A gdata.data.MediaSource object containing the file to be 373 uploaded or a string of the filepath. 374 title: str The title of the document on the server after being 375 uploaded. 376 folder_or_uri: gdata.docs.data.DocsEntry or str (optional) An object with 377 a link to the folder or the uri to upload the file to. 378 Note: A valid uri for a folder is of the form: 379 /feeds/default/private/full/folder%3Afolder_id/contents 380 content_type: str (optional) The file's mimetype. If not provided, the 381 one in the media source object is used or the mimetype is inferred 382 from the filename (if media is a string). When media is a filename, 383 it is always recommended to pass in a content type. 384 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 385 OAuthToken which authorizes this client to edit the user's data. 386 kwargs: Other parameters to pass to self.post(). 387 388 Returns: 389 A gdata.docs.data.DocsEntry containing information about uploaded doc. 390 """ 391 uri = None 392 if folder_or_uri is not None: 393 if isinstance(folder_or_uri, gdata.docs.data.DocsEntry): 394 # Verify that we're uploading the resource into to a folder. 395 if folder_or_uri.get_document_type() == gdata.docs.data.FOLDER_LABEL: 396 uri = folder_or_uri.content.src 397 else: 398 raise gdata.client.Error, 'Trying to upload item to a non-folder.' 399 else: 400 uri = folder_or_uri 401 else: 402 uri = DOCLIST_FEED_URI 403 404 # Create media source if media is a filepath. 405 if isinstance(media, (str, unicode)): 406 mimetype = mimetypes.guess_type(media)[0] 407 if mimetype is None and content_type is None: 408 raise ValueError, ("Unknown mimetype. Please pass in the file's " 409 "content_type") 410 else: 411 media = gdata.data.MediaSource(file_path=media, 412 content_type=content_type) 413 414 entry = gdata.docs.data.DocsEntry(title=atom.data.Title(text=title)) 415 416 return self.post(entry, uri, media_source=media, 417 desired_class=gdata.docs.data.DocsEntry, 418 auth_token=auth_token, **kwargs) 419 420 Upload = upload 421 422 def download(self, entry_or_id_or_url, file_path, extra_params=None, 423 auth_token=None, **kwargs): 424 """Downloads a file from the Document List to local disk. 425 426 Note: to download a file in memory, use the GetFileContent() method. 427 428 Args: 429 entry_or_id_or_url: gdata.docs.data.DocsEntry or string representing a 430 resource id or URL to download the document from (such as the content 431 src link). 432 file_path: str The full path to save the file to. 433 extra_params: dict (optional) A map of any further parameters to control 434 how the document is downloaded/exported. For example, exporting a 435 spreadsheet as a .csv: extra_params={'gid': 0, 'exportFormat': 'csv'} 436 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 437 OAuthToken which authorizes this client to edit the user's data. 438 kwargs: Other parameters to pass to self._download_file(). 439 440 Raises: 441 gdata.client.RequestError if the download URL is malformed or the server's 442 response was not successful. 443 ValueError if entry_or_id_or_url was a resource id for a filetype 444 in which the download link cannot be manually constructed (e.g. pdf). 445 """ 446 if isinstance(entry_or_id_or_url, gdata.docs.data.DocsEntry): 447 url = entry_or_id_or_url.content.src 448 else: 449 if gdata.docs.data.RESOURCE_ID_PATTERN.match(entry_or_id_or_url): 450 url = gdata.docs.data.make_content_link_from_resource_id( 451 entry_or_id_or_url) 452 else: 453 url = entry_or_id_or_url 454 455 if extra_params is not None: 456 if 'exportFormat' in extra_params and url.find('/Export?') == -1: 457 raise gdata.client.Error, ('This entry type cannot be exported ' 458 'as a different format.') 459 460 if 'gid' in extra_params and url.find('spreadsheets') == -1: 461 raise gdata.client.Error, 'gid param is not valid for this doc type.' 462 463 url += '&' + urllib.urlencode(extra_params) 464 465 self._download_file(url, file_path, auth_token=auth_token, **kwargs) 466 467 Download = download 468 469 def export(self, entry_or_id_or_url, file_path, gid=None, auth_token=None, 470 **kwargs): 471 """Exports a document from the Document List in a different format. 472 473 Args: 474 entry_or_id_or_url: gdata.docs.data.DocsEntry or string representing a 475 resource id or URL to download the document from (such as the content 476 src link). 477 file_path: str The full path to save the file to. The export 478 format is inferred from the the file extension. 479 gid: str (optional) grid id for downloading a single grid of a 480 spreadsheet. The param should only be used for .csv and .tsv 481 spreadsheet exports. 482 auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or 483 OAuthToken which authorizes this client to edit the user's data. 484 kwargs: Other parameters to pass to self.download(). 485 486 Raises: 487 gdata.client.RequestError if the download URL is malformed or the server's 488 response was not successful. 489 """ 490 extra_params = {} 491 492 match = gdata.docs.data.FILE_EXT_PATTERN.match(file_path) 493 if match: 494 extra_params['exportFormat'] = match.group(1) 495 496 if gid is not None: 497 extra_params['gid'] = gid 498 499 self.download(entry_or_id_or_url, file_path, extra_params, 500 auth_token=auth_token, **kwargs) 501 502 Export = export 503 504 505class DocsQuery(gdata.client.Query): 506 507 def __init__(self, title=None, title_exact=None, opened_min=None, 508 opened_max=None, edited_min=None, edited_max=None, owner=None, 509 writer=None, reader=None, show_folders=None, 510 show_deleted=None, ocr=None, target_language=None, 511 source_language=None, convert=None, **kwargs): 512 """Constructs a query URL for the Google Documents List API. 513 514 Args: 515 title: str (optional) Specifies the search terms for the title of a 516 document. This parameter used without title_exact will only 517 submit partial queries, not exact queries. 518 title_exact: str (optional) Meaningless without title. Possible values 519 are 'true' and 'false'. Note: Matches are case-insensitive. 520 opened_min: str (optional) Lower bound on the last time a document was 521 opened by the current user. Use the RFC 3339 timestamp 522 format. For example: opened_min='2005-08-09T09:57:00-08:00'. 523 opened_max: str (optional) Upper bound on the last time a document was 524 opened by the current user. (See also opened_min.) 525 edited_min: str (optional) Lower bound on the last time a document was 526 edited by the current user. This value corresponds to the 527 edited.text value in the doc's entry object, which 528 represents changes to the document's content or metadata. 529 Use the RFC 3339 timestamp format. For example: 530 edited_min='2005-08-09T09:57:00-08:00' 531 edited_max: str (optional) Upper bound on the last time a document was 532 edited by the user. (See also edited_min.) 533 owner: str (optional) Searches for documents with a specific owner. Use 534 the email address of the owner. For example: 535 owner='user@gmail.com' 536 writer: str (optional) Searches for documents which can be written to 537 by specific users. Use a single email address or a comma 538 separated list of email addresses. For example: 539 writer='user1@gmail.com,user@example.com' 540 reader: str (optional) Searches for documents which can be read by 541 specific users. (See also writer.) 542 show_folders: str (optional) Specifies whether the query should return 543 folders as well as documents. Possible values are 'true' 544 and 'false'. Default is false. 545 show_deleted: str (optional) Specifies whether the query should return 546 documents which are in the trash as well as other 547 documents. Possible values are 'true' and 'false'. 548 Default is false. 549 ocr: str (optional) Specifies whether to attempt OCR on a .jpg, .png, or 550 .gif upload. Possible values are 'true' and 'false'. Default is 551 false. See OCR in the Protocol Guide: 552 http://code.google.com/apis/documents/docs/3.0/developers_guide_protocol.html#OCR 553 target_language: str (optional) Specifies the language to translate a 554 document into. See Document Translation in the Protocol 555 Guide for a table of possible values: 556 http://code.google.com/apis/documents/docs/3.0/developers_guide_protocol.html#DocumentTranslation 557 source_language: str (optional) Specifies the source language of the 558 original document. Optional when using the translation 559 service. If not provided, Google will attempt to 560 auto-detect the source language. See Document 561 Translation in the Protocol Guide for a table of 562 possible values (link in target_language). 563 convert: str (optional) Used when uploading arbitrary file types to 564 specity if document-type uploads should convert to a native 565 Google Docs format. Possible values are 'true' and 'false'. 566 The default is 'true'. 567 """ 568 gdata.client.Query.__init__(self, **kwargs) 569 self.convert = convert 570 self.title = title 571 self.title_exact = title_exact 572 self.opened_min = opened_min 573 self.opened_max = opened_max 574 self.edited_min = edited_min 575 self.edited_max = edited_max 576 self.owner = owner 577 self.writer = writer 578 self.reader = reader 579 self.show_folders = show_folders 580 self.show_deleted = show_deleted 581 self.ocr = ocr 582 self.target_language = target_language 583 self.source_language = source_language 584 585 def modify_request(self, http_request): 586 gdata.client._add_query_param('convert', self.convert, http_request) 587 gdata.client._add_query_param('title', self.title, http_request) 588 gdata.client._add_query_param('title-exact', self.title_exact, 589 http_request) 590 gdata.client._add_query_param('opened-min', self.opened_min, http_request) 591 gdata.client._add_query_param('opened-max', self.opened_max, http_request) 592 gdata.client._add_query_param('edited-min', self.edited_min, http_request) 593 gdata.client._add_query_param('edited-max', self.edited_max, http_request) 594 gdata.client._add_query_param('owner', self.owner, http_request) 595 gdata.client._add_query_param('writer', self.writer, http_request) 596 gdata.client._add_query_param('reader', self.reader, http_request) 597 gdata.client._add_query_param('showfolders', self.show_folders, 598 http_request) 599 gdata.client._add_query_param('showdeleted', self.show_deleted, 600 http_request) 601 gdata.client._add_query_param('ocr', self.ocr, http_request) 602 gdata.client._add_query_param('targetLanguage', self.target_language, 603 http_request) 604 gdata.client._add_query_param('sourceLanguage', self.source_language, 605 http_request) 606 gdata.client.Query.modify_request(self, http_request) 607 608 ModifyRequest = modify_request